SQL Server-在月份不存在时插入具有空值的行

SQL Server-在月份不存在时插入具有空值的行,sql,sql-server,sql-insert,Sql,Sql Server,Sql Insert,我有一张这样的桌子: Yr | Mnth | W_ID | X_ID | Y_ID | Z_ID | Purchases | Sales | Returns | 2015 | 10 | 1 | 5210 | 1402 | 2 | 1000.00 | etc | etc | 2015 | 12 | 1 | 5210 | 1402 | 2 | 12000.00 | etc | etc | 2016 | 1 |

我有一张这样的桌子:

 Yr  | Mnth | W_ID | X_ID | Y_ID | Z_ID | Purchases |    Sales | Returns |
2015 |  10  |    1 | 5210 | 1402 |    2 |   1000.00 |      etc |     etc |
2015 |  12  |    1 | 5210 | 1402 |    2 |  12000.00 |      etc |     etc |
2016 |   1  |    1 | 5210 | 1402 |    2 |   1000.00 |      etc |     etc |
2016 |   3  |    1 | 5210 | 1402 |    2 |       etc |      etc |     etc |
2014 |   3  |    9 |  880 |    2 |    7 |       etc |      etc |     etc |
2014 |  12  |    9 |  880 |    2 |    7 |       etc |      etc |     etc |
2015 |   5  |    9 |  880 |    2 |    7 |       etc |      etc |     etc |
2015 |   7  |    9 |  880 |    2 |    7 |       etc |      etc |     etc |
对于W、X、Y、Z的每个组合,我想插入表中没有出现的月份,它们介于第一个月和最后一个月之间

在本例中,对于组合W=1,X=5210,Y=1402,Z=2,我希望在2015/11和2016/02中有额外的行,其中采购、销售和退货为空。对于组合W=9,X=880,Y=2,Z=7,我希望在2014/4和2014/11、2015/01和2015/04、2016/06之间的几个月内增加行数

我希望我的解释是正确的。
提前感谢您提供的任何帮助。

在这种情况下,此过程相当繁琐,但很有可能。一种方法使用递归CTE。另一个使用数字表。我要用后者

这个想法是:

查找每组ID的年/月组合的最小值和最大值。为此,将使用公式year*12+month将值转换为自时间0起的月份。 生成一组数字。 为每个ID组合生成两个值之间的所有行。 对于每个生成的行,使用算术重新提取年份和月份。 使用“左联接”引入原始数据。 查询如下所示:

with n as (
      select row_number() over (order by (select null)) - 1 as n -- start at 0
      from master.spt_values
     ),
     minmax as (
      select w_id, x_id, y_id, z_id, min(yr*12 + mnth) as minyyyymm,
             max(yr*12 + mnth) as maxyyyymm
      from t
      group by w_id, x_id, y_id, z_id
     ),
     wxyz as (
      select minmax.*, minmax.minyyyymm + n.n,
             (minmax.minyyyymm + n.n) / 12 as yyyy,
             ((minmax.minyyyymm + n.n) % 12) + 1 as mm
      from minmax join
           n
           on minmax.minyyyymm + n.n <= minmax.maxyyyymm
     )
select wxyz.yyyy, wxyz.mm, wxyz.w_id, wxyz.x_id, wxyz.y_id, wxyz.z_id, 
       <columns from t here>
from wxyz left join
     t
     on wxyz.w_id = t.w_id and wxyz.x_id = t.x_id and wxyz.y_id = t.y_id and
        wxyz.z_id = t.z_id and wxyz.yyyy = t.yr and wxyz.mm = t.mnth;

在这种情况下,这个过程相当麻烦,但很有可能。一种方法使用递归CTE。另一个使用数字表。我要用后者

这个想法是:

查找每组ID的年/月组合的最小值和最大值。为此,将使用公式year*12+month将值转换为自时间0起的月份。 生成一组数字。 为每个ID组合生成两个值之间的所有行。 对于每个生成的行,使用算术重新提取年份和月份。 使用“左联接”引入原始数据。 查询如下所示:

with n as (
      select row_number() over (order by (select null)) - 1 as n -- start at 0
      from master.spt_values
     ),
     minmax as (
      select w_id, x_id, y_id, z_id, min(yr*12 + mnth) as minyyyymm,
             max(yr*12 + mnth) as maxyyyymm
      from t
      group by w_id, x_id, y_id, z_id
     ),
     wxyz as (
      select minmax.*, minmax.minyyyymm + n.n,
             (minmax.minyyyymm + n.n) / 12 as yyyy,
             ((minmax.minyyyymm + n.n) % 12) + 1 as mm
      from minmax join
           n
           on minmax.minyyyymm + n.n <= minmax.maxyyyymm
     )
select wxyz.yyyy, wxyz.mm, wxyz.w_id, wxyz.x_id, wxyz.y_id, wxyz.z_id, 
       <columns from t here>
from wxyz left join
     t
     on wxyz.w_id = t.w_id and wxyz.x_id = t.x_id and wxyz.y_id = t.y_id and
        wxyz.z_id = t.z_id and wxyz.yyyy = t.yr and wxyz.mm = t.mnth;

谢谢你的帮助

您的解决方案很有效,但我注意到它在性能方面不是很好,但同时我已经设法找到了解决问题的方法

DECLARE @start_date DATE, @end_date DATE;
SET @start_date = (SELECT MIN(EOMONTH(DATEFROMPARTS(Yr , Mnth, 1))) FROM Table_Input);
SET @end_date = (SELECT MAX(EOMONTH(DATEFROMPARTS(Yr , Mnth, 1))) FROM Table_Input);

DECLARE @tdates TABLE (Period DATE, Yr INT, Mnth INT);

WHILE @start_date <= @end_date
BEGIN
    INSERT INTO @tdates(PEriod, Yr, Mnth) VALUES(@start_date, YEAR(@start_date), MONTH(@start_date));
    SET @start_date = EOMONTH(DATEADD(mm,1,DATEFROMPARTS(YEAR(@start_date), MONTH(@start_date), 1)));
END

DECLARE @pks TABLE (W_ID NVARCHAR(50), X_ID NVARCHAR(50)
, Y_ID NVARCHAR(50), Z_ID NVARCHAR(50)
, PerMin DATE, PerMax DATE);

INSERT INTO @pks (W_ID, X_ID, Y_ID, Z_ID, PerMin, PerMax)
SELECT W_ID, X_ID, Y_ID, Z_ID
, MIN(EOMONTH(DATEFROMPARTS(Ano, Mes, 1))) AS PerMin
, MAX(EOMONTH(DATEFROMPARTS(Ano, Mes, 1))) AS PerMax
FROM Table1
GROUP BY W_ID, X_ID, Y_ID, Z_ID;


INSERT INTO Table_Output(W_ID, X_ID, Y_ID, Z_ID
, ComprasLiquidas, RTV, DevManuais, ComprasBrutas, Vendas, Stock, ReceitasComerciais)
SELECT TP.DB, TP.Ano, TP.Mes, TP.Supplier_Code, TP.Depart_Code, TP.BizUnit_Code
, TA.ComprasLiquidas, TA.RTV, TA.DevManuais, TA.ComprasBrutas, TA.Vendas, TA.Stock, TA.ReceitasComerciais
FROM 
(
SELECT W_ID, X_ID, Y_ID, Z_ID
FROM @tdatas CROSS JOIN @pks
WHERE Period BETWEEN PerMin And PerMax
) AS TP
LEFT JOIN Table_Input AS TA
ON TP.W_ID = TA.W_ID AND TP.X_ID = TA.X_ID AND TP.Y_ID = TA.Y_ID
AND TP.Z_ID = TA.Z_ID
AND TP.Yr = TA.Yr
AND TP.Mnth = TA.Mnth
ORDER BY TP.W_ID, TP.X_ID, TP.Y_ID, TP.Z_ID, TP.Yr, TP.Mnth;
我做了以下工作:

获取整个表的最小和最大日期-@start\u date和@end\u date变量; 创建一个辅助表,其中所有日期都在Min和Max-@tdates表之间; 获取W_ID、X_ID、Y_ID、Z_ID的所有组合以及该组合的最小和最大日期-@pks表; 在@tdates和@pks之间创建笛卡尔乘积,并在WHERE子句I中过滤组合的最小值和最大值之间的结果; 计算笛卡尔乘积表和输入数据表的左联接。
谢谢你的帮助

您的解决方案很有效,但我注意到它在性能方面不是很好,但同时我已经设法找到了解决问题的方法

DECLARE @start_date DATE, @end_date DATE;
SET @start_date = (SELECT MIN(EOMONTH(DATEFROMPARTS(Yr , Mnth, 1))) FROM Table_Input);
SET @end_date = (SELECT MAX(EOMONTH(DATEFROMPARTS(Yr , Mnth, 1))) FROM Table_Input);

DECLARE @tdates TABLE (Period DATE, Yr INT, Mnth INT);

WHILE @start_date <= @end_date
BEGIN
    INSERT INTO @tdates(PEriod, Yr, Mnth) VALUES(@start_date, YEAR(@start_date), MONTH(@start_date));
    SET @start_date = EOMONTH(DATEADD(mm,1,DATEFROMPARTS(YEAR(@start_date), MONTH(@start_date), 1)));
END

DECLARE @pks TABLE (W_ID NVARCHAR(50), X_ID NVARCHAR(50)
, Y_ID NVARCHAR(50), Z_ID NVARCHAR(50)
, PerMin DATE, PerMax DATE);

INSERT INTO @pks (W_ID, X_ID, Y_ID, Z_ID, PerMin, PerMax)
SELECT W_ID, X_ID, Y_ID, Z_ID
, MIN(EOMONTH(DATEFROMPARTS(Ano, Mes, 1))) AS PerMin
, MAX(EOMONTH(DATEFROMPARTS(Ano, Mes, 1))) AS PerMax
FROM Table1
GROUP BY W_ID, X_ID, Y_ID, Z_ID;


INSERT INTO Table_Output(W_ID, X_ID, Y_ID, Z_ID
, ComprasLiquidas, RTV, DevManuais, ComprasBrutas, Vendas, Stock, ReceitasComerciais)
SELECT TP.DB, TP.Ano, TP.Mes, TP.Supplier_Code, TP.Depart_Code, TP.BizUnit_Code
, TA.ComprasLiquidas, TA.RTV, TA.DevManuais, TA.ComprasBrutas, TA.Vendas, TA.Stock, TA.ReceitasComerciais
FROM 
(
SELECT W_ID, X_ID, Y_ID, Z_ID
FROM @tdatas CROSS JOIN @pks
WHERE Period BETWEEN PerMin And PerMax
) AS TP
LEFT JOIN Table_Input AS TA
ON TP.W_ID = TA.W_ID AND TP.X_ID = TA.X_ID AND TP.Y_ID = TA.Y_ID
AND TP.Z_ID = TA.Z_ID
AND TP.Yr = TA.Yr
AND TP.Mnth = TA.Mnth
ORDER BY TP.W_ID, TP.X_ID, TP.Y_ID, TP.Z_ID, TP.Yr, TP.Mnth;
我做了以下工作:

获取整个表的最小和最大日期-@start\u date和@end\u date变量; 创建一个辅助表,其中所有日期都在Min和Max-@tdates表之间; 获取W_ID、X_ID、Y_ID、Z_ID的所有组合以及该组合的最小和最大日期-@pks表; 在@tdates和@pks之间创建笛卡尔乘积,并在WHERE子句I中过滤组合的最小值和最大值之间的结果; 计算笛卡尔乘积表和输入数据表的左联接。 可能的重复可能的重复