SQL Server - 存储过程中的模拟记录

时间:2017-04-12 13:14:54

标签: sql sql-server

如果这是重复的道歉 - 我已经找到了答案,但我发现的并不是我正在寻找的。

我有一个非常简单的存储过程,它从库存表中返回基于日期的库存。 开始日期结束日期参数需要 stockist id

查询如下......

select  
    S.Date, 
    S.Amount 
from Stock S
where 
    S.StockistID = @pi_stockistId and
    S.Date >= @pi_startDate and
    S.Date <= @pi_endDate

我需要确保的是,对于请求的日期范围,总会返回一行 - 即使数据库中没有该日期和库存商的记录。模拟记录的数量为零。

数据被输入到外部系统,因此我不需要插入记录,除非所述系统将值从零增加(我不需要在表中存储数千个空记录)。

如果来自外部系统的后续呼叫增加了数量,此时我将把记录插入到表中。

我知道我可以通过创建临时表变量并将不存在的记录插入结果集来实现这一点 - 我只是想知道我是否可以在查询中做任何事情以防止必须使用表变量。

再次,如果已经被问过,请道歉。

我正在使用SQL Server 2014。

由于

示例

这是来自股票表

的数据样本
------------------------------------
| Date       | StockistId | Amount |
------------------------------------
| 12/04/2017 | 1          | 10     |
| 14/04/2017 | 1          | 20     |
------------------------------------

如果我对Stockist 1的日期12/04/2017 - 14/04/2017运行查询,我会得到以下内容......

-----------------------
| Date       | Amount |
-----------------------
| 12/04/2017 | 10     |
| 14/04/2017 | 20     |
-----------------------

由于没有13/04/2017的记录

我想回来的是......

-----------------------
| Date       | Amount |
-----------------------
| 12/04/2017 | 10     |
| 13/04/2017 | 0      |
| 14/04/2017 | 20     |
-----------------------

我的查询已被嘲笑&#39;创下2017年3月13日的记录

2 个答案:

答案 0 :(得分:3)

如果您有日历表,这将成为一个非常简单的查询。

SELECT  c.Date,
        Amount = ISNULL(s.Amount, 0)
FROM    dbo.Calendar AS c
        LEFT JOIN Stock AS s
            ON s.Date = c.Date
            AND s.StockistID = @pi_stockistId
WHERE   c.Date >= @pi_startDate
AND     c.Date <= @pi_endDate;

如果您没有日历表,我建议您创建一个日历表,它们非常有用。如果你不能创建一个,那么即时生成一个很容易:

首先使用交叉连接和ROW_NUMBER()

生成一系列数字
WITH N1 AS (SELECT N FROM (VALUES (1),(1),(1),(1),(1),(1),(1),(1),(1),(1)) n (N)),
N2 (N) AS (SELECT 1 FROM N1 AS N1 CROSS JOIN N1 AS N2),
N3 (N) AS (SELECT 1 FROM N2 AS N1 CROSS JOIN N2 AS N2)
SELECT  ROW_NUMBER() OVER(ORDER BY N) - 1
FROM    N3;

这将生成0 - 9999之间的数字,该数字应涵盖您需要的任何日期范围。

然后,您可以将此号码与DATEADD一起使用,以获取您所在范围内的日期:

DECLARE @pi_startDate DATE = '20170101',
        @pi_endDate DATE = '20170301';

WITH N1 AS (SELECT N FROM (VALUES (1),(1),(1),(1),(1),(1),(1),(1),(1),(1)) n (N)),
N2 (N) AS (SELECT 1 FROM N1 AS N1 CROSS JOIN N1 AS N2),
N3 (N) AS (SELECT 1 FROM N2 AS N1 CROSS JOIN N2 AS N2),
Numbers (N) AS (SELECT ROW_NUMBER() OVER(ORDER BY N) - 1 FROM N3)

SELECT  TOP (DATEDIFF(DAY, @pi_startDate, @pi_endDate) + 1)
        Date = DATEADD(DAY, N, @pi_startDate)
FROM    Numbers;

它为您提供日期。

然后,对于最后一步,你只需要加入你的股票表

DECLARE @pi_startDate DATE = '20170101',
        @pi_endDate DATE = '20170301';

WITH N1 AS (SELECT N FROM (VALUES (1),(1),(1),(1),(1),(1),(1),(1),(1),(1)) n (N)),
N2 (N) AS (SELECT 1 FROM N1 AS N1 CROSS JOIN N1 AS N2),
N3 (N) AS (SELECT 1 FROM N2 AS N1 CROSS JOIN N2 AS N2),
Calendar (Date) AS 
(   SELECT TOP (DATEDIFF(DAY, @pi_startDate, @pi_endDate) + 1)
            DATEADD(DAY, ROW_NUMBER() OVER(ORDER BY N) - 1, @pi_startDate)
    FROM    N2
)
SELECT  c.Date,
        Amount = ISNULL(s.Amount, 0)
FROM    Calendar AS c
        LEFT JOIN Stock AS s
            ON s.Date = c.Date
            AND s.StockistID = @pi_stockistId;

有关日历表的更多信息,以及无循环的生成系列可以在3部分文章中找到:

答案 1 :(得分:1)

虽然接受的答案非常好,但我想提供另一种方法来创建日期 - 使用递归CTE。也许有争议,但我说它比较简单。

DECLARE @startDate DATE = '20170401';
DECLARE @endDate DATE = '20170410';

WITH CTE_Dates AS 
(
    SELECT @StartDate AS Dt
    UNION ALL
    SELECT DATEADD(DAY,1,Dt)
    FROM CTE_Dates 
    WHERE Dt < @endDate
)
SELECT * 
FROM CTE_Dates
--LEFT JOIN to your data here
OPTION (MAXRECURSION 0); -- needed if range is more than 100 days