sql - PostgreSQL - 按行组分页

标签 sql postgresql

我有一张包含汽车描述的表格:

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 block 的边缘开始(或结束),至少包含 N 行。

我认为,最好举例说明 N=10 行的示例: click for image .

根据上面显示的数据,我有 15、15、17、13 行大小的页面。

我希望有一个 page_number 字段来将“WHERE page_number = K”添加到我的应用查询中以获取第 K 页。

请告诉我如何为这种情况形成页码字段。

谢谢!

最佳答案

我已经在这里做了非常相似的事情:Paginate grouped query results with limit per page

正如我在那里所说的那样,我没有找到针对单个查询的任何解决方案。问题是您的页面会产生非常动态的行数。所以每个页面的内容几乎不依赖于之前的页面。因此,您无法在一个查询中找到一个简单的解决方案,该查询在几行之前引用了它自己的结果。

所以你需要一些函数来创建你的结果。我写了一个函数,它接受参数“每页的最小行数”和“预期的页面 ID”(我从上面的 SO 问题中获取函数作为这个问题的基础 - 所以两个结果非常相似):

demo:db<>fiddle

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;

解释:

  1. 此查询计算查询中每个类别的行数。结果将在 LOOP 中迭代:
  2. 如果 _page_counter 等于有趣的 _page_id,则当前类别将被添加到输出中。这可能会发生多次。
  3. _remainder 存储当前页面已容纳多少行的值。如果当前类别的行数超过剩余允许的行数,则会生成一个新页面(_page_counter 增加)并且剩余的将被重置。
  4. 否则余数会减去当前类别的行数
  5. 如果 _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)) 

免责声明

我相信应该测试某些特殊情况(并且在测试失败时必须增加功能)但总的来说这个想法应该可行。

关于sql - PostgreSQL - 按行组分页,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/54329389/

相关文章:

sql - 将一列值拆分为多列值

sql - 在 sql server 2008 中使用最小匹配条件运行 SQL 查询

SQL:计算每个国家的红黄牌数

sql - Postgres : How to set column default value as another column value while altering the table

sql - 如何获取此类数据?

php - 如何在 laravel 中显示发件人消息右侧和接收者消息左侧

Python解析SQL并查找关系

sql - 将唯一值插入数据库 PGSQL/MSSQL

postgresql - Amazon RDS IAM PAM 身份验证失败

sql - 为什么在 PostgreSQL 查询中对 DESC 进行排序时 NULL 值排在第一位?