【问题标题】:How do I fetch data grouped by month and year including the period with no data?如何获取按月和年分组的数据,包括没有数据的时期?
【发布时间】:2012-04-30 20:08:13
【问题描述】:

对 SQL 来说相对较新,我被这个小问题难住了。这似乎不是很难做到,但我似乎无法弄清楚。

我正在尝试从表中获取交易计数,但我似乎无法让 sql 让我显示所有月份,而不仅仅是交易发生的月份和年份。

这里是查询:

SELECT      YEAR(dbo.countproject.trans_date)   AS [TransYear]
        ,   MONTH (dbo.countproject.trans_date) AS [TransMonth]
        ,   COUNT(Id)                           AS TransNum 
FROM        dbo.countproject
WHERE       dbo.countproject.make_name  = 'Honda'
AND         dbo.countproject.model_name = 'Civic'
AND         dbo.countproject.type       = 'Sale'
AND         dbo.countproject.trans_type LIKE '%%EU'
AND         dbo.countproject.mfr        = '2000'
GROUP BY    YEAR(dbo.countproject.trans_date)
        ,   MONTH(dbo.countproject.trans_date)
ORDER BY    YEAR(dbo.countproject.trans_date)

查询返回以下结果集:

| TransYear | TransMonth | TransNum |
|-----------|------------|----------|
|  2004     |     1      |     5    |
|  2004     |     3      |     1    |
|  2005     |     4      |     2    |

等等……

我试图让它显示所有月份和年份,即使该值为 NULL。

我尝试创建一个以年和月为列的新表,以使其以某种方式加入,但我迷路了。

任何帮助将不胜感激。

【问题讨论】:

标签: sql select null count


【解决方案1】:

如果您使用SQL Server 2005 or above,您可以使用Common Table Expressions (CTE) 来获得所需的结果。下面的示例显示了如何获取问题中描述的结果。

Click here to view the demo in SQL Fiddle.

说明

  • 创建和插入语句创建表并填充一些示例数据。我已根据问题中提供的查询创建了表。
  • WITH 子句中的语句正在执行递归表达式。在这种情况下,UNION ALL 上方的 SELECT 获取表 dbo.countproject 中可用的最小和最大日期
  • 获取最小日期后,UNION ALL 之后的第二个 SELECT 语句会以 1 个月的间隔递增日期,直到递归表达式达到表中可用的最大日期。
  • 递归 CTE 已生成所有可能的可用日期。此输出在名为 alltransactions 的表中可用。
  • 我们必须使用 LEFT OUTER JOIN 将此 CTE 输出 alltransactions 与实际表 countproject 连接起来,因为即使没有交易,我们也希望显示所有年份和月份。
  • alltransactionscountproject 表在日期的 部分连接。然后,查询在 WHERE 子句中应用必要的过滤器,然后按年和月对数据进行分组,然后按年和月对其进行排序。
  • 从样本数据可以看出,表中最早的日期是2004-07-01,最晚的日期是2005-12-01。因此,输出显示从 2004 年/第 07 个月到 2005 年/第 12 个月。

希望对您有所帮助。

脚本

CREATE TABLE dbo.countproject
(   
        id          INT         NOT NULL IDENTITY
    ,   trans_date  DATETIME    NOT NULL
    ,   make_name   VARCHAR(20) NOT NULL
    ,   model_name  VARCHAR(20) NOT NULL
    ,   type        VARCHAR(20) NOT NULL
    ,   trans_type  VARCHAR(20) NOT NULL
    ,   mfr         INT         NOT NULL
);

INSERT INTO dbo.countproject (trans_date, make_name, model_name, type, trans_type, mfr) VALUES
    ('1900-01-01', 'Honda',     'Civic',    'Sale', 'EU', 2000),
    ('1900-01-01', 'Toyota',    'Corolla',  'Sale', 'EU', 2000),
    ('2004-07-01', 'Nissan',    'Altima',   'Sale', 'EU', 2000),
    ('2005-12-01', 'Toyota',    'Camry',    'Sale', 'EU', 2000),
    ('2004-04-01', 'Ford',      'Focus',    'Sale', 'EU', 2000),
    ('2005-08-01', 'Honda',     'Civic',    'Sale', 'EU', 2000),
    ('2005-11-01', 'Toyota',    'Camry',    'Sale', 'EU', 2000),
    ('2004-08-01', 'Toyota',    'Corolla',  'Sale', 'EU', 2000),
    ('2005-12-01', 'Honda',     'Civic',    'Sale', 'EU', 2000),
    ('2004-07-01', 'Honda',     'Civic',    'Sale', 'EU', 2000),
    ('2004-11-01', 'Honda',     'Civic',    'Sale', 'EU', 2000),
    ('2005-08-01', 'Honda',     'Civic',    'Sale', 'EU', 2000);


;WITH alltransactions
AS
(
    SELECT      MIN(trans_date) AS continuousdate
            ,   MAX(trans_date) AS maximumdate
    FROM        dbo.countproject
    WHERE           trans_date <> '1900-01-01'
    UNION ALL 
    SELECT      DATEADD(MONTH, 1, continuousdate) AS continuousdate
            ,   maximumdate
    FROM        alltransactions
    WHERE       DATEADD(MONTH, 1, continuousdate) <= maximumdate
)
SELECT          YEAR(at.continuousdate)     AS [Year]
            ,   MONTH(at.continuousdate)    AS [Month]

            ,   COUNT(cp.trans_date)        AS [Count]
FROM            alltransactions at
LEFT OUTER JOIN countproject    cp
ON              YEAR(at.continuousdate)     = YEAR(cp.trans_date)
AND             MONTH(at.continuousdate)    = MONTH(cp.trans_date)
AND             cp.make_name                = 'Honda'
and             cp.model_name               = 'Civic'
and             cp.type                     = 'Sale'
and             cp.trans_type               LIKE '%EU'
and             cp.mfr                      = '2000'
GROUP BY        YEAR(at.continuousdate)
            ,   MONTH(at.continuousdate)
ORDER BY        [Year]
            ,   [Month];

输出

Year   Month  Count
-----  ------ -----
2004      4     0
2004      5     0
2004      6     0
2004      7     1
2004      8     0
2004      9     0
2004     10     0
2004     11     1
2004     12     0
2005      1     0
2005      2     0
2005      3     0
2005      4     1
2005      5     0
2005      6     0
2005      7     0
2005      8     2
2005      9     0
2005     10     0
2005     11     0
2005     12     1

【讨论】:

  • 谢谢!这成功了。更复杂的是......无论如何我可以从查询中删除 1900-1989 年,因为我有一个 1356 行的列表。我认为将来我还想以某种方式旋转表格,以便我可以将所有名称作为行和列中的制造年份。我现在要做一些阅读。再次感谢您的帮助!
  • 是的,我的错。我意识到我在 where close 中使用了错误的字段,所以它没有提取任何值。
【解决方案2】:

您必须使用 LEFT 或 RIGHT OUTER JOIN!

这是一个简单的示例:http://www.w3schools.com/sql/sql_join_left.asp

您应该自己完成。

问候

【讨论】:

    【解决方案3】:

    唉,SQL 语句只能返回表中的数据。如果您想要所有月份,您需要一个包含您感兴趣的年/月组合的表格,或者最好是一个包含所有日期及其相关信息的日历表。

    对于日历表,您的查询有一个 from 子句,如下所示:

    from 
    (
       select distinct year(date) as yr, month(date) as mon
       from calendar c
       where date between <earliest> and <latest>
    ) c 
    left outer join CountTable ct
      on c.yr = year(ct.trans_date) 
      and c.mon = month(ct.trans_date)
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-03-22
      • 2017-11-05
      • 2023-03-15
      • 1970-01-01
      • 2017-10-17
      相关资源
      最近更新 更多