【问题标题】:TSQL Window Function Best PracticesTSQL 窗口函数最佳实践
【发布时间】:2019-01-20 11:41:55
【问题描述】:

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

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

为此,我们有几个选择:

最近,我一直在使用“一堆相同范围的窗口函数”策略,看起来像这样:

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 来避免一百万行重复,如果你想选择额外的字段(BPM、专辑、长度),你需要更多的窗口函数,我相信这会算作 RBAR。

选项二是“找出键然后加入自我”,如下所示:

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 上的索引。如果一位艺术家在一天内发布了两首歌曲,我们也会遇到问题。 此外,如果我们正在做一些时髦的优先级排序(如果可能,选择 2018 年 1 月 1 日发布的歌曲,否则选择最早发布的歌曲),我们不能像使用窗口函数那样简单:@987654334 @,有点老套,但简洁。

我们还有其他选择:self-CROSS APPLY,使用ROW_NUMBER(),但据我所知,这些往往比上面概述的“一堆相同范围的窗口函数”策略效率低或简洁。

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

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

【问题讨论】:

  • 在此示例中您没有要加入的 artist 表?这将消除对不同的需求。如果没有,请创建一个。
  • @DanielGimenez 选择按artist 分组是任意的-如果我有一个名为songLength 的列并想按此分组,那么拥有一个包含每个可能的歌曲长度。我将编辑我的问题以澄清分组所在的列可能不一定是一个好键。

标签: sql-server tsql window-functions


【解决方案1】:

1) 你应该首先找到你不同的艺术家。如果您已经有一个艺术家表,请从中选择。如果您不这样做,则创建一个艺术家表,并使用外键让歌曲表与该表相关。

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

【讨论】:

  • 做了一些研究——我认为CROSS APPLY 效率低下的假设并不一定准确。非常好的选择,谢谢。
【解决方案2】:

您可以使用subquery分析函数:

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

【讨论】:

  • 考虑到它必须对每一行执行查询,这不会在一张大表上花费很多吗?我的理解正确吗?
  • @Cowthulhu 根据我的经验,是的,这非常低效。
猜你喜欢
  • 2010-09-23
  • 1970-01-01
  • 1970-01-01
  • 2018-06-16
  • 2016-10-20
  • 1970-01-01
  • 2019-04-08
  • 2012-03-27
  • 1970-01-01
相关资源
最近更新 更多