根据另一个表中多个列之间的范围在表中插入多行

时间:2019-01-04 09:46:40

标签: sql sql-server

我在名为YEAR1, MONTH1, YEAR2, MONTH2的表中有INT的四列TABEL1。我想为TABLE2中的每一行在表TABLE1中插入多行,这些行的列值介于YEARMONTH之间,并且YEARMONTHTABLE1中。

表1类似于:

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。 另一个问题是YEARMONTH是从波斯日期中提取的,而不是公历日期,而波斯日期被另存为NVARCHAR在数据库中。 因此,看来我不能在这里使用DATE函数。

6 个答案:

答案 0 :(得分:1)

首先,创建一个 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

答案 1 :(得分: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 :(得分:1)

如果使用a Calendar table,查询将变得很简单。日历表包含每个日期一行,例如未来100年,其中分别包含年,月,日,年中的某天,周号以及其他可能需要的字段。索引各个字段可以按年,月等进行快速查找。

一旦有了这样一个表,您只需要返回位于 private long randomAndroidColor; randomAndroidColor = getIntent().getLongExtra(EXTRA_COLOR, 0L); getSupportActionBar().setBackgroundDrawable( new ColorDrawable(Color.parseColor(String.valueOf(randomAndroidColor))) ); year值之间的不同的monthFrom日历值。如果范围值是日期,则可以输入:

To

由于我们需要比较年月,因此事情变得有些复杂:

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

如果源表和日历表具有“年月”列,例如YYYYMM格式,则可以大大简化查询。这可能是实际的列,也可能是索引所涵盖的已计算和持久的列:

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

之后,查询变得简单:

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)

执行计划简单快捷:

enter image description here

答案 3 :(得分:0)

您可以尝试查询

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') 

答案 4 :(得分:0)

您可以完美地使用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

答案 5 :(得分:0)

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

功能:

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