sql - 按组限制结果集

标签 sql postgresql

我有几个表,在最简单的情况下它们是 1:N 相关的;例如:

CREATE TABLE X (id INTEGER, foo VARCHAR(16));
CREATE TABLE Y (id INTEGER, x_id INTEGER, bar VARCHAR(16));

里面有一些数据,例如:

INSERT INTO X VALUES (1, 'a'), (2, 'b'), (3, 'c');
INSERT INTO Y VALUES
    (1, 1, 'blue'), (2, 1, 'green'), (3, 1, 'red'),
    (4, 2, 'big'), (5, 2, 'small'),
    (6, 3, 'car'), (7, 3, 'bike'), (8, 3, 'skate');

我想要做的:从由 x.id 限制的两个表中选择实体。例如,我需要选择2个实体,那么这将无法正常工作:

SELECT x.id, x.foo, y.id, y.bar
FROM x INNER JOIN y ON x.id = y.x_id
LIMIT 2

因为 if 将对最终结果行集应用限制,而不是基于 x.id 。结果应该是:

x.id x.foo y.id y.bar
 1    'a'   1  'blue'
 1    'a'   2  'green'
 1    'a'   3  'red'
 2    'b'   4  'big'
 2    'b'   5  'small'

我的想法是编写一些 block 分子,它将“ block ”号添加到结果行集中,然后使用 WHERE 而不是 LIMIT,所以:

SELECT block_number, x.id, x.foo, y.id, y.bar
FROM x INNER JOIN y ON x.id = y.x_id
WHERE block_number <= 2

但到目前为止我还没有找到任何好的方法来获取该区 block 号。 “好”的意思是:

  • 没有子查询。原始选择可能相当复杂,涉及许多表,但速度很快,因为连接都是通过索引字段进行的。而且我不想破坏性能
  • 理想情况下,没有新的连接。某些窗口函数可能是很好的解决方案,因为 Postgres 支持该功能

我尝试做这样的事情:

SELECT row_number() OVER (PARTITION BY x.id), x.id, x.foo, y.id, y.bar
FROM x INNER JOIN y ON x.id = y.x_id
WHERE block_number <= 2

但是,遗憾的是,它提供了in block 中的行号,而不是per block 的行号,因此不能用于我的问题。

此外,选择 x 条记录并在子查询中“预先”限制它,如下所示:

SELECT x.id, x.foo, y.id, y.bar
FROM 
  (SELECT x.id LIMIT 2) AS x
    INNER JOIN y ON x.id = y.x_id

不是一个选项。这是因为:

  • 原始查询包含所有表中的字段,但我不确定哪个字段属于哪个表。我可以添加一些机制来确定这一点,但这会减慢应用程序的速度。初始查询是动态生成的
  • 更多,原始查询可能包含 ORDER BY 子句,这破坏了这个想法,因为我无法为仅适用于表 的子查询编写正确的 ORDER BY x

所以,重要补充:解决方案应该尊重原始查询顺序。最简单的说明方法是:

SELECT block_number, x.id, x.foo, y.id, y.bar
FROM x INNER JOIN y ON x.id = y.x_id
WHERE block_number <= 2
ORDER BY x.id DESC

应该产生:

block_number x.id x.foo y.id y.bar
     1        3    'c'   6  'car'
     1        3    'c'   7  'bike'
     1        3    'c'   8  'skate'
     2        2    'b'   4  'big'
     2        2    'b'   5  'small'

即应正确计数 block 以保持原始顺序。而且,我无法预测初始顺序,因为它是动态指定的。

附注

抱歉,没有为您提供 sql fiddle。该资源对我不起作用,而且已经这样了很长一段时间了。

此外,我的 Postgres 版本是:9.4,因此可以应用任何可能实现我的目标的"new"功能。

最佳答案

可以使用窗口函数dense_rank() 。 窗口函数不能在 WHERE 子句中调用,因此必须将其放在子查询中:

SELECT xid, foo, yid, bar
FROM (
    SELECT 
        x.id xid, x.foo, y.id yid, y.bar,
        dense_rank() over (order by x.id) rank
    FROM x INNER JOIN y ON x.id = y.x_id
    ) sub
WHERE rank <= 2;

 xid | foo | yid |  bar  
-----+-----+-----+-------
   1 | a   |   1 | blue
   1 | a   |   2 | green
   1 | a   |   3 | red
   2 | b   |   4 | big
   2 | b   |   5 | small
(5 rows)

关于sql - 按组限制结果集,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/35287437/

相关文章:

mysql - 表中开始日期和结束日期之间的周末事件

postgresql - Postgres 的 ETL 选项

mysql - Heroku (Postgres) 上的 Where 子句中断,但在本地运行良好 (MySQL)

mysql - 如何通过连接表中的引用对查找记录?

mysql - 表已存在sql

sql - 计算所有记录中两个日期时间之间的平均时间?

java - 自动持久化一个复杂的 Java 对象

Mysql - 灵活的,类似excel的结构

visual-studio-2008 - 是否可以使用 .NET 应用程序嵌入 PostgreSQL 数据库设置?

python - 使用 sqlalchemy 查询特定的 JSON 列(postgres)