SQL - 在列上排序数据而不将其包含在排名中
Posted
技术标签:
【中文标题】SQL - 在列上排序数据而不将其包含在排名中【英文标题】:SQL - Order Data on a Column without including it in ranking 【发布时间】:2021-07-02 07:21:15 【问题描述】:所以我有一个场景,我需要对列上的数据进行排序,而不将其包含在dense_rank() 中。这是我的示例数据集:
这是桌子:
create table temp
(
id integer,
prod_name varchar(max),
source_system integer,
source_date date,
col1 integer,
col2 integer);
这是数据集:
insert into temp
(id,prod_name,source_system,source_date,col1,col2)
values
(1,'ABC',123,'01/01/2021',50,60),
(2,'ABC',123,'01/15/2021',50,60),
(3,'ABC',123,'01/30/2021',40,60),
(4,'ABC',123,'01/30/2021',40,70),
(5,'XYZ',456,'01/10/2021',80,30),
(6,'XYZ',456,'01/12/2021',75,30),
(7,'XYZ',456,'01/20/2021',75,30),
(8,'XYZ',456,'01/20/2021',99,30);
现在,我想对数据执行 dense_rank(),这样对于“prod_name 和 source_system”的组合,只有当 col1 或 col2 发生变化但数据仍然应该是时,排名才会增加按 source_date 升序排列。 这是预期的结果:
id | prod_name | source_system | source_date | col1 | col2 | Dense_Rank |
---|---|---|---|---|---|---|
1 | ABC | 123 | 01-01-21 | 50 | 60 | 1 |
2 | ABC | 123 | 15-01-21 | 50 | 60 | 1 |
3 | ABC | 123 | 30-01-21 | 40 | 60 | 2 |
4 | ABC | 123 | 30-01-21 | 40 | 70 | 3 |
5 | XYZ | 456 | 10-01-21 | 80 | 30 | 1 |
6 | XYZ | 456 | 12-01-21 | 75 | 30 | 2 |
7 | XYZ | 456 | 20-01-21 | 75 | 30 | 2 |
8 | XYZ | 456 | 20-01-21 | 99 | 30 | 3 |
正如您在上面看到的,日期正在发生变化,但预期只有当 col1 或 col2 发生任何变化时,排名才会发生变化。
如果我使用这个查询
select id,prod_name,source_system,source_date,col1,col2,
dense_rank() over(partition by prod_name,source_system order by source_date,col1,col2) as rnk
from temp;
那么结果会是:
id | prod_name | source_system | source_date | col1 | col2 | rnk |
---|---|---|---|---|---|---|
1 | ABC | 123 | 01-01-21 | 50 | 60 | 1 |
2 | ABC | 123 | 15-01-21 | 50 | 60 | 2 |
3 | ABC | 123 | 30-01-21 | 40 | 60 | 3 |
4 | ABC | 123 | 30-01-21 | 40 | 70 | 4 |
5 | XYZ | 456 | 10-01-21 | 80 | 30 | 1 |
6 | XYZ | 456 | 12-01-21 | 75 | 30 | 2 |
7 | XYZ | 456 | 20-01-21 | 75 | 30 | 3 |
8 | XYZ | 456 | 20-01-21 | 99 | 30 | 4 |
而且,如果我将 source_date 从排序函数中排除,即
select id,prod_name,source_system,source_date,col1,col2,
dense_rank() over(partition by prod_name,source_system order by col1,col2) as rnk
from temp;
那么我的结果是:
id | prod_name | source_system | source_date | col1 | col2 | rnk |
---|---|---|---|---|---|---|
3 | ABC | 123 | 30-01-21 | 40 | 60 | 1 |
4 | ABC | 123 | 30-01-21 | 40 | 70 | 2 |
1 | ABC | 123 | 01-01-21 | 50 | 60 | 3 |
2 | ABC | 123 | 15-01-21 | 50 | 60 | 3 |
6 | XYZ | 456 | 12-01-21 | 75 | 30 | 1 |
7 | XYZ | 456 | 20-01-21 | 75 | 30 | 1 |
5 | XYZ | 456 | 10-01-21 | 80 | 30 | 2 |
8 | XYZ | 456 | 20-01-21 | 99 | 30 | 3 |
两个结果都不正确。我怎样才能得到预期的结果?任何指导都会有所帮助。
【问题讨论】:
我已经尝试过查询:“select id,prod_name,source_system,date,col1,col2,dense_rank() over(partition by prod_name,source_system order by col1,col2) as rnk from temp order按日期;"这也不起作用,因为 dense_rank 优先于数据排序。 不可读。将数据集显示为即用型代码格式的 CREATE TABLE + INSERT INTO 脚本。将您的查询格式化为代码。将所有输出格式化为表格。 mysql 或 RedShift,选择其中之一。 @Akina - 我希望它现在可读。任何指导都会有所帮助。 @RohanKapoor 。 . . PL/SQL 似乎与这个问题无关。该代码与 Oracle 不兼容。而甲骨文与 Redshift 关系不大。 【参考方案1】:WITH cte AS (
SELECT *,
LAG(col1) OVER (PARTITION BY prod_name, source_system ORDER BY source_date, id) lag1,
LAG(col2) OVER (PARTITION BY prod_name, source_system ORDER BY source_date, id) lag2
FROM temp
)
SELECT *,
SUM(CASE WHEN (col1, col2) = (lag1, lag2)
THEN 0
ELSE 1
END) OVER (PARTITION BY prod_name, source_system ORDER BY source_date, id) AS `Dense_Rank`
FROM cte
ORDER BY id;
https://dbfiddle.uk/?rdbms=mysql_8.0&fiddle=ac70104c7c5dfb49c75a8635c25716e6
【讨论】:
感谢您的快速回复。我正在使用红移数据库。此外,这只是我创建的一个示例数据集。实际上,我需要在 25 列上决定是否应该增加排名。数据量也约为 9000 万。我也在考虑这些因素。那么对所有 25 列进行滞后然后比较它们是一个好主意吗?不使用CTE还有其他转机吗? @RohanKapoor 实际上我有 25 列我需要决定是否应该增加排名。数据量也约为 9000 万。我也将这些因素考虑在内。 此任务是否经常执行且对性能至关重要?如果是这样,那么考虑一些虚拟生成的列,它允许检测差异并在索引中使用它(或者如果 DBMS 允许,则通过表达式进行索引)。 这是我在此论坛上发布的第一个问题。为标记 my-sql 道歉。在您在第一条评论中指出它后,我将其删除。 此任务是否经常执行且对性能至关重要? - 是的,这经常被执行,问题是我在 Redshift 中经常遇到这个大数据集的磁盘满错误。 @RohanKapoor 我不使用 RedShift,只知道大概。快速文档搜索不显示生成的列或显式索引是可能的。所以我帮不了你,对不起。【参考方案2】:在比较多个列时,我喜欢查看排序列的先前值,而不是单个列。这使得添加越来越多的列变得更加简单。
基本思想是对每个产品/源系统的更改进行累积总和。在 Redshift 中,我将其表述为:
select t.*,
sum(case when prev_date = prev_date_2 then 0 else 1 end) over (
partition by prod_name, source_system
order by source_date
rows between unbounded preceding and current row
)
from (select t.*,
lag(source_date) over (partition by prod_name, source_system order by source_date, id) as prev_date,
lag(source_date) over (partition by prod_name, source_system, col1, col2 order by source_date, id) as prev_date_2
from temp t
) t
order by id;
我认为我有适合 Redshift 的语法。 Here 是一个使用 Postgres 的 dbfiddle。
请注意,日期的关系可能会导致问题 - 无论解决方案如何。这使用id
来打破僵局。也许id
可以在一般情况下使用,但是您的代码使用的是日期,所以它使用带有id 的日期。
【讨论】:
砰的一声@Gordon Linoff .. 感谢这个优化的解决方案。这给出了预期的结果。我刚刚按照您的建议使用了“id”。 @RohanKapoor 。 . .如果这回答了您的问题,您可以接受它作为答案。以上是关于SQL - 在列上排序数据而不将其包含在排名中的主要内容,如果未能解决你的问题,请参考以下文章
使用 SQL Server Rank 函数对行进行排名而不跳过排名号