代码之家  ›  专栏  ›  技术社区  ›  Hayley Guillou

按年份和月份的计数记录,包括零计数

  •  1
  • Hayley Guillou  · 技术社区  · 11 年前

    我使用的是SQL Server Compact Edition服务器,我希望统计每个月对应于某个日期范围内的某个教程的评论数,包括计数为零的月份。我知道我需要将一个“日历”表加入到我的表中,以说明缺少的月份,但我需要帮助来正确实现这一点。

    我有一个表格,里面有来自不同教程的所有评论。这个表叫做注释,我需要的列是 [Tutorial] ( nvarchar )以及 [DateAdded] ( DateTime ).

    Tutorial |  DateAdded  
    ---------+-------------
    sample   |  2013-09-02  
    sample   |  2013-09-04
    sample   |  2013-09-12
    sample   |  2013-09-12
    example  |  2013-09-15
    sample   |  2013-09-16
    sample   |  2013-09-21
    sample   |  2013-09-30
    sample   |  2013-10-01   
    sample   |  2013-11-11
    sample   |  2013-11-11
    example  |  2013-11-14
    sample   |  2013-11-15
    sample   |  2013-11-19
    sample   |  2013-11-21
    sample   |  2013-11-25
    sample   |  2014-02-04
    sample   |  2014-02-06  
    

    我有一个 Calendar 表中有年份和月份列,如下所示:

    Year | Month
    -----+------
    2000 | 01
    2000 | 02
    .    | .
    .    | .
    .    | .
    2099 | 12
    

    如果我要查找过去一年(截至2014年2月14日)的“样本”评论的每月计数,那么理想的输出将是:

    Tutorial | Year | Month | Count
    ---------+------+-------+------
     sample  | 2013 |  09   |  7
     sample  | 2013 |  10   |  1
     sample  | 2013 |  11   |  6
     sample  | 2013 |  12   |  0
     sample  | 2014 |  01   |  0
     sample  | 2014 |  02   |  2
    

    我能够找出如何执行以下查询,但我需要没有注释的月份也返回0。

    SELECT 
         Tutorial, 
         datepart(year, DateAdded) AS Year, 
         datepart(month, DateAdded) AS Month, 
         COUNT(*) AS Count From Comments 
    WHERE 
         DateAdded > DATEADD(year,-1,GETDATE())  
           AND 
         Tutorial='sample' 
    GROUP BY 
         Tutorial, 
         datepart(year, DateAdded), 
         datepart(month, DateAdded)
    

    使用上面的示例数据输出。

    Tutorial | Year | Month | Count
    ---------+------+-------+------
     sample  | 2013 |  09   |  7
     sample  | 2013 |  10   |  1
     sample  | 2013 |  11   |  6
     sample  | 2014 |  02   |  2
    

    我知道我需要连接表,但我似乎无法确定要使用哪个连接或如何正确实现它。请记住,这是针对SQL Server CE的,因此不能使用来自SQL Server的所有命令。

    提前感谢!

    2 回复  |  直到 11 年前
        1
  •  1
  •   Dag Høidahl    7 年前

    如果您有 Calendar 带有的表格 Month Year 你应该试试

    SELECT t2.Tutorial, t1.[Month], t1.[Year], COALESCE(t2.Number, 0) AS Result
      FROM Calendar AS t1 LEFT JOIN (
        SELECT 
          Tutorial, 
          CONVERT(NCHAR(6), DateAdded, 112) AS tutDate,
          COUNT(*) AS Count From Comments 
        WHERE 
          DateAdded > DATEADD(year,-1,GETDATE())  
          AND 
          Tutorial='sample' 
        GROUP BY 
          Tutorial, 
          CONVERT(NCHAR(6), [Order Date], 112)
      ) AS t2
      ON (t1.[Year] + t1.[Month]) = t2.tutDate
      ORDER BY t1.[Year] + t1.[Month]
    
        2
  •  0
  •   Brandon R. Gates    11 年前

    下面是一个独立的脚本,您可以使用它来尝试,而不会在生产中触及任何真实的数据库对象。代码的底部三分之一包含您要查找的连接的帮助。

    SQL Server CE将允许您编写存储过程,而存储过程又可以用作报表的源。存储过程很好,因为它们可以接受输入参数,这是进行报告的理想选择。

    -- create dummy Comments table for prototyping
    create table #Comments (
        ID int identity(1,1) not null,
        Tutorial nvarchar(50) not null,
        DateAdded datetime not null,
        primary key clustered(DateAdded,ID,Tutorial)
    );
    
    -- populate dummy Comments table
    declare @startDate datetime = '2000-01-01';
    declare @endDate datetime = '2014-02-14';
    declare @numTxns int = 5000;
    
    set nocount on;
    
    declare @numDays int = cast(@endDate as int) - cast(@startDate as int) + 1;
    declare @i int = 1;
    declare @j int = @i + @numTxns;
    declare @rnd float;
    while @i <= @j
    begin
        set @rnd = RAND();
        insert into #Comments (Tutorial,DateAdded)
        select 
        -- random tutorial titles
            coalesce (
                case when @rnd < .25 then 'foo' else null end,
                case when @rnd between .5 and .75 then 'baz' else null end, 
                case when @rnd > .75 then 'qux' else null end,
                'bar'
            ) as Tutorial,
        -- random dates between @startDate and @endDate
            cast(cast(rand() * @numDays + @startDate as int) as datetime) as DateAdded
        set @i = @i + 1
    end;
    
    -- try deleting some months to see what happens
    delete from #Comments
    where DateAdded between '2013-11-01' and '2013-11-30'
        or DateAdded between '2014-01-01' and '2014-01-31';
    
    set nocount off;
    go
    
    -- ### following could easily be rewritten as a stored procedure
    
    -- stored procedure parameters
    declare @startDate datetime = '2000-01-01';
    declare @endDate datetime = '2014-03-31';
    -- pick only one option below
    --declare @Tutorial nvarchar(50) = 'foo'; -- this only gets data for Tutorials called 'foo'
    declare @Tutorial nvarchar(50) = 'all'; -- this gets data for all tutorials
    
    -- begin stored procedure code
    set nocount on;
    
    -- this temp table is an alternative to 
    -- creating ***and maintaining*** a table full of dates,
    -- months, etc., and cluttering up your database
    -- in production, it will automatically delete itself
    -- once the user has completed running the report.
    create table #dates (
        DateAdded datetime not null,
        YearAdded int null,
        MonthAdded int null,
        primary key clustered (DateAdded)
    );
    
    -- now we put dates into #dates table
    -- based on the parameters supplied by 
    -- the user running the report
    declare @date datetime = @startDate;
    while @date <= @endDate
    begin
        insert into #dates
        select @date, YEAR(@date), MONTH(@date);
    
        set @date = @date + 1;
    end;
    
    -- ## Why put every day of the month in this table?
    -- ## I asked for a monthy report, not daily!
    -- Yes, but looping through dates is easier, simply add 1 for the next date.
    -- You can always build a monthly summary table later if you'd like.
    -- This *is* kind of a brute-force solution, but easy to write.
    -- More answers to this question in the code below, where they'll make more sense.
    
    set nocount off;
    
    -- now we return the data to the user
    -- any month with no Tutorials will still show up in the report
    -- but the counts will show as zero
    select YearAdded, MonthAdded, SUM(Count_From_Comments) as Count_From_Comments,
        SUM(foo) as Count_Foo, SUM(bar) as Count_Bar, 
        SUM(baz) as Count_Baz, SUM(qux) as Count_Qux
    from (
    -- ## you can reuse the following code for a detail report by day
    -- ## another answer to 'Why not by month?' from above
    -- start daily report code
        select t1.DateAdded, t1.YearAdded, t1.MonthAdded, t2.Tutorial, 
                coalesce(Count_From_Comments,0) as Count_From_Comments, 
                case when t2.Tutorial = 'foo' then 1 else 0 end as foo,
                case when t2.Tutorial = 'bar' then 1 else 0 end as bar,
                case when t2.Tutorial = 'baz' then 1 else 0 end as baz,
                case when t2.Tutorial = 'qux' then 1 else 0 end as qux
        from #dates as t1 -- no where clause needed because #dates only contains the ones we want
        left join ( -- left join here so that we get all dates, not just ones in #Comments
            select *, 1 AS Count_From_Comments
            from #Comments
            where @Tutorial in (Tutorial,'all')
        ) as t2
            on t1.DateAdded = t2.DateAdded -- ## join on one field instead of two, another answer to 'Why not by month?' from above
    -- end daily report code
    ) as qDetail
    group by YearAdded, MonthAdded
    order by YearAdded, MonthAdded
    
    -- end stored procedure code
    go
    
    -- ## Not required in production code,
    -- ## but handy when testing this script.
    drop table #dates;
    
    -- #### Since this will be a real table in production
    -- #### we definitely only want this for testing!
    drop table #Comments;
    
    go
    

    编码愉快。