分页分组查询结果,每页限制

时间:2018-12-19 13:11:53

标签: sql ruby-on-rails postgresql

我有一个项目表: [id, name, category_id]

查询表达式:name LIKE '%Smi%'

每页限制为100

查询结果应按类别分组,并且每个页面可以显示一个或多个包含项目的类别。

每个页面包含一个或多个类别组(包含项目),但是单个页面上所有类别内所有项目的总数不能大于per_page(100),但至少是一个类别。

类别可能不会在多个页面之间细分。

没有类别ID(null)的项目也应显示在组的中间。

结论:如何对组进行分组,并限制组的总项目数?

1 个答案:

答案 0 :(得分:2)

我没有找到一个查询的解决方案,因为行之间存在某些依赖关系,这会导致递归问题。这可能真的很残酷。例如(对于每组最大行数== 5):

CATEGORY_ID | NUMBER OF ROWS
------------+----------------
      1     |   4
      2     |   3
      3     |   2
      4     |   1

如果我只添加列,第一行将得到4。这是它自己的页面。下一个将是7行(4 + 3)。 7大于5,新页面。现在我将有9(4 + 3 + 2)。与以前相同的类别。接下来我将得到10。通常,下一页将在11生成。因此,第4个类别将与2和3放在同一页面中(这当然不合适,因为这是6行)。原因是简单的SUM不会计算第一页的一个空行(仅占用4行)。因此,从理论上讲,我们需要存储下一步5与实际填充的行之间的差。必须为下一行添加一个,如此。因此,每一行的每个SUM递归取决于前几行的差异。在一个简单的查询中确实很难做到这一点。


我的解决方案带有一个简单的命令性功能:

demo:db<>fiddle

CREATE OR REPLACE FUNCTION get_category_for_page(_max_rows int, _page_id int, _filter text) RETURNS int[] AS $$
DECLARE
    _remainder int := _max_rows;
    _page_counter int := 1;
    _categories int[] = '{}';
    _temprow record;
BEGIN
    FOR _temprow IN

        SELECT                                                    -- 1
            category_id, count(*)
        FROM categories 
        WHERE name LIKE _filter
        GROUP BY category_id
        ORDER BY category_id

    LOOP
        IF (_remainder - _temprow.count < 0) THEN                 -- 2
            _page_counter := _page_counter + 1;
            _remainder := _max_rows;
        END IF;

        IF (_page_counter > _page_id) THEN                        -- 3
            EXIT;
        END IF;

        _remainder := _remainder - _temprow.count;                -- 4

        IF (_page_counter = _page_id) THEN                        -- 5
            _categories := _categories || _temprow.category_id;
        END IF;
    END LOOP;

    RETURN _categories;
END;
$$ LANGUAGE plpgsql;

该函数具有3个参数:

  1. 每页最大行数
  2. 您感兴趣的页面的索引
  3. name过滤器文本

说明

  1. 此查询计算每个类别的行数。结果将在LOOP中进行迭代:
  2. _remainder存储当前页面已容纳多少行的值。如果当前类别的行多于其余行,则允许生成新页面(增加_page_counter),其余行将被重置。
  3. 如果_page_counter高于有趣的_page_id,则无需进一步计算
  4. 剩余部分将减少当前类别的行数
  5. 如果_page_counter等于有趣的_page_id,当前类别将添加到输出中。这可能会发生多次。

现在您可以通过以下方式调用该函数:

SELECT get_category_for_page(5, 1, '%A%');

所以最终您的查询将如下所示:

SELECT 
    *
FROM categories
WHERE 
    category_id = ANY(get_category_for_page(5, 1, '%A%')) 
    AND name LIKE '%A%'
ORDER BY id

免责声明

考虑一下_max_rows == 5。现在,您的第一个类别有6行。由于此类别将超出每页的最大行数,因此必须将其拆分以适合一页。但是您的约束条件表明类别不能拆分。因此,没有定义的行为可以处理这种特殊情况。因此,仅当每个类别的行数小于或等于_max_rows时,此函数才起作用。