我有一张桌子,上面有汽车的描述:
create table car
(
id serial constraint car_pk primary key,
vendor_name varchar not null,
model_name varchar not null,
body_type varchar not null,
specifications_name varchar not null,
price int4 not null
);
填充下一个数据:
INSERT INTO car(vendor_name, model_name, body_type, specifications_name, price) VALUES
('Peugeot', '408', 'Sedan', 'Allure 115hp brown', 1144000),
('LADA', 'Vesta', 'Sedan', 'Luxe seawave', 635000),
('Ford', 'Focus', 'Hatchback', 'Sync gray', 1109000),
('Ford', 'Focus', 'Sedan', 'Sync white', 1250800),
('LADA', 'Vesta', 'Sedan', 'Сlassic green', 631800),
('Audi', 'A4', 'Wagon', 'yellow', 2900000),
('Ford', 'Focus', 'Hatchback', 'Special tangerine', 1126000),
('LADA', 'Granta', 'Sedan', 'Comfort gray', 520000),
('LADA', 'Vesta', 'Sedan', 'Сomfort blue', 631100),
('Ford', 'Focus', 'Sedan', 'Trend blue', 1235000),
('LADA', 'Vesta', 'Wagon', 'Comfort orange', 679000),
('Audi', 'A4', 'Sedan', 'yellow', 2000000),
('LADA', 'Granta', 'Sedan', 'Luxe Prestige green', 576000),
('Peugeot', '408', 'Sedan', 'Active red', 1177000),
('Audi', 'A4', 'Sedan', 'yellow', 2000000),
('Ford', 'Focus', 'Sedan', 'Special tangerine', 1203000),
('LADA', 'Granta', 'Sedan', 'Luxe gray', 531000),
('Peugeot', '408', 'Sedan', 'Allure 150hp white', 1122000),
('Audi', 'A4', 'Wagon', 'gray', 2900000),
('LADA', 'Vesta', 'Wagon', 'Luxe white', 680000),
('Ford', 'Focus', 'Sedan', 'Special orange', 1211000),
('Ford', 'Focus', 'Hatchback', 'Special orange', 1125000),
('LADA', 'Vesta', 'Wagon', 'Comfort plum', 630000),
('Peugeot', '408', 'Sedan', 'Allure 150hp purple', 1125000),
('Audi', 'A3', 'HatchBack', 'white', 2000000),
('Ford', 'Focus', 'Hatchback', 'Special lemon', 1088000),
('LADA', 'Vesta', 'Wagon', 'Luxe blue', 699000),
('Ford', 'Focus', 'Sedan', 'Trend green', 1230000),
('LADA', 'Vesta', 'Sedan', 'Luxe dark green', 634000),
('Ford', 'Focus', 'Sedan', 'Sync gray', 1260000),
('LADA', 'Granta', 'Wagon', 'Comfort magenta', 566000),
('LADA', 'Granta', 'Sedan', 'Comfort red', 520000),
('LADA', 'Vesta', 'Sedan', 'Сlassic brown', 631000),
('Ford', 'Focus', 'Sedan', 'Special lemon', 1201000),
('Ford', 'Focus', 'Hatchback', 'Trend blue', 1065000),
('LADA', 'Vesta', 'Wagon', 'Luxe red', 679000),
('LADA', 'Granta', 'Wagon', 'Standart white', 520000),
('Audi', 'A4', 'Wagon', 'black', 3000000),
('LADA', 'Vesta', 'Sedan', 'Сomfort impressive', 641000),
('Ford', 'Focus', 'Sedan', 'Sync black', 1250000),
('LADA', 'Granta', 'Sedan', 'Standart black', 438000),
('Audi', 'A3', 'HatchBack', 'yellow', 2000000),
('LADA', 'Granta', 'Wagon', 'Standart black', 465030),
('LADA', 'Vesta', 'Sedan', 'Сlassic white', 638005),
('LADA', 'Granta', 'Wagon', 'Standart blue', 485000),
('LADA', 'Granta', 'Wagon', 'Comfort asphalt', 566000),
('Audi', 'A4', 'Wagon', 'white', 2900000),
('Ford', 'Focus', 'Hatchback', 'Trend white', 1027000),
('LADA', 'Granta', 'Sedan', 'Standart blue', 438000),
('LADA', 'Granta', 'Wagon', 'Luxe purple', 662000),
('LADA', 'Vesta', 'Wagon', 'Comfort yellow', 679010),
('Ford', 'Focus', 'Sedan', 'Trend white', 1230000),
('Audi', 'A3', 'HatchBack', 'black', 2000000),
('LADA', 'Granta', 'Wagon', 'Comfort cyan', 566000),
('LADA', 'Granta', 'Wagon', 'Luxe brown', 662080),
('LADA', 'Granta', 'Wagon', 'Luxe like a boss', 662100),
('LADA', 'Vesta', 'Sedan', 'Сomfort navy', 631000),
('LADA', 'Vesta', 'Sedan', 'Luxe blue', 636000),
('Ford', 'Focus', 'Hatchback', 'Sync black', 1082000),
('Ford', 'Focus', 'Hatchback', 'Sync white', 1092000)
;
我对汽车进行分类的方式是
所以,这是它的查询:
SELECT
*,
MIN(price) OVER win_vendor min_price_vendor,
MIN(price) OVER win_model min_price_model,
MIN(price) OVER win_body min_price_body
FROM
car
WINDOW
win_vendor AS (PARTITION BY vendor_name),
win_model AS (PARTITION BY vendor_name, model_name),
win_body AS (PARTITION BY vendor_name, model_name, body_type)
ORDER BY
min_price_vendor,
min_price_model,
min_price_body,
price,
specifications_name
我想问你如何处理分页。 我需要将排序后的结果分页到页面,行数彼此不同,所以我不能使用LIMIT / OFFSET函数; 我需要每个页面的起始(或结束)在vendor-model-body块包含至少N行的边缘。
我认为,最好以N = 10行为例进行说明: click for image。
根据上面显示的数据,我查看了15、15、17、13行大小的页面。
我希望有一个page_number字段,将“ WHERE page_number = K”添加到我的应用查询中以获得第K个页面。
请告诉我如何为这种情况形成页码字段。
谢谢!
答案 0 :(得分:1)
我已经在这里做过非常类似的事情:Paginate grouped query results with limit per page
正如我所说,我没有找到任何针对单个查询的解决方案。问题在于您的页面可以产生非常动态的行数。因此,每个页面的内容几乎都不依赖于以前的页面。因此,您无法在一个查询中找到一个简单的解决方案,该查询在其前几行引用了它自己的结果。
因此,您将需要一些功能来创建结果。我编写了一个函数,该函数接受参数“每页的最小行数”和“预期的页面ID”(我将上面的SO问题中的函数作为该函数的基础-所以两个结果都非常相似):
CREATE OR REPLACE FUNCTION get_category_for_page(_min_rows int, _page_id int) RETURNS int[] AS $$
DECLARE
_remainder int := _min_rows;
_page_counter int := 1;
_categories int[] = '{}';
_temprow record;
BEGIN
FOR _temprow IN
SELECT -- 1
min_price_vendor,
min_price_model,
min_price_body,
COUNT(*)
FROM (
-- <your query>
) s
GROUP BY
min_price_vendor,
min_price_model,
min_price_body
ORDER BY
min_price_vendor,
min_price_model,
min_price_body
LOOP
IF (_page_counter = _page_id) THEN -- 2
_categories := _categories || _temprow.min_price_body;
END IF;
IF (_remainder - _temprow.count < 0) THEN -- 3
_page_counter := _page_counter + 1;
_remainder := _max_rows;
ELSE
_remainder := _remainder - _temprow.count; -- 4
END IF;
IF (_page_counter > _page_id) THEN -- 5
EXIT;
END IF;
END LOOP;
RETURN _categories;
END;
$$ LANGUAGE plpgsql;
说明:
LOOP
中进行迭代:_page_counter
等于有趣的_page_id
,当前类别将添加到输出中。这可能会发生多次。_remainder
存储当前页面已容纳多少行的值。如果当前类别的行多于其余行,则允许生成新页面(增加_page_counter
),其余行将被重置。_page_counter
高于有趣的_page_id
,则无需进一步计算现在您可以通过以下方式调用该函数:
SELECT get_category_for_page(10, 2);
所以最终您的查询将如下所示:
SELECT
*
FROM -- <your query>
WHERE
min_price_body= ANY(get_category_for_page(10, 2))
免责声明
我认为应该对某些特殊情况进行测试(在失败的测试中,必须增加功能),但总的来说,这种想法应该可行。
答案 1 :(得分:1)
在我看来,主要问题是保存页面迭代器的状态。 自定义窗口函数可能是最好的解决方案,但是我无法在Google上找到编写它的示例。
我发现,PostgreSql允许保存“静态变量”。我们可以使用current_setting / set_config函数。 set_config还允许仅将值保存为活动交易,这已经足够了。
因此,我使用这些“静态变量”编写了一个函数,该函数可以与具有字符串分组键的排序列表一起使用。在我的情况下,此密钥是vendor-model-body。
CREATE OR REPLACE FUNCTION grouped_pagination_page(current_key VARCHAR, per_page INT4) RETURNS INT4 AS $$
DECLARE
last_key VARCHAR;
last_row_count INT4;
last_page INT4;
BEGIN
SELECT COALESCE(current_setting('GPP.last_key', TRUE), '') INTO last_key;
SELECT CAST(COALESCE(NULLIF(current_setting('GPP.last_row_count', TRUE),''),'0') AS INT) INTO last_row_count;
SELECT CAST(COALESCE(NULLIF(current_setting('GPP.last_page', TRUE),''),'1') AS INT) INTO last_page;
IF current_key <> last_key THEN
PERFORM set_config('GPP.last_key', current_key, TRUE);
IF last_row_count >= per_page THEN
last_page = last_page + 1;
last_row_count = 0;
PERFORM set_config('GPP.last_page', last_page::VARCHAR, TRUE);
END IF;
END IF;
last_row_count = last_row_count + 1;
PERFORM set_config('GPP.last_row_count', last_row_count::VARCHAR, TRUE);
RETURN last_page;
END;
$$ LANGUAGE 'plpgsql';
因此,这是我的一个带有page_number字段的查询,该字段的页数是可变行:
SELECT *,
MIN(price) OVER win_vendor min_price_vendor,
MIN(price) OVER win_model min_price_model,
MIN(price) OVER win_body min_price_body,
grouped_pagination_page((vendor_name || model_name || body_type)::VARCHAR, 10) page_number
FROM
car
WINDOW
win_vendor AS (PARTITION BY vendor_name),
win_model AS (PARTITION BY vendor_name, model_name),
win_body AS (PARTITION BY vendor_name, model_name, body_type)
ORDER BY min_price_vendor,
min_price_model,
min_price_body,
price,
specifications_name
它返回预期的15、15、17、13页的页面;
这不是优雅的解决方案,但可以。