SQL 在某些条件下分组

SQL group by under some conditions

我有一个很大的 table,其中有大量重复的行(在我关心的那些列中)。让我从以下示例开始:

|field1 | field2| field3| field4| field5|
| aa    | 1     | NULL  | 1     | 0     | 
| aaa   | 1     | NULL  | 1     | 1     | 
| aaa   | 1     | NULL  | 1     | 2     | 
| a     | 2     | 0     | 1     | 3     | 
| a     | 2     | 0     | NULL  | 4     |  
| a     | 2     | NULL  | 2     | 5     |  
| b     | 3     | NULL  | 2     | 6     |  
| b2    | 3     | NULL  | NULL  | 7     |  
| c     | 4     | NULL  | NULL  | 8     |  

我对获取以下内容的高效查询感兴趣 table:

|field1 | field2| field3| field4|
| aaa   | 1     | NULL  | 1     | 
| a     | 2     | 0     | 1     | 
| b     | 3     | NULL  | 2     | 
| c     | 4     | NULL  | NULL  | 

基本上遵循以下规则:

  1. 对于 field2 的每个值,应该只有一行存在
  2. 在所有field2值相同的行中select依次满足以下条件的行:
    • select field4 不为 Null 的行(如果可能)
    • 在字段 4 具有非空值的行中 select 字段 3 具有非空值的行
    • 在字段 4 和 3 具有非 Null 值的行中,select 字段 1 具有最长字符串值的行
    • 满足以上条件的,select只有一行(与field5的值无关)

我可以用一堆连接来完成,但它变得非常慢。有更好的建议吗?

编辑 field2 值可能没有特定顺序。我只是把 1,2,3,4 放在例子中,但在我的例子中这通常不是真的。我没有直接在 table 上更改它,因为其中一个建议的解决方案实际上正在考虑 field2 的顺序值,所以我保留了 if 以供将来可能对此感兴趣的读者使用。

这种类型的优先排序具有挑战性。我认为 MySQL 中最简单的方法使用变量:

select t.*
from (select t.*,
             (@rn := if(@f2 = field2, @rn + 1,
                        if(@f2 := field2, 1, 1)
                       )
             ) as seqnum
      from t cross join
           (select @rn := 0, @field2 := '') params
      order by field2,
               (field4 is not null) desc,
               (field3 is not null) desc,
               length(field1) desc
     ) t
where seqnum = 1;

我不是 100% 确定我的条件是否正确(第三个似乎与前两个冲突)。但无论优先级如何,想法都是相同的:使用 order by 以正确的顺序获取行并使用变量获取第一个。

编辑:

在 SQL 服务器——或任何其他合理的数据库——你用 row_number():

select t.*
from (select t.*,
             row_number() over (partition by field2
                                order by (case when field4 is not null then 0 else 1 end),
                                         (case when field3 is not null then 0 else 1 end),
                                         len(field1)
                               ) as seqnum
      from t 
     ) t
where seqnum = 1;