为列中的每个不同值获取 2 行
Get 2 rows for each distinct value in column
我有一个 table 包含定期(500 毫秒)以 10 个为一组处理的队列项。
select * from tbl_queue order by prio asc;
----------------------------
client_id |object_key |prio
----------------------------
1 |4711 | 10
1 |2831 | 10
1 |1912 | 10
1 |1913 | 10
1 |1914 | 10
1 |1915 | 10
1 |1922 | 10
1 |1933 | 10
1 |1944 | 10
1 |1955 | 10
1 |1966 | 10
2 |7861 | 10
2 |1234 | 10
3 |5463 | 10
3 |5464 | 10
4 |7341 | 10
4 |7342 | 10
5 |9425 | 10
5 |9426 | 10
5 |9427 | 10
每次我 select 从 table 我限制我想工作的数量:
select * from tbl_queue order by prio asc limit 10;
client_id |object_key |prio
----------------------------
1 |4711 | 10
1 |2831 | 10
1 |1912 | 10
1 |1913 | 10
1 |1914 | 10
1 |1915 | 10
1 |1922 | 10
1 |1933 | 10
1 |1944 | 10
1 |1955 | 10
但是,我想平等对待每个客户,因此我想要的结果是:
----------------------------
client_id |object_key |prio
----------------------------
1 |1913 | 10
1 |1966 | 10
2 |7861 | 10
2 |1234 | 10
3 |5463 | 10
3 |5464 | 10
4 |7341 | 10
4 |7342 | 10
5 |9425 | 10
5 |9426 | 10
请注意,我不关心选择了哪个 object_key。有两个重要要求:
- 如果存在来自不同客户的项目,每个项目
客户端必须 selected(最好是同等)。
- 查询必须总是 return 10 行,
除非少于 10 行。
建议的解决方案必须在 Mysql 和 PostgreSQL 中工作,并且从 table.
插入和 select 的成本不能太高
我已经考虑添加像 sort_idx 这样的列,并在每一行中插入每个客户的实际行数,所以我可以这样做:
--------------------------------------
client_id |object_key |prio| sort_idx
--------------------------------------
1 |4711 | 10 | 1
1 |2831 | 10 | 2
1 |1912 | 10 | 3
1 |1913 | 10 | 4
1 |1914 | 10 | 5
1 |1915 | 10 | 6
1 |1922 | 10 | 7
1 |1933 | 10 | 8
1 |1944 | 10 | 9
1 |1955 | 10 | 10
1 |1966 | 10 | 11
2 |7861 | 10 | 1
2 |1234 | 10 | 2
3 |5463 | 10 | 1
3 |5464 | 10 | 2
4 |7341 | 10 | 1
4 |7342 | 10 | 2
5 |9425 | 10 | 1
5 |9426 | 10 | 2
5 |9427 | 10 | 3
select * from tbl_queue order by prio, sort_index asc limit 10;
--------------------------------------
client_id |object_key |prio| sort_idx
--------------------------------------
1 |4711 | 10 | 1
2 |7861 | 10 | 1
3 |5463 | 10 | 1
4 |7341 | 10 | 1
5 |9425 | 10 | 1
1 |2831 | 10 | 2
2 |1234 | 10 | 2
3 |5464 | 10 | 2
4 |7342 | 10 | 2
5 |9426 | 10 | 2
但是,我对有效计算每个插入的排序索引不太有信心。可能会出现一次插入 20.000 行(或以快速方式)的情况。
我不是在寻找引入一些变量或函数的解决方案,我也不想引入数据库触发器,只是简单的sql。
编辑 2017 年 9 月 13 日 17:13: 如果在 springs 的 JdbcTemplate 上下文中可以使用用户定义的变量,我想这是一个接受table解决方案。
但是我希望在数据库层上尽可能简单(即不使用 database-specific/exclusive 命令)
编辑 26.09.2017 11:18
因此,我针对包含大约 40.000 条记录的真实数据集测试了建议的解决方案,但我不得不发现它的性能并不理想。事实上,我在大约一分钟后取消了查询执行。
对该查询的解释给出了以下输出:
explain select c1.client_id,
c1.object_key,
c1.prio,
count(*)-1 as sort_idx
from clients as c1
left join clients as c2
on c1.client_id = c2.client_id
and c1.object_key >= c2.object_key
and c1.prio >= c2.prio
group by c1.client_id,
c1.object_key,
c1.prio
order by
c1.prio,
sort_idx
limit 10;
Limit (cost=512.53..512.56 rows=10 width=12)
-> Sort (cost=512.53..513.03 rows=200 width=12)
Sort Key: c1.prio, ((count(*) - 1))
-> HashAggregate (cost=505.71..508.21 rows=200 width=12)
Group Key: c1.client_id, c1.object_key, c1.prio
-> Nested Loop Left Join (cost=0.15..484.80 rows=2091 width=12)
-> Seq Scan on clients c1 (cost=0.00..29.40 rows=1940 width=12)
-> Index Only Scan using clients_idx on clients c2 (cost=0.15..0.22 rows=1 width=12)
Index Cond: ((client_id = c1.client_id) AND (object_key <= c1.object_key) AND (prio <= c1.prio))
我不是解释解释计划的专家,但每当成本数字很高或我看到嵌套循环连接时,我就会感觉到危险。
使用密集排名函数给出的语句工作得更快,不幸的是我需要支持 mysql 和 postgres。
通常(MSSQL、Oracle、PostgreSQL等)这个问题可以借助DENSE_RANK函数来解决。
但是因为 MySQL 没有 window 函数,你可以这样做:
select c1.client_id,
c1.object_key,
c1.prio,
count(*)-1 as sort_idx
from clients as c1
left join clients as c2
on c1.client_id = c2.client_id
and c1.object_key >= c2.object_key
and c1.prio >= c2.prio
group by c1.client_id,
c1.object_key,
c1.prio
order by
c1.prio,
sort_idx
limit 10;
我做了一个fiddle,欢迎大家测试
更新:
还使用 DENSE_RANK 为 PostgreSQL 制定了一个解决方案 - 以防其他人不会受到 MySQL 限制的限制:
select c.client_id,
c.object_key,
c.prio,
DENSE_RANK() OVER
(PARTITION BY c.client_id ORDER BY c.object_key) as sort_idx
from clients as c
order by
c.prio,
sort_idx
limit 10;
还有 fiddle
更新 2:
为 dense_rank
计算制定了 MySQL 特定解决方案,在 40000 条记录上比自连接快得多。但它依赖于顺序,因此您必须以某种方式使用此查询结果(可能使用临时 table)来获取按 prio ASC, dense_rank ASC
.
排序的结果
SELECT
t2.client_id,
t2.object_key,
t2.prio,
t2.dense_rank
FROM
(SELECT
t1.*,
@dense:=IF(@prev_client_id=t1.client_id AND @prev_prio=t1.prio, @dense+1, 1) AS dense_rank,
@prev_client_id:=t1.client_id,
@prev_prio:=t1.prio
FROM (select * from clients c1 order by prio, client_id) t1,
(SELECT @dense:=0, @prev_client_id:=NULL, @prev_prio:=NULL) var1
) t2;
还有 fiddle.
我有一个 table 包含定期(500 毫秒)以 10 个为一组处理的队列项。
select * from tbl_queue order by prio asc;
----------------------------
client_id |object_key |prio
----------------------------
1 |4711 | 10
1 |2831 | 10
1 |1912 | 10
1 |1913 | 10
1 |1914 | 10
1 |1915 | 10
1 |1922 | 10
1 |1933 | 10
1 |1944 | 10
1 |1955 | 10
1 |1966 | 10
2 |7861 | 10
2 |1234 | 10
3 |5463 | 10
3 |5464 | 10
4 |7341 | 10
4 |7342 | 10
5 |9425 | 10
5 |9426 | 10
5 |9427 | 10
每次我 select 从 table 我限制我想工作的数量:
select * from tbl_queue order by prio asc limit 10;
client_id |object_key |prio
----------------------------
1 |4711 | 10
1 |2831 | 10
1 |1912 | 10
1 |1913 | 10
1 |1914 | 10
1 |1915 | 10
1 |1922 | 10
1 |1933 | 10
1 |1944 | 10
1 |1955 | 10
但是,我想平等对待每个客户,因此我想要的结果是:
----------------------------
client_id |object_key |prio
----------------------------
1 |1913 | 10
1 |1966 | 10
2 |7861 | 10
2 |1234 | 10
3 |5463 | 10
3 |5464 | 10
4 |7341 | 10
4 |7342 | 10
5 |9425 | 10
5 |9426 | 10
请注意,我不关心选择了哪个 object_key。有两个重要要求:
- 如果存在来自不同客户的项目,每个项目 客户端必须 selected(最好是同等)。
- 查询必须总是 return 10 行, 除非少于 10 行。
建议的解决方案必须在 Mysql 和 PostgreSQL 中工作,并且从 table.
插入和 select 的成本不能太高我已经考虑添加像 sort_idx 这样的列,并在每一行中插入每个客户的实际行数,所以我可以这样做:
--------------------------------------
client_id |object_key |prio| sort_idx
--------------------------------------
1 |4711 | 10 | 1
1 |2831 | 10 | 2
1 |1912 | 10 | 3
1 |1913 | 10 | 4
1 |1914 | 10 | 5
1 |1915 | 10 | 6
1 |1922 | 10 | 7
1 |1933 | 10 | 8
1 |1944 | 10 | 9
1 |1955 | 10 | 10
1 |1966 | 10 | 11
2 |7861 | 10 | 1
2 |1234 | 10 | 2
3 |5463 | 10 | 1
3 |5464 | 10 | 2
4 |7341 | 10 | 1
4 |7342 | 10 | 2
5 |9425 | 10 | 1
5 |9426 | 10 | 2
5 |9427 | 10 | 3
select * from tbl_queue order by prio, sort_index asc limit 10;
--------------------------------------
client_id |object_key |prio| sort_idx
--------------------------------------
1 |4711 | 10 | 1
2 |7861 | 10 | 1
3 |5463 | 10 | 1
4 |7341 | 10 | 1
5 |9425 | 10 | 1
1 |2831 | 10 | 2
2 |1234 | 10 | 2
3 |5464 | 10 | 2
4 |7342 | 10 | 2
5 |9426 | 10 | 2
但是,我对有效计算每个插入的排序索引不太有信心。可能会出现一次插入 20.000 行(或以快速方式)的情况。
我不是在寻找引入一些变量或函数的解决方案,我也不想引入数据库触发器,只是简单的sql。
编辑 2017 年 9 月 13 日 17:13: 如果在 springs 的 JdbcTemplate 上下文中可以使用用户定义的变量,我想这是一个接受table解决方案。
但是我希望在数据库层上尽可能简单(即不使用 database-specific/exclusive 命令)
编辑 26.09.2017 11:18 因此,我针对包含大约 40.000 条记录的真实数据集测试了建议的解决方案,但我不得不发现它的性能并不理想。事实上,我在大约一分钟后取消了查询执行。
对该查询的解释给出了以下输出:
explain select c1.client_id,
c1.object_key,
c1.prio,
count(*)-1 as sort_idx
from clients as c1
left join clients as c2
on c1.client_id = c2.client_id
and c1.object_key >= c2.object_key
and c1.prio >= c2.prio
group by c1.client_id,
c1.object_key,
c1.prio
order by
c1.prio,
sort_idx
limit 10;
Limit (cost=512.53..512.56 rows=10 width=12)
-> Sort (cost=512.53..513.03 rows=200 width=12)
Sort Key: c1.prio, ((count(*) - 1))
-> HashAggregate (cost=505.71..508.21 rows=200 width=12)
Group Key: c1.client_id, c1.object_key, c1.prio
-> Nested Loop Left Join (cost=0.15..484.80 rows=2091 width=12)
-> Seq Scan on clients c1 (cost=0.00..29.40 rows=1940 width=12)
-> Index Only Scan using clients_idx on clients c2 (cost=0.15..0.22 rows=1 width=12)
Index Cond: ((client_id = c1.client_id) AND (object_key <= c1.object_key) AND (prio <= c1.prio))
我不是解释解释计划的专家,但每当成本数字很高或我看到嵌套循环连接时,我就会感觉到危险。
使用密集排名函数给出的语句工作得更快,不幸的是我需要支持 mysql 和 postgres。
通常(MSSQL、Oracle、PostgreSQL等)这个问题可以借助DENSE_RANK函数来解决。 但是因为 MySQL 没有 window 函数,你可以这样做:
select c1.client_id,
c1.object_key,
c1.prio,
count(*)-1 as sort_idx
from clients as c1
left join clients as c2
on c1.client_id = c2.client_id
and c1.object_key >= c2.object_key
and c1.prio >= c2.prio
group by c1.client_id,
c1.object_key,
c1.prio
order by
c1.prio,
sort_idx
limit 10;
我做了一个fiddle,欢迎大家测试
更新: 还使用 DENSE_RANK 为 PostgreSQL 制定了一个解决方案 - 以防其他人不会受到 MySQL 限制的限制:
select c.client_id,
c.object_key,
c.prio,
DENSE_RANK() OVER
(PARTITION BY c.client_id ORDER BY c.object_key) as sort_idx
from clients as c
order by
c.prio,
sort_idx
limit 10;
还有 fiddle
更新 2:
为 dense_rank
计算制定了 MySQL 特定解决方案,在 40000 条记录上比自连接快得多。但它依赖于顺序,因此您必须以某种方式使用此查询结果(可能使用临时 table)来获取按 prio ASC, dense_rank ASC
.
SELECT
t2.client_id,
t2.object_key,
t2.prio,
t2.dense_rank
FROM
(SELECT
t1.*,
@dense:=IF(@prev_client_id=t1.client_id AND @prev_prio=t1.prio, @dense+1, 1) AS dense_rank,
@prev_client_id:=t1.client_id,
@prev_prio:=t1.prio
FROM (select * from clients c1 order by prio, client_id) t1,
(SELECT @dense:=0, @prev_client_id:=NULL, @prev_prio:=NULL) var1
) t2;
还有 fiddle.