选择前 N 行,其中 TEXT 字段的长度总和达到某个限制

Posted

技术标签:

【中文标题】选择前 N 行,其中 TEXT 字段的长度总和达到某个限制【英文标题】:Selecting first N rows, where sum of lengths of TEXT field is up to some limit 【发布时间】:2017-09-22 15:14:23 【问题描述】:

我有一张这样的桌子:

CREATE TABLE cache (
  id BIGSERIAL PRIMARY KEY,
  source char(2) NOT NULL,
  target char(2) NOT NULL,
  q TEXT NOT NULL,
  result TEXT,
  profile TEXT NOT NULL DEFAULT '',
  created TIMESTAMP NOT NULL DEFAULT now(),
  api_engine text NOT NULL,
  encoded TEXT NOT NULL
);

我想传递 encoded 字段的列表(也许 OVER ... WINDOW ?) 类似于:

SELECT id, string_agg(encoded, '&q=') FROM cache

所以我会得到相应的 id 列表,以及一串串联字段编码'&q=encoded1&q=encoded2&q=encoded3' ...总长度不超过某个限制(比如不超过 2000 个字符)。

第二个条件,我想转到下一个窗口,当其中一个字段:源、目标或配置文件发生更改时。

如果在 FOR LOOP 中使用 SQL SELECT 可以做到这一点?

我知道如何使用 plpgsql/plpython/plperl 做到这一点,但我想优化这个请求。

FOR rec IN
  SELECT array_agg(id) AS ids, string_agg(encoded, '&q=') AS url FROM cache
  WHERE result IS NULL
  ORDER BY source, target
LOOP
  -- here I call curl with that *url*

示例数据:

INSERT INTO cache (id, source, target, q, result, profile, api_engine, encoded) VALUES
   (1, 'ru', 'en', 'Длинная фраза по-русски'            , NULL, '', 'google', '%D0%94%D0%BB%D0%B8%D0%BD%D0%BD%D0%B0%D1%8F+%D1%84%D1%80%D0%B0%D0%B7%D0%B0+%D0%BF%D0%BE-%D1%80%D1%83%D1%81%D1%81%D0%BA%D0%B8')
 , (2, 'ru', 'es', 'Ещё одна непонятная фраза по-русски', NULL, '', 'google', '%D0%95%D1%89%D1%91+%D0%BE%D0%B4%D0%BD%D0%B0+%D0%BD%D0%B5%D0%BF%D0%BE%D0%BD%D1%8F%D1%82%D0%BD%D0%B0%D1%8F+%D1%84%D1%80%D0%B0%D0%B7%D0%B0+%D0%BF%D0%BE-%D1%80%D1%83%D1%81%D1%81%D0%BA%D0%B8')
-- etc...

等等,100500 行这样的。 sourcetarget 字段可以是不同的语言代码,它们会重复,所以我可能需要做GROUP BY source, target, profile

我想选择前 N 行,其中字段 encoded 与一些分隔符(如

)连接
&q=%D0%94%D0%BB%D0%B8%D0%BD%D0%BD%D0%B0%D1%8F+%D1%84%D1%80%D0%B0%D0%B7%D0%B0+%D0%BF%D0%BE-%D1%80%D1%83%D1%81%D1%81%D0%BA%D0%B8&q=%D0%95%D1%89%D1%91+%D0%BE%D0%B4%D0%BD%D0%B0+%D0%BD%D0%B5%D0%BF%D0%BE%D0%BD%D1%8F%D1%82%D0%BD%D0%B0%D1%8F+%D1%84%D1%80%D0%B0%D0%B7%D0%B0+%D0%BF%D0%BE-%D1%80%D1%83%D1%81%D1%81%D0%BA%D0%B8

所以这个连接字符串的长度不超过(2000)个字符。所以我将在 url 中包含该字符串以及这些行的所有 id(当然,以相同的顺序)。

然后我想选择具有相同条件的下 N 行,依此类推。

【问题讨论】:

您能否编辑您的问题并提供示例数据和所需的结果? 长度超过2000个字符怎么办? 如果长度超过配额,我将结果推送到函数中,该函数将发送/接收来自 http API 的慢速答案,并从表的下一部分重新开始。 对不起,我的定义不准确。确保该请求将进入 FOR IN SELECT LOOP。 encoded 未定义 NOT NULL?领先的http://google.translation-api.com/api/v2&q=来自哪里?它是否计入 2000 个字符的限制?您的 Postgres 版本是什么? 【参考方案1】:

您可以使用智能递归 CTE 来做到这一点:

WITH RECURSIVE c AS ( -- 1st CTE is not recursive
   SELECT dense_rank()  OVER (ORDER BY     source, target, profile)             AS rnk
        , row_number()  OVER (PARTITION BY source, target, profile ORDER BY id) AS rn
        , lead(encoded) OVER (PARTITION BY source, target, profile ORDER BY id) AS next_enc
        , id, encoded
   FROM   cache
   )

 , rcte AS (  -- "recursion" starts here
   SELECT rnk, rn, ARRAY[id] AS ids, encoded AS url
        , CASE WHEN length(concat_ws('&q=', encoded || next_enc)) > 2000  -- max len
                 OR next_enc IS NULL  -- last in partition
               THEN TRUE END AS print
   FROM   c
   WHERE  rn = 1

   UNION ALL
   SELECT c.rnk, c.rn
        , CASE WHEN r.print THEN ARRAY[id] ELSE r.ids || c.id                      END AS ids
        , CASE WHEN r.print THEN c.encoded ELSE concat_ws('&q=', r.url, c.encoded) END AS url
        , CASE WHEN length(
             CASE WHEN r.print THEN concat_ws('&q=', c.encoded, c.next_enc)
                  ELSE concat_ws('&q=', r.url, c.encoded, c.next_enc) END) > 2000  -- max len
                 OR c.next_enc IS NULL  -- last in partition
               THEN TRUE END AS print
   FROM   rcte r
   JOIN        c USING (rnk)
   WHERE  c.rn = r.rn + 1
   )
SELECT ids, url
FROM   rcte
WHERE  print
ORDER  BY rnk, rn;

关于包含非递归 CTE 的 rCTE:

Multiple CTE in single query

但这可能是在 plpgsql 函数中循环实际上更快的罕见情况之一。

查看这个相关答案以获得更多解释:

Grouping or Window

【讨论】:

谢谢你,Erwin,我从未听说过 CTE。它看起来比普通的循环复杂得多。正是您的请求返回了重复项。 ids, url: 1,2,3,4,18,19,21,22,23,25,37, '%%%here long string'; 1,2,3,4,18,19,21,22,23,25,37,38, '%%%previous string + some data'; 1,2,3,4,18,19,21,22,23,25,37,38,39, '%%%等等'; @Dimitri:抱歉,我很着急,忘记在打印后重新启动聚合。现在修好了。无论如何,这只是一个概念证明。我几乎可以肯定,对于这种特殊情况,循环遍历表更快更简单。

以上是关于选择前 N 行,其中 TEXT 字段的长度总和达到某个限制的主要内容,如果未能解决你的问题,请参考以下文章

需要 SQL 来选择行,直到列的总和达到最后一行不会完全消耗值的值

达到最大长度值后,将下一个输入聚焦

木板切割问题——贪心

如何从 T-SQL 中的表中选择前 N 行?

BZOJ 2006 超级钢琴(堆+主席树)

如何用行上的条件定义总和?