2011-04-13 83 views
2

我有一个表叫EmployeesSQL Server 2008的:复杂的插入

BeginYear | EndYear | Name 
1974   1983   Robert 

对于每个记录在Employees我每年都需要插入一个新的表名为EmployeeYears

所以:

For Each Record in Employees 
    For i as int = Begin Year to End year 
     INSERT i, Name into EmployeeYears 

任何方式在SQL中做到这一点...可能与游标?

回答

3

它的要点是使用WITH语句创建所有记录并使用它们插入到最终表中。

;WITH q AS (
    SELECT Year = BeginYear 
     , Name 
    FROM Employees 
    UNION ALL 
    SELECT q.Year + 1 
     , q.Name 
    FROM q 
     INNER JOIN Employees e ON e.Name = q.Name 
            AND e.EndYear > q.Year 
) 
INSERT INTO EmployeeYears 
SELECT * FROM q 
OPTION(MAXRECURSION 0) 

TESTDATA

CREATE TABLE Employees (BeginYear INTEGER, EndYear INTEGER, Name VARCHAR(32)) 
CREATE TABLE EmployeeYears (Year INTEGER, Name VARCHAR(32)) 

INSERT INTO Employees 
    SELECT 1974, 1976, 'Robert' 
    UNION ALL SELECT 1972, 1975, 'Lieven' 

结果

SELECT * 
FROM EmployeeYears 
ORDER BY Name, Year 

1972 Lieven 
1973 Lieven 
1974 Lieven 
1975 Lieven 
1974 Robert 
1975 Robert 
1976 Robert 
+0

然而,这只会得到明年吗?如果有多年呢? – William 2011-04-13 19:14:51

+0

@William,我刚刚使用SSMS和afaik检查了它,它可以满足您的需求。 – 2011-04-13 19:18:43

+0

这是因为递归,但您很可能会很快达到递归限制。 – 2011-04-13 19:19:29

3

如果您有一个数字表,您可以加入它以获取单个年份记录并避免使用游标。我只是用1965年到968年的数字对数字表进行了统计,但是一个真实数字表(它也不会是一个临时表,如下图所示,但存在于您的模式中)可能会有数百万条记录对于很多比较是有用的。

create table #Numbers (Number int) 
insert into #Numbers 
select 1965 
union 
select 1966 
union 
select 1967 
union 
select 1968 

create table #employees (name varchar (50), beginyear int, endyear int) 
insert into #employees 
select 'Dick', 1966, 1968 
union all 
select 'harry', 1965, 1967 
union all 
select 'tom', 1955, 1966 

insert into EmployeeYears (Name, [Year]) 
select Name, n.number 
from #Employees e 
join #Numbers n on n.number between e.beginyear and e.endyear 
order by name 
+0

能否请你解释一下此外,我不相信我有一个数字表。 – William 2011-04-13 19:02:47

+0

的确如此,但这就像说......“如果你有结果,你会得到结果。”......他要求的* IS *是一个“数字表”。 – 2011-04-13 19:09:01

1

是的,你确实有做一个循环......我宁愿不使用游标,但这种情况下,八九不离十是有道理的......总之,这里的代码只是一个直接回路告诉你,你可以在SQL中做这样的代码:

DECLARE @Employee VARCHAR(100) 
DECLARE @BeginYear INT, @EndYear INT, @i INT 

SET @Employee = '' 

WHILE (1=1) 
BEGIN 
    SET @Employee = (SELECT TOP 1 Name FROM Employees ORDER BY Name WHERE Name > @Employee) 

    IF @Employee IS NULL BREAK 

    SELECT @BeginYear = BeginYear, @EndYear = EndYear FROM Employees WHERE Name = @Employee 

    SET @i = @BeginYear 

    WHILE (@i <= @EndYear) 
    BEGIN 
     INSERT INTO EmployeeYears (Year, Name) VALUES (@i, @Employee) 
     SET @i = @i + 1 
    END 
END 
+0

这似乎是可行的,但是做一个选择最上面的一个w /该表中的where和order子句最终需要很长时间 – William 2011-04-13 19:11:43

+0

不应该将'YEAR'增加到某个地方吗? – JNK 2011-04-13 19:12:57

+0

是的,我忘了......关于“需要很长时间”,你可以先把它放在一个已经排序的临时表中。基本上,你有一个设计问题,现在你试图解决它,解决方案不会很漂亮。 – 2011-04-13 19:15:58

0

您可以使用递归过程。 Llike的一个波纹管:

CREATE Procedure InsertYear 
    @Name .... 
    @BeginYear ... 
    @EndYear ... 
AS 
{ 
    INSERT INTO EmployeeYears VALUES(@BeginYear, @Name); 
    SET @BeginYear = @BeginYear + 1 
    IF @BeginYear < @EndYear 
    BEGIN 
     InsertYear(@Name, @BeginYear, @EndYear) 
    END 

    RETURN 
} 
+0

这可以很好地作为一个ud函数,但是,我仍然需要为雇员表中的每条记录实现它 – William 2011-04-13 19:18:17

1

您可以使用递归CTE:

;WITH CTE AS 
(
    SELECT BeginYear, EndYear, Name 
    FROM Employees 
    UNION ALL 
    SELECT BeginYear+1, EndYear, Name 
    FROM CTE 
    WHERE BeginYear < EndYear 
) 
INSERT INTO EmployeeYears (Year, Name) 
SELECT BeginYear, Name 
FROM CTE 
ORDER BY Name, BeginYear 
OPTION(MAXRECURSION 0) 
+0

什么是#Temp1 Here? – William 2011-04-13 19:24:53

+0

类型在递归查询“CTE”的列“BeginYear”中的锚和递归部分之间不匹配。 – William 2011-04-13 19:28:39

+0

@William - 对不起,#Temp1从我的测试中复制而来,更新了我的答案,谢谢。我试过上面的代码,它工作得很好,假设BeginYear是一个'INT' – Lamak 2011-04-13 19:35:47

0

你可以这样做,但如果开始它会失败或最终超过2047

INSERT INTO EmployeeYears (number, name) 
SELECT v.number, e.name 
FROM 
    Employees e 
    INNER JOIN master..spt_values v on 
    v.number between beginYear and endYear