在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
我曾尝试使用一些使用窗口函数的示例,但我认为因为它们适用于不同的目的,所以对我来说并没有多大意义。这是正确的方法吗?
我认为你不能直接使用内置插件,尽管它们是我解决方案的一部分。下面的代码基本上检测范围的开始和结束(表中没有条目,键值分别小于或大于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)
使用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);
(编辑:正如@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