将连续密钥组作为SQL Server中的范围

问题描述 投票:0回答:3

在SQL Server 2012中,我的(简化)表如下所示:

Key  SubKey Quantity
--------------------
96614   1   0.604800
96615   1   1.920000
96615   2   3.840000
96616   1   1.407600
96617   1   0.453600
96617   2   3.568320
96617   3   2.710260
96618   1   11.520000
96619   1   0.453600
96620   1   7.919100
96620   2   4.082400
96626   1   14.394000
96627   1   9.525600
96627   2   4.762800
96627   3   4.536000
96628   1   2.268000

我的查询需要识别连续的密钥(SubKeys基本上不相关)并将它们分组到范围中,适当地对数量求和。所以上面的预期输出是:

KeyRange    TotalQuantity
-------------------------
96614-96620 38.47968
96626-96628 35.48640

我曾尝试使用一些使用窗口函数的示例,但我认为因为它们适用于不同的目的,所以对我来说并没有多大意义。这是正确的方法吗?

sql-server-2012
3个回答
1
投票

我认为你不能直接使用内置插件,尽管它们是我解决方案的一部分。下面的代码基本上检测范围的开始和结束(表中没有条目,键值分别小于或大于1),并使用这些条目对连接到它的数据进行分组。

WITH RangeStarts AS (
  SELECT 
    ROW_NUMBER () OVER (ORDER BY [Key] ASC) RangeId, 
    [Key] RangeStart
  FROM (SELECT DISTINCT [Key] FROM ConsKeyAsTable t) t
  WHERE NOT Exists (
    SELECT * FROM ConsKeyAsTable t2 WHERE t2.[Key] = t.[Key] - 1
  )
)
,RangeEnds AS (
  SELECT
    ROW_NUMBER () OVER (ORDER BY [Key] ASC) RangeId, 
    [Key] RangeEnd
  FROM (SELECT DISTINCT [Key] FROM ConsKeyAsTable t) t
  WHERE NOT Exists (
    SELECT * FROM ConsKeyAsTable t2 WHERE t2.[Key] = t.[Key] + 1
  )
)
SELECT 
  Cast(s.RangeStart as varchar(10)) + '-' + Cast(e.RangeEnd as varchar(10)) as KeyRange,
  SUM(t.Quantity) as Quantity
FROM RangeStarts s
  INNER JOIN RangeEnds e ON s.RangeId = e.RangeId
  INNER JOIN ConsKeyAsTable t ON t.[Key] BETWEEN s.RangeStart AND e.RangeEnd
GROUP BY
  s.RangeStart,
  e.RangeEnd

Sql小提琴http://sqlfiddle.com/#!18/080fa/31

设置代码

CREATE TABLE ConsKeyAsTable ([Key] int NOT NULL, [SubKey] int NOT NULL, Quantity float, Constraint PK PRIMARY KEY CLUSTERED ([Key], [SubKey]))

INSERT ConsKeyAsTable VALUES 
(96614,   1,   0.604800),
(96615,   1,   1.920000),
(96615,   2,   3.840000),
(96616,   1,   1.407600),
(96617,   1,   0.453600),
(96617,   2,   3.568320),
(96617,   3,   2.710260),
(96618,   1,   11.520000),
(96619,   1,   0.453600),
(96620,   1,   7.919100),
(96620,   2,   4.082400),
(96626,   1,   14.394000),
(96627,   1,   9.525600),
(96627,   2,   4.762800),
(96627,   3,   4.536000),
(96628,   1,   2.268000)

1
投票

使用window functions和序列号tallies with recursive CTE's的组合,以下应该可以工作(并且还将处理样本中的单数范围;请参阅下面的设置SQL语句):

DECLARE @start INT = (SELECT MIN(pKey) FROM @t);
DECLARE @end INT = (SELECT MAX(pKey) FROM @t);

WITH cte_RangeTally AS (
    SELECT @start num
    UNION ALL
    SELECT num + 1 FROM cte_RangeTally WHERE num+1 <= @end),
cte_Group AS (
    SELECT 
        T.pKey,
        ROW_NUMBER() OVER (ORDER BY RT.num) - ROW_NUMBER() OVER (ORDER BY T.pKey) grp
    FROM
        cte_RangeTally RT
    LEFT JOIN 
        (SELECT pKey 
        FROM @t 
        GROUP BY pKey) T ON RT.num = T.pKey),
cte_NumRanges AS (
    SELECT
        pKey,
        FIRST_VALUE(pKey) OVER(PARTITION BY grp 
                               ORDER BY pKey
                               ROWS BETWEEN UNBOUNDED PRECEDING 
                               AND CURRENT ROW) AS FirstNum,
        LAST_VALUE(pKey) OVER(PARTITION BY grp 
                               ORDER BY pKey
                               ROWS BETWEEN UNBOUNDED PRECEDING 
                               AND UNBOUNDED FOLLOWING) AS LastNum
    FROM
        cte_Group
    WHERE 
        cte_Group.pKey IS NOT NULL)
SELECT 
    CAST(NR.FirstNum AS VARCHAR(10)) + ' - ' + CAST(NR.LastNum AS VARCHAR(10)),
    SUM(T1.Quantity) AS TotalQty
FROM
    cte_NumRanges NR
RIGHT JOIN
    @t T1 ON T1.pKey = NR.pKey
GROUP BY 
    NR.FirstNum, 
    NR.LastNum;

假设以下设置代码:

DECLARE @t TABLE (pKey INT, SubKey INT, Quantity FLOAT);

INSERT @t VALUES 
(96614,   1,   0.604800),
(96615,   1,   1.920000),
(96615,   2,   3.840000),
(96616,   1,   1.407600),
(96617,   1,   0.453600),
(96617,   2,   3.568320),
(96617,   3,   2.710260),
(96618,   1,   11.520000),
(96619,   1,   0.453600),
(96620,   1,   7.919100),
(96620,   2,   4.082400),
(96626,   1,   14.394000),
(96627,   1,   9.525600),
(96627,   2,   4.762800),
(96627,   3,   4.536000),
(96628,   1,   2.268000),
(96630,   1,   2.165000),
(96632,   1,   2.800000),
(96633,   1,   2.900000);

0
投票

(编辑:正如@scrawny所指出的,这个解决方案目前不支持单数范围。)

我独立于@MonkeyPushButton发布的答案的想法没有成功 - 我试图使用LAG和LEAD以及其他一些技术,但无法使其运行。然而,在这个过程中,我有另一个想法,我在这里发布。我不相信它比猴子“更好”,但认为其他人可能会感兴趣。 (我完全抄袭了他的设置代码,我希望没问题。)

SQL小提琴http://sqlfiddle.com/#!18/8e86a/3

CREATE TABLE MyTable ([Key] int NOT NULL, [SubKey] int NOT NULL, Quantity float, Constraint PK PRIMARY KEY CLUSTERED ([Key], [SubKey]))

INSERT MyTable VALUES 
(96614,   1,   0.604800),
(96615,   1,   1.920000),
(96615,   2,   3.840000),
(96616,   1,   1.407600),
(96617,   1,   0.453600),
(96617,   2,   3.568320),
(96617,   3,   2.710260),
(96618,   1,   11.520000),
(96619,   1,   0.453600),
(96620,   1,   7.919100),
(96620,   2,   4.082400),
(96626,   1,   14.394000),
(96627,   1,   9.525600),
(96627,   2,   4.762800),
(96627,   3,   4.536000),
(96628,   1,   2.268000)

表的四次调用用于创建一组键范围。 t1和t4创建StartKey,t2和t3创建EndKey。

WITH cte_KeyRange AS
     (
            SELECT [Key] AS StartKey,
                    (
                          SELECT MIN([Key])
                          FROM MyTable t2
                          WHERE t2.[Key] > t1.[Key]
                                 AND NOT EXISTS
                                 (
                                        SELECT [Key]
                                        FROM MyTable t3
                                        WHERE t3.[Key] = t2.[Key] + 1
                                 )
                   ) AS EndKey
            FROM MyTable t1
            WHERE NOT EXISTS
                   (
                          SELECT [Key]
                          FROM MyTable t4
                          WHERE t4.[Key] = t1.[Key] - 1
                   )
     )
SELECT CAST(StartKey AS varchar(10)) + '-' + CAST(EndKey AS varchar(10)) AS KeyRange, SUM(Quantity) AS TotalQuantity
FROM cte_KeyRange INNER JOIN MyTable ON [Key] BETWEEN StartKey AND EndKey
GROUP BY StartKey, EndKey
© www.soinside.com 2019 - 2024. All rights reserved.