TSQL Window 函数最佳实践

TSQL Window Function Best Practices

我在日常查询中越来越多地使用 window 函数,并且一直在想我是否正确地使用了它。

假设我们有一个数据库 dbo.songs,其中每首歌曲包含一条记录,包含以下列:artistsongNamereleaseDate
对于每个艺术家,我想 select 他们的第一个 songNamereleaseDate,按 releaseDate 升序排列。请注意,根据 artist 进行分组的决定是任意的 - 明天,我可能需要按不同的列(BPM、专辑、长度)进行分组。

为此,我们有几种选择:

最近,我一直在使用 "bunch of identically scoped window functions" 策略,它看起来像这样:

SELECT DISTINCT
    s.artist
    , FIRST_VALUE(s.songName) OVER (PARTITION BY s.artist ORDER BY s.releaseDate ASC) AS songName
    , FIRST_VALUE(s.releaseDate) OVER (PARTITION BY s.artist ORDER BY s.releaseDate ASC) AS releaseDate
FROM dbo.songs s

这似乎有点草率,不是吗?它完全依赖 DISTINCT 来避免一百万个重复行,如果你想 select 额外的字段(BPM、专辑、长度)你需要更多的 window 函数,我相信会算作 RBAR。

选项二是 "figure out the keys and then join to self",它看起来像这样:

WITH earliestArtistRelease AS (
    SELECT
        s.artist
        , MIN(s.releaseDate) AS releaseDate
    FROM dbo.songs s
    GROUP BY s.artist
)

SELECT
    e.artist
    , e.releaseDate
    , s.songName
FROM dbo.songs s
INNER JOIN earliestArtistRelease e
ON s.releaseDate = e.releaseDate
    AND s.artist = e.artist

这样就完成了工作,但似乎效率不高 - 特别是如果我们在 releaseDateartist 上没有索引。我们还 运行 解决了艺术家一天发行两首歌的问题。 此外,如果我们正在做一些时髦的优先级排序(select 如果可能的话,歌曲在 2018-01-01 发布,否则是最早发布的歌曲),我们几乎无法像使用 window functions: OVER (PARTITION BY s.artist ORDER BY IIF(s.releaseDate = '20180101', '19000101', s,releaseDate)),这有点笨拙,但简洁。

我们还有其他选择:self-CROSS APPLY,使用 ROW_NUMBER(),但据我所知,与 "bunch of identically scoped window functions" 策略相比,这些方法要么效率较低,要么不够简洁以上概述。

那么,我的问题是:最佳做法是什么?您将如何处理这个问题,既可以节省处理器周期,又可以避免代码库的长度加倍?一个选项在 CTE 中更好,另一个选项更适合插入临时 table?

非常感谢任何指向现有标准、论文或资源的链接。

您可以使用subquery分析函数:

select s.*
from dbo.songs s
where releaseDate = (select min(s1.releaseDate)
                     from dbo.songs s1
                     where s.artist = s1.artist
                    );

1) 你应该先找到你独特的艺术家。如果您已经有艺术家 table,那么 select。如果你不这样做,那么创建一个艺术家 table 并让歌曲 table 与外键相关联。

2) 完成后,CROSS APPLY 将是检索相关歌曲数据的正确操作符。

SELECT a.artist, t.songName, t.releaseDate
FROM artists a
CROSS APPLY (
    SELECT TOP 1 s.songName, s.releaseDate
    FROM songs s
    WHERE s.artistId = a.artistId
    -- any other "funky" prioritization.
    ORDER BY s.releaseDate ASC
) topSongs t