TSQL Window 函数最佳实践
TSQL Window Function Best Practices
我在日常查询中越来越多地使用 window 函数,并且一直在想我是否正确地使用了它。
假设我们有一个数据库 dbo.songs
,其中每首歌曲包含一条记录,包含以下列:artist
、songName
和 releaseDate
。
对于每个艺术家,我想 select 他们的第一个 songName
和 releaseDate
,按 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
这样就完成了工作,但似乎效率不高 - 特别是如果我们在 releaseDate
和 artist
上没有索引。我们还 运行 解决了艺术家一天发行两首歌的问题。
此外,如果我们正在做一些时髦的优先级排序(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
我在日常查询中越来越多地使用 window 函数,并且一直在想我是否正确地使用了它。
假设我们有一个数据库 dbo.songs
,其中每首歌曲包含一条记录,包含以下列:artist
、songName
和 releaseDate
。
对于每个艺术家,我想 select 他们的第一个 songName
和 releaseDate
,按 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
这样就完成了工作,但似乎效率不高 - 特别是如果我们在 releaseDate
和 artist
上没有索引。我们还 运行 解决了艺术家一天发行两首歌的问题。
此外,如果我们正在做一些时髦的优先级排序(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