【问题标题】:Looking for a more performant way to retrieve the top two rows of a group in SQL寻找一种更高效的方法来检索 SQL 中组的前两行
【发布时间】:2019-07-27 03:21:30
【问题描述】:

我正在寻找一种高效的方法来检索 SQL 中每组数据的前两行。我有一个非常大的数据表(大约 100 亿行)。每行数据由四个维度(构成主键)描述,表由其中一个维度(主键的最后一列)进行分区。

-- Medium table (2 to 3 million rows)
CREATE TABLE [smallDatabase].[dbo].[dimTableA] (
    [colA] [int] NOT NULL PRIMARY KEY
    ,[valueA] [int]
);

-- Small table (<1000 rows)
CREATE TABLE [smallDatabase].[dbo].[dimTableB] (
    [colB] [int] NOT NULL PRIMARY KEY
    ,[valueB] [int]
);

-- Small table (<10000 rows)
CREATE TABLE [smallDatabase].[dbo].[dimTableC] (
    [colC] [int] NOT NULL PRIMARY KEY
    ,[valueC] [int]
);

-- Small table (100 to 200 rows)
CREATE TABLE [smallDatabase].[dbo].[dimTableD] (
    [colD] [int] NOT NULL PRIMARY KEY
    ,[grouperD] [int] NOT NULL
    ,[dateD] [date]
);

CREATE PARTITION FUNCTION [pfColD](int) AS RANGE RIGHT FOR VALUES (1, 2, 3, ..., n);
CREATE PARTITION SCHEME [psColD] AS PARTITION [pfColD] TO ([PRIMARY], [PRIMARY], [PRIMARY], ..., [PRIMARY]);

-- Large table (~10 billion rows)
CREATE TABLE [bigDatabase].[dbo].[factBigTable] (
    [colA] [int] NOT NULL
    ,[colB] [int] NOT NULL
    ,[colC] [int] NOT NULL
    ,[colD] [int] NOT NULL
    ,[datum] [float] NULL
    ,PRIMARY KEY (
        [colA] ASC
        ,[colB] ASC
        ,[colC] ASC
        ,[colD] ASC
    )
) ON psColD([colD]);

另一个要求是我只需要随时为数据子集执行此操作。为了表示需要定位的数据,我们使用临时表进行过滤。

CREATE TABLE #filter (
    [colA] [int] NOT NULL
    ,[colB] [int] NOT NULL
    ,PRIMARY KEY (
        [colA] ASC
        ,[colB] ASC
    )
);

我在网上找到了一些其他解决方案,建议使用行号选择前 2 位,如下所示:

-- Get the most recent two data points for each group of data
SELECT *
FROM (
    SELECT big.*
        ,dimD.[grouperD]
        ,ROW_NUMBER() OVER (
            PARTITION BY dimD.[grouperD], big.[colA], big.[colB], big.[colC]
            ORDER BY dimD.[dateD] DESC
        ) AS rowNumber
    FROM [bigDatabase].[dbo].[factBigTable] AS big
    INNER JOIN [smallDatabase].[dbo].[dimTableD] AS dimD
        ON big.[colD] = dimD.[colD]
    INNER JOIN #filter
        ON big.[colA] = #filter.[colA]
            AND big.[colB] = #filter.[colB]
) AS bigDataRanked
WHERE rowNumber <= 2;

这确实让我得到了我正在寻找的确切数据;但是,它非常慢!

此时我已经尝试了许多不同的解决方案,但所有解决方案的执行速度都比我想要的要慢。值得注意的是,由于数据的性质,并不是每个维度组合都有数据。有些组合非常稀疏。

我尝试过的一种算法在纸面上看起来很棒,但由于数据的稀疏性,最终表现非常缓慢。这个想法是:

  1. 缓存每个组的列表。即,[grouperD]、[colA]、[colB] 和 [colC]。跟踪为每个组找到的行。
  2. [colD] 上的光标,按 [dateD] 排序。当每组找到 2 行时停止。
  3. 从 [factBigTable] 中选择与找到的行数小于 2 的组匹配的行。缓存结果。
  4. 对于缓存的结果,增加找到的行数。
  5. 将缓存的结果移动到临时表以供以后使用。
  6. 继续循环中的下一个 [colD]。

每个循环都执行得相对较快,因为 SQL 能够在大多数查询上使用 PK 搜索。但是,我的一些组的最大 [colD] 非常低,因此循环必须迭代多次。

到目前为止,我发现的最快的解决方案在纸面上看起来很糟糕,但最终表现最好。然而;它仍然比我想要的慢,而且扩展性很差。

  1. 对于我们关心的数据子集(即加入过滤器),缓存每个组的所有主键。
  2. 选择并缓存每个组的最大 [colD]。
  3. 从 PK by group 列表中删除最大值。
  4. 再次选择并缓存每个组的最大 [colD]。获得第二个到最大值 [colD]。
  5. 使用 max 和 second to max 缓存键来查找我们需要的所有行。

是否有人对如何快速检索我正在寻找的行有任何其他想法?这绝不需要在单个查询中完成。我可以根据需要使用尽可能多的临时表或临时表来快速获取数据。此外,我愿意添加索引或其他数据模型更改。我不希望 - 仅仅因为表太大,任何更改都可能意味着重要的存储考虑 - 但是,如果这是唯一的方法,那么我会让它工作。

【问题讨论】:

  • 您是否对有效的查询进行了解释?可能只需要一个索引。
  • 使用游标绝对不是一个好的选择。循环非常低效,因为它基本上必须运行 100 亿次查询才能查看每一行。我和@Hogan 在一起,索引几乎可以肯定是这里的最佳选择。
  • 您可能会通过搜索“top n in group”获得一些好主意 Itzik Ben Gan 展示了各种解决方案 here 以及其他 similar questions 以及其他问题的详细答案dba.stackexchange.com。我更倾向于在 dba.stackexchange.com 上询问如何对具有这种大小和结构的表进行分区,以允许针对它编写有效和高效的查询。

标签: sql sql-server tsql database-performance


【解决方案1】:

不能评论,不够大,但很好奇,我们说的是什么版本的sql server?

我怀疑它会更快(特别是如果我们在 rowNumber

;with bigDataRanked as (
SELECT big.*
        ,dimD.[grouperD]
        ,dimD.[dateD]
    FROM [bigDatabase].[dbo].[factBigTable] AS big
    INNER JOIN [smallDatabase].[dbo].[dimTableD] AS dimD
        ON big.[colD] = dimD.[colD]
    INNER JOIN #filter
        ON big.[colA] = #filter.[colA]
            AND big.[colB] = #filter.[colB]
) 
select bdr.*, ROW_NUMBER() OVER (
            PARTITION BY bdr.[grouperD], bdr.[colA], bdr.[colB], bdr.[colC]
            ORDER BY bdr.[dateD] DESC) rowNumber
from bigDataRanked bdr -- will have an additional column dateD returned from dimTableD (from bdr.*)
where rowNumber <= 2;

【讨论】:

    猜你喜欢
    • 2023-01-19
    • 1970-01-01
    • 2017-01-12
    • 2013-02-15
    • 1970-01-01
    • 2019-01-06
    • 1970-01-01
    • 2011-08-04
    • 1970-01-01
    相关资源
    最近更新 更多