【问题标题】:Insert multiple rows in a table based on ranges between multiple columns in another table根据另一个表中多列之间的范围在表中插入多行
【发布时间】:2019-01-04 20:33:34
【问题描述】:

我在名为TABEL1 的表中有四列YEAR1, MONTH1, YEAR2, MONTH2 和INT。我想为TABLE1 中的每一行在表TABLE2 中插入多行,其中YEAR 和MONTH 的值在YEAR 和MONTH 之间的TABLE1 列值。

Table1 是这样的:

ID   YEAR1   MONTH1   YEAR2   MONTH2   
------------------------------------
1    2010      11     2011      2
2    2012      10     2012      12

Table2 应该是这样的:

ID   YEAR   MONTH
-----------------
1    2010    11
1    2010    12
1    2011     1
1    2011     2
2    2012    10
2    2012    11
2    2012    12

我不知道我应该使用 while 循环还是只使用 select 语句。 我写了一个插入函数,但我发现我不能在用户定义的函数中使用 CRUD。

SELECT Calc(ID,YEAR1,YEAR2,MONTH1,MONTH2) FROM Table1

CREATE FUNCTION Calc(@ID INT, @YEAR1 INT, @YEAR2 INT, @PERIOD1 INT, @PERIOD2 INT)
RETURNS TINYINT
AS BEGIN
    DECLARE @ID INT
    DECLARE @YEAR INT
    DECLARE @MONTH INT

    SET @YEAR = @YEAR1
    SET @MONTH = @MONTH1
    WHILE (@YEAR <= @YEAR2)
        WHILE (@MONTH <= @MONTH2)
            INSERT INTO TABLE2 (ID,YEAR,MONTH)
                        VALUES (@ID,@YEAR,@MONTH)
            SET @MONTH = @MONTH + 1
        END
        SET @YEAR = @YEAR + 1
    END
    RETURN 1
END

更新:

MONTH 这里不完全是月份。它实际上是SEASON,其值为1,2,3,4。 另一个问题是YEAR 和MONTH 是从波斯日期而不是公历日期中提取的,并且波斯日期在数据库中保存为NVARCHAR。 所以,我似乎不能在这里使用DATE 函数。

【问题讨论】:

    标签: sql sql-server


    【解决方案1】:

    已经提到了很多不错的选择。我也会倾向于元素周期表和连接。一种方法可能是以下一种:

    USE TEMPDB
    
    CREATE TABLE #Y (YY INT)
    INSERT INTO #Y VALUES (2000), (2001), (2002), (2003), (2004),
                      (2005), (2006), (2007), (2008), (2009),
                      (2010), (2011), (2012), (2013), (2014),
                      (2015), (2016), (2017), (2018), (2019)
    
    CREATE Table #M (Mnth INT)
    INSERT INTO #M VALUES (1), (2), (3), (4), (5), (6), 
                      (7), (8), (9), (10), (11), (12)
    
    SELECT *, CASE WHEN Mnth < 10 THEN CAST (YY AS CHAR (4)) + '0' + CAST (Mnth AS CHAR (2)) 
            ELSE CAST (YY AS CHAR (4)) + CAST (Mnth AS CHAR (2)) END AS Combined   
    INTO #Period
    FROM #Y
    CROSS JOIN #M
    
    SELECT * FROM #Period 
    
    CREATE TABLE TABLE1 (ID INT, YEAR1 INT, MONTH1 INT, Combined1 CHAR (6), YEAR2 INT, MONTH2 INT, Combined2 CHAR (6))
    INSERT INTO TABLE1 VALUES (1, 2010, 11, '201011', 2011, 2, '201102'), (2, 2012, 10, '201210', 2012, 12, '201212')
    
    SELECT * FROM TABLE1
    
    SELECT T.ID, P.YY, P.Mnth
    FROM #Period AS P
    INNER JOIN TABLE1 AS T ON P.Combined BETWEEN Combined1 AND Combined2
    ORDER BY 1, 2
    
    --DROP TABLE #M, #Period, #Y, TABLE1
    

    【讨论】:

    • 这是我接受的答案,因为其他答案依赖于日期函数,这在我的情况下不起作用
    【解决方案2】:

    首先,创建一个 table-valued function,它根据传递的@ID 为您返回日期

    CREATE FUNCTION dbo.GetDates
    (
      @ID INT
    )
    RETURNS TABLE
    AS
    RETURN
    (
      WITH CTE AS
      (
      SELECT DATEFROMPARTS(YEAR1, MONTH1, 1) Dates
      FROM T
      WHERE ID = @ID
      UNION ALL
      SELECT DATEADD(Month, 1, Dates)
      FROM CTE
      WHERE DATEADD(Month, 1, Dates) < 
        (SELECT DATEFROMPARTS(YEAR2, MONTH2, 2) FROM T WHERE ID = @ID)
      )
      SELECT *
      FROM CTE
    )
    

    然后只需将其用作(使用CROSS APPLY 访问ID 列)

    SELECT T.ID,
           YEAR(Dates) [YEAR],
           MONTH(Dates) [MONTH]
    FROM T CROSS APPLY dbo.GetDates(ID) TT
    

    返回:

    +----+------+-------+
    | ID | YEAR | MONTH |
    +----+------+-------+
    |  1 | 2010 |    11 |
    |  1 | 2010 |    12 |
    |  1 | 2011 |     1 |
    |  1 | 2011 |     2 |
    |  2 | 2012 |    10 |
    |  2 | 2012 |    11 |
    |  2 | 2012 |    12 |
    +----+------+-------+
    

    Demo

    【讨论】:

    • 您在回答中使用了 DATE 函数。如果我想使用季节而不是月份怎么办?我的意思是我有 SEASON 列,其值为 1,2,3,4
    • 我想我给了你完整的功能,如果需要,你可以轻松地进行一些更改。
    【解决方案3】:

    如果您使用a Calendar table,查询将变得微不足道。日历表包含每个日期的一行,例如未来 100 年,其中包含年、月、日、一年中的某一天、周数以及您可能需要的任何其他字段。索引单个字段允许按年、月等非常快速地查找。

    一旦有了这样的表,您只需返回位于 From 和 To 值之间的不同的 year 和 month 日历值。如果范围值是日期,则可以这样写:

    select distinct id,Calendar.year,Calendar.Month
    from #table1 inner join DimDate as Calendar 
        on Calendar.Date between #table1.StartDate and #table1.EndDate
    order by Calendar.Year,Calendar.Month
    

    事情变得有点复杂,因为我们需要比较年份和月份:

    CREATE TABLE #Table1 (
        ID int,
        YEAR1 int,
        MONTH1 int,
        YEAR2 int,
        MONTH2 int   
    )
    INSERT INTO #Table1
        (ID, YEAR1, MONTH1, YEAR2, MONTH2)
    VALUES 
        (1, 2010, 11, 2011, 2),
        (2, 2012, 10, 2012, 12)
    
    select distinct id,Calendar.year,Calendar.Month
    from #table1 inner join DimDate as Calendar on 
        (#table1.YEAR1=Calendar.Year and #Table1.Month1<=Calendar.Month 
                                     and (#table1.YEAR2>Calendar.Year or #Table1.Month2>=Calendar.Month)
        ) 
        or  
        (#table1.YEAR1<Calendar.Year and (#table1.YEAR2>Calendar.Year 
                                           or (#table1.YEAR2=Calendar.Year 
                                               and #Table1.Month2>=Calendar.Month)))    
    order by Calendar.Year,Calendar.Month
    

    如果源表和日历表具有年-月列,例如 YYYYMM 格式,则查询可以大大简化。这可能是一个实际的列,也可能是一个被索引覆盖的计算并持久化的列:

    CREATE TABLE #Table1 (
        ID int,
        YEAR1 int,
        MONTH1 int,
        YEAR2 int,
        MONTH2 int ,
        YearMonth1 AS YEAR1*100+Month1 persisted,
        YearMonth2 as Year2*100+Month2 persisted
    )
    
    create index IX_Table1_YearMonth1 on #Table1 (YearMonth1) Include (Year1,Month1)
    create index IX_Table1_YearMonth2 on #Table1 (YearMonth2) include (Year2,Month2)
    

    之后,查询就变成了简单的:

    select distinct id,Calendar.year,Calendar.Month
    from #table1 inner join DimDate as Calendar on 
        Calendar.YearMonth between #table1.YearMonth1 and #Table1.YearMonth2
    order by Calendar.Year,Calendar.Month
    

    执行计划简单快捷:

    【讨论】:

      【解决方案4】:

      你能试试这个查询吗

      DECLARE @minYear  VARCHAR(10), 
              @minMonth VARCHAR(10), 
              @maxYear  VARCHAR(10), 
              @maxMonth VARCHAR(10) 
      
      SELECT * 
      INTO   #table1 
      FROM   (SELECT 1    id, 
                     2010 year1, 
                     11   month1, 
                     2011 year2, 
                     2    month2 
              UNION ALL 
              SELECT 2    id, 
                     2012 year1, 
                     10   month1, 
                     2012 year2, 
                     12   month2) Table1 
      
      SELECT @minYear = Min(year1), 
             @minMonth = Min(month1), 
             @maxYear = Max(year2), 
             @maxMonth = Max(month2) 
      FROM   #table1; 
      
      WITH cte 
           AS (SELECT CONVERT(DATE, @minYear + '-' + @minMonth + '-01') AS dt 
               UNION ALL 
               SELECT Dateadd (month, 1, dt) dt 
               FROM   cte 
               WHERE  dt < CONVERT(DATE, @maxYear + '-' + @maxMonth + '-01')) 
      SELECT id, 
             Year(dt)[year], 
             Month(dt) [month] 
      FROM   #table1 Table1 
             INNER JOIN cte 
                     ON cte.dt BETWEEN CONVERT(DATE, CONVERT (VARCHAR, year1) + '-' 
                                                     + CONVERT(VARCHAR, month1) + 
                                                     '-01') AND 
                                       CONVERT( 
                                                  DATE, 
                                       CONVERT(VARCHAR, year2) + 
                                       '-' 
                                       + CONVERT(VARCHAR, month2) + 
                                       '-01') 
      

      【讨论】:

        【解决方案5】:

        你可以完美使用UNPIVOT,这里是我的提案解决方案

        DECLARE @Table1 AS TABLE
        (
          ID INT,
          YEAR1 INT,
          MONTH1 INT,
          YEAR2 INT,
          MONTH2 INT
        )
        
        INSERT INTO @Table1  VALUES (1,2010,11,2011,2),(2,2012,10,2012,12)
        
        
        
        SELECT ID, [YEAR],[MONTH]
        FROM
        (
          SELECT ID, YEAR1,YEAR2,MONTH1,MONTH2
          FROM @Table1
        ) AS t
        UNPIVOT 
        (
          [YEAR] FOR YEARS IN (YEAR1, YEAR2)
        ) AS up
        UNPIVOT 
          (
            [MONTH] FOR MONTHS IN (MONTH1,MONTH2)
          ) AS um
        

        【讨论】:

          【解决方案6】:

          一种可能的方法是定义一个表值函数,然后使用这个函数:

          功能:

          CREATE FUNCTION [dbo].[udf_GetMonths] (@year1 int, @month1 int, @year2 int, @month2 int)
          RETURNS TABLE
          AS
          RETURN
          (
              WITH Months AS
              (
                  SELECT DATEFROMPARTS(@year1, @month1, 1) AS SomeDate
                  UNION ALL
                  SELECT DATEADD(month, 1, SomeDate) AS SomeDate
                  FROM Months 
                  WHERE 
                      (DATEPART(year, SomeDate) < @year2) OR
                      (DATEPART(month, SomeDate) < @month2)
              )
              SELECT YEAR(SomeDate) AS [YEAR], MONTH(SomeDate) AS [MONTH]
              FROM Months
          )
          

          INSERT 语句:

          -- Table 1
          CREATE TABLE #Table1 (
              ID int,
              YEAR1 int,
              MONTH1 int,
              YEAR2 int,
              MONTH2 int   
          )
          INSERT INTO #Table1
              (ID, YEAR1, MONTH1, YEAR2, MONTH2)
          VALUES 
              (1, 2010, 11, 2011, 2),
              (2, 2012, 10, 2012, 12)
          
          -- Table 2
          CREATE TABLE #Table2 (
              ID int,
              [YEAR] int,
              [MONTH] int
          )
          
          -- Insert
          INSERT INTO #Table2 (ID, [YEAR], [MONTH])
          SELECT t.ID, f.[YEAR], f.[MONTH]
          FROM #Table1 t
          CROSS APPLY (SELECT * FROM dbo.udf_GetMonths(t.YEAR1, t.MONTH1, t.YEAR2, t.MONTH2)) f
          OPTION (MAXRECURSION 0)
          

          插入的行:

          ID  YEAR    MONTH
          1   2010    11
          1   2010    12
          1   2011    1
          1   2011    2
          2   2012    10
          2   2012    11
          2   2012    12
          

          【讨论】:

            猜你喜欢
            • 2013-10-08
            • 2023-03-09
            • 2021-09-06
            • 1970-01-01
            • 2013-10-08
            • 1970-01-01
            • 1970-01-01
            • 2021-07-14
            • 1970-01-01
            相关资源
            最近更新 更多