我在SO上看到过关于如何按SQL查询中的范围对数据进行分组的lot of questions。
确切的情景会有所不同,但每个方面的一般基本问题是按一系列值而不是GROUP BY
列中的每个离散值进行分组。换句话说,要按照比存储在数据库表中更精确的粒度进行分组。
在生成直方图,日历表示,数据透视表和其他定制报告输出等内容时,往往会在现实世界中出现。
一些示例数据(表格无关):
| OrderHistory | | Staff |
--------------------------- ------------------------
| Date | Quantity | | Age | Name |
--------------------------- ------------------------
|01-Jul-2012 | 2 | | 19 | Barry |
|02-Jul-2012 | 5 | | 53 | Nigel |
|08-Jul-2012 | 1 | | 29 | Donna |
|10-Jul-2012 | 3 | | 26 | James |
|14-Jul-2012 | 4 | | 44 | Helen |
|17-Jul-2012 | 2 | | 49 | Wendy |
|28-Jul-2012 | 6 | | 62 | Terry |
--------------------------- ------------------------
现在假设我们要使用Date
表的OrderHistory
列按周分组,即7天范围。或者将Staff
分为10年龄段:
| Week | QtyCount | | AgeGroup | NameCount |
-------------------------------- -------------------------
|01-Jul to 07-Jul | 7 | | 10-19 | 1 |
|08-Jul to 14-Jul | 8 | | 20-29 | 2 |
|15-Jul to 21-Jul | 2 | | 30-39 | 0 |
|22-Jul to 28-Jul | 6 | | 40-49 | 2 |
-------------------------------- | 50-59 | 1 |
| 60-69 | 1 |
-------------------------
GROUP BY Date
和GROUP BY Age
本身不会这样做。
我看到的最常见的答案(其中没有一个一直被评为“正确”)是使用以下一个或多个:
CASE
语句,每个分组一个UNION
个查询,每个分组都有不同的WHERE
个句子PIVOT()
and UNPIVOT()
是否存在用于处理此类查询的已建立的通用模式?
答案 0 :(得分:3)
您可以使用一些维度建模技术,例如fact tables和dimension tables。订单历史记录可以充当事件表,其中DateKey与Date维度的外键关系。 日期维度可以具有如下的模式:
请注意,日期表预先填充了最多N年的数据。
使用上面的示例,这是一个获取结果的示例查询:
select CalendarWeek, sum(Quantity)
from OrderHistory a
join DimDate b
on a.DateKey = b.DateKey
group by CalendarWeek
对于员工表,您可以存储生日密钥而不是年龄,并让查询计算年龄和范围。
日期维度填充脚本取自here。
答案 1 :(得分:2)
通常情况下,此SQL问题需要在组合中使用多个模式。
在这种情况下,你可以使用的是
您可以使用NTITLE创建一定数量的群组。但是,由于您没有代表组的每个成员,因此您还需要使用数字表因为您使用的是SQL Server,所以您可以轻松实现,因为您无需模拟。< / p>
以下是员工问题的示例
WITH g as (
SELECT
NTILE(6) OVER (ORDER BY number) grp,
NUMBER
FROM
master..spt_values
WHERE
TYPE = 'P'
and number >=10 and number <=69
)
SELECT
CAST(min(g.number) as varchar) + ' - ' +
CAST(max(g.number) as varchar) AgeGroup ,
COUNT(s.age) NameCount
FROM
g
LEFT JOIN Staff s
ON g.NUMBER = s.Age
GROUP BY
grp
您可以将此应用于日期,只需要一些日常操作
答案 2 :(得分:1)
难道你不能将年龄(或日期)视为一个新的小表,只是年龄(或日期)及其相应的范围吗? join语句可以为新表提供包含AgeGroups的列。使用新表,您可以使用标准的分组方法。
为分组创建一个新表似乎是鲁莽的,但是以编程方式进行编写很容易,而且我认为维护(或删除和重新创建)比case语句或where子句更容易。如果这个查询的结果是一次性的,一次性的sql语句可能效果最好,但我认为我的方法最适合长期使用。
答案 3 :(得分:1)
查看OVER clause及其相关条款:PARTITION BY,ROW,RANGE ......
确定行之前的行集的分区和排序 应用关联的窗口函数。也就是OVER子句 在查询结果中定义窗口或用户指定的行集 组。然后,窗口函数计算每行中的一个值 窗口。您可以将OVER子句与函数一起使用来计算 汇总值,如移动平均线,累计总量, 运行总计,或每组结果的前N个。
答案 4 :(得分:1)
好吧,几年前在Oracle DB中,我们采用以下方式实现:
select sum(t.Value), r.Name from DataTable t join Ranges r on (r.Session = ? and r.Start t.MyDate) group by r.Name
这完美无缺。从那时起,Oracle添加了新的SQL子句,也许可以使用它们。但在其他RDBMS上,这仍然是一种有效的方式。
另一种方法是创建许多函数,例如GET_YEAR_BY_DATE或GET_QUARTER_BY_DATE或GET_WEEK_BY_DATE(它们将返回相应的开始日期) 例如,对于任何日期返回开始日期的年份)。然后由他们分组:
select sum(Value), GET_YEAR_BY_DATE(MyDate) from DataTable
group by GET_YEAR_BY_DATE(MyDate)
答案 5 :(得分:1)
此类型中我最喜欢的案例是交易必须按财政季度或财政年度分组。各个企业的财政季度或财政年度界限可以接近奇怪。
我最喜欢的实现方法是为日期属性创建一个单独的表。我们称之为“Almanac”。此表中的一列是财务季度,另一列是会计年度。这张桌子的关键当然是日期。十年的数据填满了3,650行,加上一些闰年。然后,您需要一个可以从头开始填充此表的程序。所有企业日历规则都内置在这一个程序中。
当您需要按会计季度对交易数据进行分组时,您只需加入此表格的日期,然后按财务季度分组。
我认为这种模式可以通过其他类型的范围扩展到分组,但我自己从未做过。
答案 6 :(得分:1)
在您的第一个示例中,您的间隔是常规的,因此您只需使用功能即可获得所需的结果。下面是根据您的需要获取数据的示例。第一个查询使第一列保持日期格式(我最好如何处理它在SQL之外进行任何格式化),第二个为你进行字符串转换。
DECLARE @OrderHistory TABLE (Date DATE, Quantity INT)
INSERT @OrderHistory VALUES
('20120701', 2), ('20120702', 5), ('20120708', 1), ('20120710', 3),
('20120714', 4), ('20120717', 2), ('20120728', 6)
SET DATEFIRST 7
SELECT DATEADD(DAY, 1 - DATEPART(WEEKDAY, Date), Date) AS WeekStart,
SUM(Quantity) AS Quantity
FROM @OrderHistory
GROUP BY DATEADD(DAY, 1 - DATEPART(WEEKDAY, Date), Date)
SELECT WeekStart,
SUM(Quantity) AS Quantity
FROM @OrderHistory
CROSS APPLY
( SELECT CONVERT(VARCHAR(6), DATEADD(DAY, 1 - DATEPART(WEEKDAY, Date), Date), 6) + ' to ' +
CONVERT(VARCHAR(6), DATEADD(DAY, 7 - DATEPART(WEEKDAY, Date), Date), 6) AS WeekStart
) ws
GROUP BY WeekStart
使用以下方法可以为您的年龄组做类似的事情:
SELECT CAST(FLOOR(Age / 10.0) * 10 AS INT)
但是30-39失败了,因为该组没有数据。
我对这个问题的立场是,如果你作为一个关闭进行查询,使用临时表,cte或case语句应该工作得很好,这也应该扩展到对小数据集重用相同的查询。
如果您可能重复使用该组,或者您指的是大量数据,则创建一个永久表,其中定义了范围并将索引应用于所需的任何列。这是在OLAP中创建维度的基础。