【问题标题】:SQL Server - Insert lines with null values when month doesn't existSQL Server - 当月份不存在时插入具有空值的行
【发布时间】:2016-05-08 23:32:26
【问题描述】:

我有一张这样的桌子:

 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 增加额外的行,其中 Purchases、Sales 和 Returns 是无效的。对于组合(W=9、X=880、Y=2、Z=7),我希望在 2014/4 和 2014/11、2015/01 和 2015/04、2016/06 之间的月份有额外的行。

我希望我已经正确地解释了自己。 提前感谢您提供的任何帮助。

【问题讨论】:

标签: sql sql-server sql-insert


【解决方案1】:

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

想法是:

  • 查找每组 id 的年/月组合的最小值和最大值。为此,将使用公式 year*12 + month 将值转换为从时间 0 开始的月份。
  • 生成一堆数字。
  • 为每个 id 组合生成两个值之间的所有行。
  • 对于每个生成的行,使用算术重新提取年份和月份。
  • 使用left join引入原始数据。

查询看起来像:

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;

【讨论】:

    【解决方案2】:

    感谢您的帮助。

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

    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;
    

    我执行以下操作:

    1. 获取整个表的最小和最大日期 - @start_date 和 @end_date 变量;
    2. 创建一个包含 Min 和 Max 之间所有日期的辅助表 - @tdates 表;
    3. 获取 (W_ID, X_ID, Y_ID, Z_ID) 的所有组合以及该组合的最小和最大日期 - @pks 表;
    4. 在@tdates 和@pks 之间创建笛卡尔积,并在WHERE 子句中过滤组合的Min 和Max 之间的结果;
    5. 计算笛卡尔积表与输入数据表的 LEFT JOIN。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2021-09-03
      • 2021-11-20
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-08-07
      相关资源
      最近更新 更多