T-SQL INSERT到UNLESS

时间:2016-09-22 14:23:19

标签: sql-server tsql

我有下表:

Account              | Period   | Amount 
-------------------- | -------- | ------
Umbrella Corporation | 201601   | 100
Umbrella Corporation | 201602   | 50
Umbrella Corporation | 201608   | 100
Acme Inc             | 201504   | 85
Acme Inc             | 201504   | 90
Acme Inc             | 201512   | 40

[期间]是纯文本,但代表日期为YYYMM,有时也可能包含YYYMMDD,但为此,我忽略了这一天。

目标

为[帐户]的每个唯一组合和每年的12个可能时间段(YYYYMM)插入新行。

示例

- >如果表中已存在唯一组合,则不执行任何操作 - >如果唯一组合尚不存在,请插入一个包含帐户,期间和金额的新行(对于新插入的行,该值始终为零)。

期望的结果

Account                 | Period    | Amount 
--------------------    | --------  | ------
Umbrella Corporation    | 201601    | 100
Umbrella Corporation    | 201602    | 100
Umbrella Corporation    | 201603    | 0
Umbrella Corporation    | 201604    | 0
Umbrella Corporation    | 201605    | 0
Umbrella Corporation    | 201606    | 0
Umbrella Corporation    | 201607    | 0
Umbrella Corporation    | 201608    | 100
Umbrella Corporation    | 201609    | 0
Umbrella Corporation    | 201610    | 0
Umbrella Corporation    | 201611    | 0
Umbrella Corporation    | 201612    | 0
Acme Inc                | 201501    | 0
Acme Inc                | 201502    | 0
Acme Inc                | 201503    | 0
Acme Inc                | 201504    | 85
Acme Inc                | 201504    | 90
Acme Inc                | 201605    | 0
Acme Inc                | 201506    | 0
Acme Inc                | 201507    | 0
Acme Inc                | 201508    | 0
Acme Inc                | 201509    | 0
Acme Inc                | 201510    | 0
Acme Inc                | 201511    | 0
Acme Inc                | 201512    | 40

我还没有找到一个坚实的起点来做这件事。我在这里发现了一些类似的问题,它们使用INSERT INTO ....不存在或合并或加入。但理想情况下,如果可能的话,我希望在不需要其他表的情况下实现此结果。

非常感谢任何指导。我使用的是SQL Server 2008R2。

*我试图提出一个好问题,如果您认为可能会更好,请告诉我。

3 个答案:

答案 0 :(得分:1)

您可以使用递归CTE来生成所需的所有静态值,并将现有数据加入到该表中,而不是使用另一个表。

答案 1 :(得分:1)

一种选择是使用CTE创建计数表以帮助确定可能的月间隔。以下示例演示了此方法。

-- Create example table and sample data set.
CREATE TABLE Accounts
(
     Account    NVARCHAR(100)
    ,Period     NVARCHAR(10)
    ,Amount     FLOAT
)

INSERT INTO Accounts
(
 Account
,Period 
,Amount 
)
SELECT 'Umbrella Corporation' , '201601'   , 100    UNION ALL
SELECT 'Umbrella Corporation' , '201602'   , 50     UNION ALL
SELECT 'Umbrella Corporation' , '201608'   , 100    UNION ALL
SELECT 'Acme Inc'             , '201504'   , 85     UNION ALL
SELECT 'Acme Inc'             , '201504'   , 90     UNION ALL
SELECT 'Acme Inc'             , '201512'   , 40;

DECLARE @endPeriod NVARCHAR(10) = '201612';

WITH
E1(N) AS (SELECT 1 FROM (VALUES (1), (1), (1), (1), (1), (1), (1), (1), (1), (1)) dt(n)),
E2(N) AS (SELECT 1 FROM E1 a, E1 b), 
E4(N) AS (SELECT 1 FROM E2 a, E2 b), 
Tally(N) AS     -- Create tally table. This tally table will return a maximum of a 1000 rows.
(
    SELECT  (ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) - 1) FROM E4
)
, PossiblePeriods AS    --Determine possible monthly intervals for the year. Implementation assumes a maximum back fill of 5 years (60 months).
(
    SELECT   Account
            ,LEFT(CONVERT(NVARCHAR(10), DATEADD(MONTH, [T].N, [A].StartYear), 112), 6) AS Period
    FROM    (
                SELECT   Account
                        ,CONVERT(DATETIME, LEFT(MIN(Period), 4), 112) AS StartYear
                FROM    Accounts
                GROUP BY Account
            ) [A]
    CROSS JOIN 
            (
                SELECT TOP 60 N FROM Tally  -- Modify the TOP statement to control the number of months to back fill (up to a 1000 months)
            ) T
)
, NewPeriods AS -- Determine the new periods to add.
(
    SELECT   [P].Account
            ,[P].Period
            ,[A].Amount
    FROM    PossiblePeriods [P]
    LEFT OUTER JOIN Accounts [A]  ON [A].Account = P.Account AND A.Period = [P].Period
    WHERE   [P].Period <= @endPeriod
)
INSERT INTO Accounts
(
     Account
    ,Period 
    ,Amount 
)
SELECT  Account
        ,Period 
        ,0  
FROM    NewPeriods
WHERE   Amount IS NULL


--  Select out result.
SELECT      *
FROM        Accounts
ORDER BY    Account, Period

DROP TABLE Accounts

答案 2 :(得分:0)

您可以在CROSS JOINaccounts之间使用periods

DECLARE @StartPeriod VARCHAR(8), @EndPeriod VARCHAR(8);
SET @StartPeriod = '201501';
SET @EndPeriod = '201612';

WITH Periods AS
(
    SELECT CONVERT(VARCHAR(6),DATEADD(MONTH,number,@StartPeriod + '01'),112) Period
    FROM master.dbo.spt_values
    WHERE type = 'P'
    AND CONVERT(VARCHAR(6),DATEADD(MONTH,number,@StartPeriod + '01'),112) <= @EndPeriod
)
SELECT  A.Account,
        B.Period,
        ISNULL(C.Amount,0) Amount
FROM (  SELECT DISTINCT Account
        FROM dbo.YourTable) A
CROSS JOIN Periods B
LEFT JOIN dbo.YourTable C
    ON A.Account = C.Account
    AND B.Period = C.Period;

此解决方案假设您还没有一个包含所需时间段的表格。如果你有一个,那么使用它而不是在CTE中创建它们。