我有一个项目表:
[id, name, category_id]
查询表达式:name LIKE '%Smi%'
每页限制为100
。
查询结果应按类别分组,并且每个页面可以显示一个或多个包含项目的类别。
每个页面包含一个或多个类别组(包含项目),但是单个页面上所有类别内所有项目的总数不能大于per_page
(100),但至少是一个类别。
类别可能不会在多个页面之间细分。
没有类别ID(null
)的项目也应显示在组的中间。
结论:如何对组进行分组,并限制组的总项目数?
答案 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递归取决于前几行的差异。在一个简单的查询中确实很难做到这一点。
我的解决方案带有一个简单的命令性功能:
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个参数:
name
过滤器文本说明:
LOOP
中进行迭代:_remainder
存储当前页面已容纳多少行的值。如果当前类别的行多于其余行,则允许生成新页面(增加_page_counter
),其余行将被重置。_page_counter
高于有趣的_page_id
,则无需进一步计算_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
时,此函数才起作用。