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

时间:2014-02-14 21:23:38

标签: database join count sql-server-ce webmatrix

我正在使用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 个答案:

答案 0 :(得分:1)

如果您的Calendar表格中包含MonthYear,那么您应尝试使用

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]

答案 1 :(得分:0)

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

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

快乐的编码。