在查询结果上添加假行

时间:2013-02-22 13:17:50

标签: sql-server tsql

我有一个表(T1),其中包含以下列:department,dateofsale,totalsales。 我想要实现的是从开始日起一年内每月销售部门,并向后退一年。 也许以下查询将更好地展示我想要实现的目标。

-- Create the table T1
    CREATE TABLE [dbo].[T1](
    [department] [nvarchar](50) NULL,
    [dateofsale] [datetime] NULL,
    [totalsales] [decimal](18, 5) NULL
    ) ON [PRIMARY]

-- Add some data 
    INSERT [dbo].[T1] ([department], [dateofsale], [totalsales]) VALUES (N'0001', CAST(0x0000A29B00000000 AS DateTime), CAST(200.00000 AS Decimal(18, 5)))
    INSERT [dbo].[T1] ([department], [dateofsale], [totalsales]) VALUES (N'0001', CAST(0x0000A27D00000000 AS DateTime), CAST(300.00000 AS Decimal(18, 5)))
    INSERT [dbo].[T1] ([department], [dateofsale], [totalsales]) VALUES (N'0001', CAST(0x0000A29C00000000 AS DateTime), CAST(200.00000 AS Decimal(18, 5)))

-- The query
    declare @dataBegin datetime
    declare @dataEnd datetime
    set @dataEnd = '21/12/2013'
    set @dataBegin = DATEADD(month,-11, @dataEnd) - (DAY(@dataEnd)-1)
    set @dataEnd = DATEADD(month,1, @dataEnd) - (DAY(@dataEnd))
    SELECT department,SUM(totalsales) AS totsales, MONTH(dateofsale) as month, YEAR(dateofsale) as year
    FROM T1
    WHERE dateofsale >= @dataBegin AND dateofsale< @dataEnd 
    GROUP BY department,MONTH(dateofsale), YEAR(dateofsale)
    ORDER BY department,MONTH(dateofsale), YEAR(dateofsale)

在查询结果出现之前添加数据:

    department  /totsales/  month /year
    0001/ 300.00000 /11 /2013
    0001/ 400.00000 /12 /2013

问题是我还想要总数为零的月份。所以结果必须是:

department    /totsales/  month /year
0001/ 0   /1  /2013
0001/ 0   /2  /2013
0001/ 0   /3  /2013
0001/ 0   /4  /2013
0001/ 0   /5  /2013
0001/ 0   /6  /2013
0001/ 0   /7  /2013
0001/ 0   /8  /2013
0001/ 0   /9  /2013
0001/ 0   /10 /2013
0001/ 300.00000   /11 /2013
0001/ 400.00000   /12 /2013

我该怎么做?

6 个答案:

答案 0 :(得分:2)

您可以创建一个表Months并使用它进行左连接

SELECT *
FROM Months M
LEFT JOIN T1 T ON M.month = T.Month

答案 1 :(得分:2)

您不需要模拟缺失的行,只需为其获取正确的值。

注意:数据不仅需要按年度轮换,也需要按部门轮换。否则,您将获得NULL值

    -- Create the table T1
    DECLARE  @T1 TABLE(
    [department] [nvarchar](50) NULL,
    [dateofsale] [datetime] NULL,
    [totalsales] [decimal](18, 5) NULL
    ) 

-- Add some data 
    INSERT @T1([department], [dateofsale], [totalsales]) VALUES (N'0001', CAST(0x0000A29B00000000 AS DateTime), CAST(200.00000 AS Decimal(18, 5)))
    INSERT @T1([department], [dateofsale], [totalsales]) VALUES (N'0001', CAST(0x0000A27D00000000 AS DateTime), CAST(300.00000 AS Decimal(18, 5)))
    INSERT @T1([department], [dateofsale], [totalsales]) VALUES (N'0001', CAST(0x0000A29C00000000 AS DateTime), CAST(200.00000 AS Decimal(18, 5)))
    INSERT @T1([department], [dateofsale], [totalsales]) VALUES (N'0003', CAST(0x0000A29C00000000 AS DateTime), CAST(100.00000 AS Decimal(18, 5)))
-- The query
DECLARE @dataBegin DATETIME
DECLARE @dataEnd DATETIME

SET @dataEnd = '20140101'
SET @dataBegin = DATEADD(month, - 11, @dataEnd) - (DAY(@dataEnd) - 1)
SET @dataEnd = DATEADD(month, 1, @dataEnd) - (DAY(@dataEnd));

WITH Months (
    MonthNr
    ,Year
    ,Department
    )
AS (
    SELECT MonthNr
        ,Y.Year
        ,D.department
    FROM (
        VALUES (1),(2),(3),(4),(5),(6),(7),(8),(9),(10),(11),(12)
        ) M(MonthNr)
    CROSS JOIN (
        SELECT DISTINCT T.department
        FROM @T1 T
        ) D
    CROSS JOIN (
        SELECT year
        FROM (
            VALUES (2013) --insert as many years as you need
            ) T(year)
        ) Y
    )
SELECT M.department
    ,ISNULL(T.totsales, 0) totalSales
    ,M.MonthNr month
    ,M.year
FROM Months M
LEFT JOIN (
    SELECT department
        ,SUM(totalsales) AS totsales
        ,MONTH(dateofsale) AS month
        ,YEAR(dateofsale) AS year
    FROM @T1
    WHERE dateofsale >= @dataBegin
        AND dateofsale < @dataEnd
    GROUP BY department
        ,MONTH(dateofsale)
        ,YEAR(dateofsale)
    ) T ON T.month = M.MonthNr and T.department = M.Department 
ORDER BY department
    ,M.MonthNr
    ,M.Year

结果:

department       totalSales          month       year
--------------- --------------------- ----------- -----------
0001            0.00000               1           2013
0001            0.00000               2           2013
0001            0.00000               3           2013
0001            0.00000               4           2013
0001            0.00000               5           2013
0001            0.00000               6           2013
0001            0.00000               7           2013
0001            0.00000               8           2013
0001            0.00000               9           2013
0001            0.00000               10          2013
0001            300.00000             11          2013
0001            400.00000             12          2013
0003            0.00000               1           2013
0003            0.00000               2           2013
0003            0.00000               3           2013
0003            0.00000               4           2013
0003            0.00000               5           2013
0003            0.00000               6           2013
0003            0.00000               7           2013
0003            0.00000               8           2013
0003            0.00000               9           2013
0003            0.00000               10          2013
0003            0.00000               11          2013
0003            100.00000             12          2013

答案 2 :(得分:1)

您可以创建两个查询并对它们进行UNION,或者为了制作缺失的行,使用CTE。我理解你的意思是说你在11月之前没有数据。

WITH months
AS 
( 
    SELECT 2013 as yr, 1 as mnth     
    UNION ALL 
    SELECT 2013 as yr, mnth+1 as mnth
    FROM months
    WHERE  mnth < 12      
) select months.yr, months.mnth, department, isnull(totsales,0.00) as totsales
from months
left join sales on sales.yr = months.yr and sales.month = months.mnth

只需使用datepart函数从销售日期中提取月份。上面的查询只是为了向您展示如何获得数据中没有的月份。

答案 3 :(得分:1)

当我遇到这个问题时,我设法解决这个问题的方法是创建一个临时表,创建所有必需的日期,然后在临时表和select中的数据查询之间执行UNION语句:

-- Create the table T1
CREATE TABLE #T1(
[department] [nvarchar](50) NULL,
[dateofsale] [datetime] NULL,
[totalsales] [decimal](18, 5) NULL
) ON [PRIMARY]
-- Add some data 
INSERT #T1 ([department], [dateofsale], [totalsales]) VALUES (N'0001', CAST(0x0000A29B00000000 AS DateTime), CAST(200.00000 AS Decimal(18, 5)))
INSERT #T1 ([department], [dateofsale], [totalsales]) VALUES (N'0001', CAST(0x0000A27D00000000 AS DateTime), CAST(300.00000 AS Decimal(18, 5)))
INSERT #T1 ([department], [dateofsale], [totalsales]) VALUES (N'0001', CAST(0x0000A29C00000000 AS DateTime), CAST(200.00000 AS Decimal(18, 5)))

--Solution Start
DECLARE @dataBegin datetime
DECLARE @dataEnd datetime
DECLARE @CurrentDate DATETIME
SET @dataEnd = '2013-12-23'
SET @dataBegin = DATEADD(month,-11, @dataEnd) - (DAY(@dataEnd)-1)
SET @dataEnd = DATEADD(month,1, @dataEnd) - (DAY(@dataEnd))

SET @CurrentDate = @dataBegin

-- Create Temporary Table   
CREATE TABLE #calDate (calDate DATETIME)

-- Populate Table
INSERT INTO #calDate (calDate)
    SELECT @CurrentDate

WHILE DATEADD(MONTH, 1, @CurrentDate) <= @dataEnd 
BEGIN
    INSERT INTO #calDate (calDate)
        SELECT DATEADD(MONTH, 1, @CurrentDate)
    SET @CurrentDate = DATEADD(MONTH, 1, @CurrentDate)
END

-- Query Data
SELECT 
    department
    , sum(totsales)
    , month
    , year
FROM(
    SELECT '0001' as 'department',0 AS totsales, MONTH(calDate) as month, YEAR(calDate) as year FROM #calDate
    UNION
    SELECT department,SUM(totalsales) AS totsales, MONTH(dateofsale) as month, YEAR(dateofsale) as year
    FROM #T1
    WHERE dateofsale >= @dataBegin AND dateofsale< @dataEnd 
    GROUP BY department,MONTH(dateofsale), YEAR(dateofsale)
)a
GROUP BY department,month, year
ORDER BY department,month, year
DROP table #calDate
DROP table #T1

上述唯一的问题是部门在临时表创建时是硬编码的,可以作为参数传递。

答案 4 :(得分:1)

数字表的一个重要用途:

-- Populate numbers table; keep this around, you'll find uses for it!
;WITH
  Pass0 as (select 1 as C union all select 1), --2 rows
  Pass1 as (select 1 as C from Pass0 as A, Pass0 as B),--4 rows
  Pass2 as (select 1 as C from Pass1 as A, Pass1 as B),--16 rows
  Pass3 as (select 1 as C from Pass2 as A, Pass2 as B),--256 rows
  Pass4 as (select 1 as C from Pass3 as A, Pass3 as B),--65536 rows
  Pass5 as (select 1 as C from Pass4 as A, Pass4 as B),--4,294,967,296 rows
  Tally as (select row_number() over(order by C) as Number from Pass5)
 select Number into dbo.Numbers from Tally where Number <= 1000000


-- The query
declare @dataBegin datetime
declare @dataEnd datetime
set @dataEnd = '2013-12-21'
set @dataBegin = DATEADD(month,-11, @dataEnd) - (DAY(@dataEnd)-1)
set @dataEnd = DATEADD(month,1, @dataEnd) - (DAY(@dataEnd));
with sales as (
    SELECT department,SUM(totalsales) AS totsales, MONTH(dateofsale) as month, YEAR(dateofsale) as year
    FROM T1
    WHERE dateofsale >= @dataBegin AND dateofsale< @dataEnd 
    GROUP BY department,MONTH(dateofsale), YEAR(dateofsale)
),
all_months as (
    select distinct department, Number as [month], 2013 as [year]
    from T1 as t
    cross join dbo.Numbers as n
    where n.Number <= 12
)
select m.department, coalesce(s.totsales, 0), m.[month], m.[year]
from all_months as m
left join sales as s
    on m.department = s.department
    and m.[year] = s.[year]
    and m.[month] = s.[month]
ORDER BY m.department, m.[month], m.[year]

答案 5 :(得分:0)

为每个月或每个部门插入一个零值。现在您的数据是明确的,您的查询也得到了简化。

假设缺少数据意味着零值不是一个好的数据实践。