2016-06-08 84 views
2

我有值SQL Server 2008的更新表和开关值查询优化

ID   Son  Father 
----------- ---------- ---------- 
1   Mark  Gerard 
2   Gerard  Ivan 
3   Leo  Samuel 
4   Samuel  Johan 
5   Ivan  Carles 

我需要改变的表像这样的表:

ID   Son  Father 
----------- ---------- ---------- 
1   Mark  Carles 
2   Gerard  Carles 
3   Leo  Johan 
4   Samuel  Johan 
5   Ivan  Carles 

的目标是找到一个主要'Father'和用此值更新所有'Son'记录。主要'Father'可以不同。

我的代码是未来:

DECLARE @CNT INT 
DECLARE @CH_1 NVARCHAR(10) 
DECLARE @CH_2 NVARCHAR(10) 

CREATE TABLE #PPL (ID INT, Son NVARCHAR(10), Father NVARCHAR(10)) 

INSERT INTO #PPL VALUES (1, 'Mark', 'Gerard') 
INSERT INTO #PPL VALUES (2, 'Gerard', 'Ivan') 
INSERT INTO #PPL VALUES (3, 'Leo', 'Samuel') 
INSERT INTO #PPL VALUES (4, 'Samuel', 'Johan') 
INSERT INTO #PPL VALUES (5, 'Ivan', 'Carles') 

SET @I = 1 
SET @CNT = (SELECT COUNT(ID) FROM #PPL) 

WHILE @I <= @CNT 
BEGIN 
    SET @J = 1 

     WHILE @J <= @CNT 
     BEGIN 
      SET @CH_1 = (SELECT Son FROM #PPL WHERE ID = @J) 
      SET @CH_2 = (SELECT Father FROM #PPL WHERE ID = @J) 
      UPDATE #PPL SET Father = @CH_2 WHERE Father = @CH_1 
      SET @J = @J + 1 
     END; 

    SET @I = @I + 1 
END; 

SELECT * FROM #PPL 

DROP TABLE #PPL 

此代码工作正确的,但对于低数量的记录。这个代码如何优化?

谢谢!

+1

请勿使用LOOP。改用递归CTE。 – xQbert

+0

作为一个例子:http://stackoverflow.com/questions/14274942/sql-server-cte-and-recursion-example – xQbert

+0

@xQbert:你怎么定义这里的根节点 – TheGameiswar

回答

1

下面是你如何使用递归CTE来做到这一点。

CREATE TABLE #PPL (ID INT, Son NVARCHAR(10), Father NVARCHAR(10)) 

INSERT INTO #PPL VALUES (1, 'Mark', 'Gerard') 
INSERT INTO #PPL VALUES (2, 'Gerard', 'Ivan') 
INSERT INTO #PPL VALUES (3, 'Leo', 'Samuel') 
INSERT INTO #PPL VALUES (4, 'Samuel', 'Johan') 
INSERT INTO #PPL VALUES (5, 'Ivan', 'Carles') 

;WITH CTE_FamilyGenealogy 
AS 
(
    SELECT ID 
      ,Son 
      ,Father 
      ,1 AS [Level] 
    FROM #PPL Ancor 
    UNION ALL 
    SELECT CTE_FamilyGenealogy.ID 
      ,CTE_FamilyGenealogy.Son 
      ,Fathers.Father AS Father 
      ,CTE_FamilyGenealogy.[Level] + 1 AS [Level] 
    FROM #PPL Fathers 
    INNER JOIN CTE_FamilyGenealogy ON CTE_FamilyGenealogy.Father = Fathers.Son 
), 
CTE_MajorFathers 
AS 
(
    SELECT ID 
      ,Son 
      ,Father 
      ,ROW_NUMBER() OVER (PARTITION BY Son ORDER BY [Level] DESC) AS RowRank 
    FROM CTE_FamilyGenealogy 
) 
SELECT ID 
     ,Son 
     ,Father 
FROM CTE_MajorFathers 
WHERE RowRank = 1 
ORDER BY ID 

的递归CTE CTE_FamilyGenealogy找到所有的父子组合,并确定家谱中的水平。 CTE使用ROW_NUMBER根据FamilyGenealogy中的等级对可能的组合进行排序以确定主要父亲。

0

请尝试使用基于递归的方法(请参阅递归公用表表达式)和HIERARCHYID(SQL2008 +)数据类型。基本思想是为每一行构建一个从“第一个”父亲开始的层次结构值:-)并以“last”子结束:-)。例如:对于第一行(1, 'Mark', 'Gerard')该节点/家族树是/ 5/2/1 /其中/ 5 /是“第一”父亲;-)和/ 1 /是“最后”的儿子。接下来,将这些值转换为hiearchyid值,并使用GetLevelGetAncestor方法计算“第一个”父亲:Father1ID:Johan或Carles。

IF OBJECT_ID('tempdb.dbo.#Results') IS NOT NULL 
BEGIN 
    DROP TABLE #Results; 
END 
CREATE TABLE #Results (ID INT NOT NULL PRIMARY KEY, Father1ID INT); 

WITH CteRec 
AS (
    -- It returns Father only rows 
    SELECT l1.ID, l1.Son, l1.Father, CONVERT(VARCHAR(900), '/'+LTRIM(l1.ID)+'/') AS Node -- FamilyTree 
    FROM #PPL AS l1 -- First level 
    WHERE NOT EXISTS(SELECT * FROM #PPL p WHERE p.Son = l1.Father) 
    UNION ALL 
    -- It returns Son only and Son-Father rows 
    SELECT ln.ID, ln.Son, ln.Father, CONVERT(VARCHAR(900), prt.Node+LTRIM(ln.ID)+'/') AS Node -- FamilyTree 
    FROM #PPL AS ln -- Next level 
    JOIN CteRec AS prt ON prt.Son = ln.Father 
) 
INSERT #Results (ID, Father1ID) 
SELECT ID, 
     Father1ID = CONVERT(INT,REPLACE(CONVERT(HIERARCHYID, Node).GetAncestor(CONVERT(HIERARCHYID, Node).GetLevel()-1).ToString(),'/','')) 
FROM CteRec; 

SELECT p.*, r.Father1ID, rp.Father AS Father1Name 
FROM #PPL p 
INNER JOIN #Results r ON p.ID = r.ID 
INNER JOIN #PPL rp ON r.Father1ID = rp.ID 
-- Also you ca use #Result with UPDATE statement but I would store this values within new column Father1 
+0

如果有什么不清楚的请问。 –

0

递归CTE的是高估=)

这种简单的方法将通过一样快(在正常数据运行),永远不会抱怨最大递归和易于阅读。我能看到的唯一缺点是,当数据被破坏时,它可能会进入一个永恒的循环。

CREATE TABLE #PPL (ID INT, Son NVARCHAR(10), Father NVARCHAR(10)) 

INSERT INTO #PPL VALUES (1, 'Mark', 'Gerard') 
INSERT INTO #PPL VALUES (2, 'Gerard', 'Ivan') 
INSERT INTO #PPL VALUES (3, 'Leo', 'Samuel') 
INSERT INTO #PPL VALUES (4, 'Samuel', 'Johan') 
INSERT INTO #PPL VALUES (5, 'Ivan', 'Carles') 

DECLARE @rowcount int = -1 
WHILE @rowcount <> 0 
    BEGIN 
     UPDATE upd 
      SET Father = new.Father 
     FROM #PPL upd 
     JOIN #PPL new 
      ON new.Son = upd.Father 
     WHERE upd.Father <> new.Father 

     SELECT @rowcount = @@ROWCOUNT 
    END 

SELECT * FROM #PPL 

PS:在大型数据集上运行时,可能有助于在子列上创建索引。