【问题标题】:Conditionally selecting top 1 from groups based on cascading conditions根据级联条件有条件地从组中选择前 1 个
【发布时间】:2014-03-14 10:00:47
【问题描述】:

我需要根据“级联 if 条件”集从每组项目中选择“前 1”记录。

分组基于 MovieId、Formatid 和 Date。

这是我的表格的一个例子(为了突出显示我试图实现的逻辑的 4 个“案例”)...

Id | MovieId | FormatId | SourceId | Date       | Lock | Created    | Modified
------------------------------------------------------------------------------
1  | 1       | 1        | 1        | 2014-03-12 | 1    | 2014-03-12 | NULL
2  | 1       | 1        | 2        | 2014-03-12 | NULL | 2014-03-12 | NULL
3  | 1       | 1        | 3        | 2014-03-12 | NULL | 2014-03-12 | 2014-03-13
4  | 1       | 1        | 4        | 2014-03-12 | NULL | 2014-03-12 | NULL

5  | 1       | 2        | 1        | 2014-03-12 | NULL | 2014-03-12 | NULL
6  | 1       | 2        | 2        | 2014-03-12 | NULL | 2014-03-12 | NULL
7  | 1       | 2        | 3        | 2014-03-12 | NULL | 2014-03-12 | NULL
8  | 1       | 2        | 4        | 2014-03-12 | NULL | 2014-03-12 | NULL

9  | 1       | 3        | 1        | 2014-03-12 | NULL | 2014-03-12 | NULL
10 | 1       | 3        | 3        | 2014-03-12 | NULL | 2014-03-12 | 2014-03-13

11 | 2       | 1        | 2        | 2014-03-12 | NULL | 2014-03-12 | NULL

我的预期结果将是这些行...

1  | 1       | 1        | 1        | 2014-03-12 | 1    | 2014-03-12 | NULL
8  | 1       | 2        | 4        | 2014-03-12 | NULL | 2014-03-12 | NULL
10 | 1       | 3        | 3        | 2014-03-12 | NULL | 2014-03-12 | 2014-03-13
11 | 2       | 1        | 2        | 2014-03-12 | NULL | 2014-03-12 | NULL

这是算法(同样,每个 MovieId/FormatId/Date 组)...

首先,如果有一条 SourceId = 1 AND Lock NOT NULL 的记录,则选择该记录。
其次,如果有 SourceId = 4 的记录,则选择那个。
三、如果有SourceId 2的记录,则选择最近更新的记录。
最后,如果只有 1 条记录 SourceId = 2,则选择那条记录。

另外两个注释/请求... 1) 我已经有一个索引视图来执行其中的一些操作并使用带有 ROW_NUMBER() 的“条件 ORDER BY”子句,即

ORDER BY (CASE WHEN X THEN A END) DESC, (CASE WHEN Y THEN B END)...

但这并不完全正确,而且表现很糟糕! 2) 这个表非常大(目前大约有 600 万行),所以我看到的一些东西推荐适用于 small'ish 表,但这个表不是其中之一。

提前致谢! --亨利

【问题讨论】:

    标签: sql sql-server tsql


    【解决方案1】:

    您的问题只是说:“使用row_number(),使用row_number()!”

    这会为组内的每一行分配一个序号。这些行基于order by 排序。为此,只需将逻辑用于您的优先级:

    select Id, MovieId, FormatId, SourceId, Date, Lock, Created, Modified
    from (select t.*,
                 row_number() over (partition by MovieId, FormatId, Date
                                    order by (case when SourceId = 1 AND Lock NOT NULL then 1
                                                   when SourceId = 4 then 2
                                                   when SourceId <> 2 then 3
                                                   when SourceId = 2 then 4
                                                   else 5
                                              end), Modified desc
                                   ) as seqnum,
                sum(case when SourceId = 2 then 1 else 0 end) over (partition by MovieId, FormatId, Date) as NumSourceId2
          from table t
         ) t
    where seqnum = 1 and not (SourceId = 2 and NumSourceId2 > 1);
    

    请注意,当不满足任何条件时,这仍然会选择一行。您没有指定在这种情况下要做什么。

    【讨论】:

    • Gordon Linoff,感谢您的快速回复,不幸的是我收到了 "Column 'table.Id' is invalid in the select list because it is not contained in either an aggregate function or the GROUP BY clause." 错误。但是没有“GROUP BY”,所以我不确定它在抱怨什么?!?
    • @HenryC 。 . . sum() 应该是窗口函数,而不是聚合函数。
    • 戈登谢谢我认为这可能是问题所在。现在非常接近了!唯一似乎不起作用的情况是最后修改优先的第三种情况。因此,在我的示例数据中,我希望选择第 10 行而不是第 9 行。我尝试使用 ISNULL(Modified, Created) DESC 而不仅仅是 Modified DESC,但这似乎没有任何作用。
    • 实际上,我的立场是正确的。我根据我的一些真实数据运行它并得到了正确的结果,所以我只是根据我提供的样本数据运行它,它按照我的要求给了我第 1、8、10 和 11 行。让我再看几个案例……
    • 这确实有效,但是对于我们的表结构和记录数量来说太慢了,无法满足客户的需求,所以我最终将这个逻辑拉到了 C# 代码中,它在小得多的记录子集,并且比尝试在 SQL 中执行此操作要快得多。同样,这确实有效,只是最终不适合我们的场景。谢谢@戈登
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2022-12-20
    • 2021-12-21
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多