计算某些字段后如何从另一个表创建新表

Posted

技术标签:

【中文标题】计算某些字段后如何从另一个表创建新表【英文标题】:How can create a new table from another after Calculate Some Field 【发布时间】:2019-11-02 09:01:05 【问题描述】:

我有一个名为 WorkSpace 的表,有 4 列。

此表中的每条记录通过Parent_id 列引用它们的父记录。

您应该继续这个循环,直到您到达主要父级以及View_Parent_id 列中指定的所有这些步骤。

主父编号的ID由1指定。

所以现在我想创建一个NEW_WorkSpace 表并将View_Parent_id 列中的每个值分隔到一个单独的列中。

View_Parent_id 列最终值为 15,因此我们需要新表中的 15 列 (Level_1...Level_15)。

我在查询中使用了循环,但速度很慢。

WorkSpace 表有 150 万行。

WorkSpace表:

+-----+-------+-----------+------------------+
| W_ID| Title | Parent_id | View_Parent_id   |
+-----+-------+-----------+------------------+
| 1   |   AAA |    null   | null             |
| 2   |   BV  |    1      | 1                |
| 3   |   CX  |    2      | 1+2              |
| 4   |   DSO |    2      | 1+2              |
| 5   |   ER  |    3      | 1+2+3            |
| 6   |   ER  |    5      | 1+2+3+5          |
| ... |  ...  |    ...    | ...              |
| 1000|   MNV |    1      | 1                |
| 1001|   SF  |    1000   | 1+1000           |
| 1002|   EDD |    1000   | 1+1000           |
| 1003|   YSG |    1001   | 1+1000+1001      |
| 1004|   RPO |    1003   | 1+1000+1001+1003 |
+-----+-------+-----------+------------------+

NEW_WorkSpace表:

+-----+-------+-----------+---------+---------+---------+-----+----------+
| ID  |  W_id | Parent_id | Level_1 | Level_2 | Level_3 | ... | Level_15 |
+-----+-------+-----------+---------+-------- +---------+-----+----------+
| 100 |   1   |    null   | AAA     |         |         | ... |          |
| 101 |   2   |    1      | AAA     |  BV     |         | ... |          |
| 102 |   3   |    2      | AAA     |  BV     |         | ... |          |
| 103 |   4   |    2      | AAA     |  BV     |  CX     | ... |          |
| 104 |   5   |    3      | AAA     |  BV     |  CX     | ... |          |
| ... |   ... |    ...    | ...     |  ...    |  ...    | ... |  ...     |
+-----+-------+-----------+---------+---------+---------+-----+----------+

我的代码:

BEGIN    
    DECLARE @W_ID decimal(20, 0);
    DECLARE @parent_id decimal(20, 0);

    DECLARE @Level1 nvarchar(MAX);
    DECLARE @Level2 nvarchar(MAX);
    DECLARE @Level3 nvarchar(MAX);
    DECLARE @Level4 nvarchar(MAX);
    DECLARE @Level5 nvarchar(MAX);
    DECLARE @Level6 nvarchar(MAX);
    DECLARE @Level7 nvarchar(MAX);
    DECLARE @Level8 nvarchar(MAX);
    DECLARE @Level9 nvarchar(MAX);
    DECLARE @Level10 nvarchar(MAX);
    DECLARE @Level11 nvarchar(MAX);
    DECLARE @Level12 nvarchar(MAX);
    DECLARE @Level13 nvarchar(MAX);
    DECLARE @Level14 nvarchar(MAX);
    DECLARE @Level15 nvarchar(MAX);
    DECLARE @titles_tmp nvarchar(MAX);

    DECLARE @cont_spilit_tittle int;
    DECLARE @parent_titles_tmp nvarchar(MAX);
    DECLARE @cont_tmp int;
    DECLARE @cont int;

    SELECT @cont = COUNT(*) FROM dbo.WorkSpace ;
    SET @cont_tmp = 0;

    WHILE (@cont_tmp < @cont)
    BEGIN  
        SET @W_ID = (SELECT dbo.WorkSpace.W_ID FROM dbo.WorkSpace  
                     ORDER BY W_ID ASC OFFSET @cont_tmp ROWS FETCH NEXT 1 ROWS ONLY)
        SET @parent_id = (SELECT dbo.WorkSpace.parent_id FROM dbo.WorkSpace 
                          ORDER BY W_ID ASC OFFSET @cont_tmp ROWS FETCH NEXT 1 ROWS ONLY)
        SET @titles_tmp = (SELECT dbo.WorkSpace.title FROM dbo.WorkSpace 
                           ORDER BY W_ID ASC OFFSET @cont_tmp ROWS FETCH NEXT 1 ROWS ONLY)
        SET @parent_titles_tmp = (SELECT dbo.WorkSpace.parent_titles FROM dbo.WorkSpace 
                                  ORDER BY W_ID ASC OFFSET @cont_tmp ROWS FETCH NEXT 1 ROWS ONLY)

        IF OBJECT_ID('tempdb..#MyTempTable') IS NOT NULL 
             DROP TABLE #MyTempTable

        SELECT IDENTITY(INT, 1, 1) AS 'RowID', * 
        INTO #MyTempTable 
        FROM StringSplitXML(@parent_titles_tmp, '+')

        INSERT INTO #MyTempTable 
        VALUES (@titles_tmp)

        SET @cont_spilit_tittle = (SELECT COUNT(*) FROM #MyTempTable)

        IF(@cont_spilit_tittle < 0)
            SET @cont_spilit_tittle = 1

        WHILE (@cont_spilit_tittle < 15)
        BEGIN
            INSERT INTO #MyTempTable VALUES ('')
            SET @cont_spilit_tittle = CAST(@cont_spilit_tittle AS INT) + 1
        END

        SET @Level1 = (SELECT Value FROM #MyTempTable 
                       ORDER BY RowID ASC OFFSET 0 ROWS FETCH NEXT 1 ROWS ONLY)
        SET @Level2 = (SELECT Value FROM #MyTempTable 
                       ORDER BY RowID ASC OFFSET 1 ROWS FETCH NEXT 1 ROWS ONLY)
        SET @Level3 = (SELECT Value FROM #MyTempTable 
                       ORDER BY RowID ASC OFFSET 2 ROWS FETCH NEXT 1 ROWS ONLY)
        SET @Level4 = (SELECT Value FROM #MyTempTable 
                       ORDER BY RowID ASC OFFSET 3 ROWS FETCH NEXT 1 ROWS ONLY)
        SET @Level5 = (SELECT Value FROM #MyTempTable 
                       ORDER BY RowID ASC OFFSET 4 ROWS FETCH NEXT 1 ROWS ONLY)
        SET @Level6 = (SELECT Value FROM #MyTempTable 
                       ORDER BY RowID ASC OFFSET 5 ROWS FETCH NEXT 1 ROWS ONLY)
        SET @Level7 = (SELECT Value FROM #MyTempTable 
                       ORDER BY RowID ASC OFFSET 6 ROWS FETCH NEXT 1 ROWS ONLY)
        SET @Level8 = (SELECT Value FROM #MyTempTable 
                       ORDER BY RowID ASC OFFSET 7 ROWS FETCH NEXT 1 ROWS ONLY)
        SET @Level9 = (SELECT Value FROM #MyTempTable 
                       ORDER BY RowID ASC OFFSET 8 ROWS FETCH NEXT 1 ROWS ONLY)
        SET @Level10 = (SELECT Value FROM #MyTempTable 
                        ORDER BY RowID ASC OFFSET 9 ROWS FETCH NEXT 1 ROWS ONLY)
        SET @Level11 = (SELECT Value FROM #MyTempTable 
                        ORDER BY RowID ASC OFFSET 10 ROWS FETCH NEXT 1 ROWS ONLY)
        SET @Level12 = (SELECT Value FROM #MyTempTable 
                        ORDER BY RowID ASC OFFSET 11 ROWS FETCH NEXT 1 ROWS ONLY)
        SET @Level13 = (SELECT Value FROM #MyTempTable 
                        ORDER BY RowID ASC OFFSET 12 ROWS FETCH NEXT 1 ROWS ONLY)
        SET @Level14 = (SELECT Value FROM #MyTempTable 
                        ORDER BY RowID ASC OFFSET 13 ROWS FETCH NEXT 1 ROWS ONLY)
        SET @Level15 = (SELECT Value FROM #MyTempTable 
                        ORDER BY RowID ASC OFFSET 14 ROWS FETCH NEXT 1 ROWS ONLY)


        INSERT INTO [].[dbo].[NEW_WorkSpace]
           ([W_ID], [parent_id],
            [Level1], [Level2], [Level3], [Level4], [Level5],
            [Level6], [Level7], [Level8], [Level9], [Level10],
            [Level11], [Level12], [Level13], [Level14], [Level15])
        VALUES (@W_ID, @parent_id,
                @Level1, @Level2, @Level3, @Level4, @Level5,
                @Level6, @Level7, @Level8, @Level9, @Level10,
                @Level11, @Level12, @Level13, @Level14, @Level15) 

        SET @cont_tmp = CAST(@cont_tmp AS INT) + 1
    END  

    RETURN
END

感谢您的帮助。

【问题讨论】:

请向我们展示您的尝试 @squirrel 代码已添加。 正如您提到的 pivote,但在查看您的代码和输出时没有 pivote 概念?? @nits-patel 我只是猜测它可能是枢轴的解决方案,但我没有自己写 是的,因为WHILE 是在 SQL 中做任何事情的最糟糕的方法之一;它不是一种编程语言。查询语言中的WHILE 本来就很慢。 【参考方案1】:

我会在view_parent_id 上使用字符串操作来解决这个问题。然后聚合最终结果:

with cte as (
      select w_id, parent_id, view_parent_id,
             0 as lev, convert(varchar(max), concat(view_parent_id, '+', w_id, '+')) as parents 
      from t
      union all
      select w_id, parent_id, view_parent_id,
             1 + lev, 
             convert(int, left(parents, charindex('+', parents) - 1)),
             stuff(parents, 1, charindex('+', parents), '')
      from cte
      where parents <> ''
     )
select w_id, parent_id, view_parent_id,
       max(case when lev = 1 then parent_title end) as title_1,
       max(case when lev = 2 then parent_title end) as title_2,
       max(case when lev = 3 then parent_title end) as title_3,
       max(case when lev = 4 then parent_title end) as title_4,
       max(case when lev = 5 then parent_title end) as title_5
from (select cte.*, t.title as parent_title, count(*) over (partition by cte.w_id) as cnt
      from cte join
           t
           on t.w_id = cte.parent
      where lev > 0
     ) cte
group by w_id, parent_id, view_parent_id;

Here 是一个 dbfiddle。

至于处理。查询中最昂贵的部分可能是递归 CTE 之后的聚合。递归部分没有做任何连接,所以它应该相当快(字符串操作可能很慢)。

获取标签的连接使用了正确的类型,因此可以使用w_id 上的索引。

【讨论】:

您好 Linoff 先生,我应该一如既往地说您的回答对我有所帮助,但我认为您忘记了我的问题的一部分。实际上答案是正确的而且太快了,但是您错过了每条记录的最后一级。 @JavadAbedi 。 . .这很容易通过将当前的w_id 添加到父母列表中来解决。但是,该列似乎应该只有包含当前节点的完整路径。无论如何,我调整了答案,您可以在 dbfiddle. 中看到它 是的,没错,我将 w_id 添加到父列表中。这个答案解决了我的问题。非常感谢 Linoff 先生。【参考方案2】:

好吧,这个丑陋的假设您最多有 15 个级别,而您的预期结果是错误的;正如我所拥有的W_ID 4 对于Level_3 的值为'DSO',但您的预期结果为CX,即使该行没有以任何方式链接到CX

USE Sandbox;
GO

CREATE TABLE dbo.YourTable (W_ID int NOT NULL,
                            Title varchar(3) NOT NULL,
                            Parent_id int NULL,
                            View_Parent_id varchar(100) NULL);
GO

INSERT INTO dbo.YourTable
VALUES (1,'AAA',NULL,NULL),
       (2,'BV',1,'1'),
       (3,'CX',2,'1+2'),
       (4,'DSO',2,'1+2'),
       (5,'ER',3,'1+2+3'),
       (6,'ER',5,'1+2+3+5');

GO

SELECT *
FROM dbo.YourTable;
GO
WITH rCTE AS(
    SELECT (YT.W_ID + 99) AS ID,
           YT.W_ID      ,
           YT.Parent_id,
           Title AS level_1,
           CONVERT(varchar(3),NULL) AS Level_2,
           CONVERT(varchar(3),NULL) AS Level_3,
           CONVERT(varchar(3),NULL) AS Level_4,
           CONVERT(varchar(3),NULL) AS Level_5,
           CONVERT(varchar(3),NULL) AS Level_6,
           CONVERT(varchar(3),NULL) AS Level_7,
           CONVERT(varchar(3),NULL) AS Level_8,
           CONVERT(varchar(3),NULL) AS Level_9,
           CONVERT(varchar(3),NULL) AS Level_10,
           CONVERT(varchar(3),NULL) AS Level_11,
           CONVERT(varchar(3),NULL) AS Level_12,
           CONVERT(varchar(3),NULL) AS Level_13,
           CONVERT(varchar(3),NULL) AS Level_14,
           CONVERT(varchar(3),NULL) AS Level_15,
           1 AS [Level]
    FROM dbo.YourTable YT
    WHERE YT.Parent_id IS NULL
    UNION ALL
    SELECT (YT.W_ID + 99) AS ID,
           YT.W_ID,
           YT.Parent_id,
           r.Level_1,
           CASE r.[Level] WHEN 1 THEN YT.Title ELSE r.Level_2 END AS Level_2,
           CASE r.[Level] WHEN 2 THEN YT.Title ELSE r.Level_3 END AS Level_3,
           CASE r.[Level] WHEN 3 THEN YT.Title ELSE r.Level_4 END AS Level_4,
           CASE r.[Level] WHEN 4 THEN YT.Title ELSE r.Level_5 END AS Level_5,
           CASE r.[Level] WHEN 5 THEN YT.Title ELSE r.Level_6 END AS Level_6,
           CASE r.[Level] WHEN 6 THEN YT.Title ELSE r.Level_7 END AS Level_7,
           CASE r.[Level] WHEN 7 THEN YT.Title ELSE r.Level_8 END AS Level_8,
           CASE r.[Level] WHEN 8 THEN YT.Title ELSE r.Level_9 END AS Level_9,
           CASE r.[Level] WHEN 9 THEN YT.Title ELSE r.Level_10 END AS Level_10,
           CASE r.[Level] WHEN 10 THEN YT.Title ELSE r.Level_11 END AS Level_11,
           CASE r.[Level] WHEN 11 THEN YT.Title ELSE r.Level_12 END AS Level_12,
           CASE r.[Level] WHEN 12 THEN YT.Title ELSE r.Level_13 END AS Level_13,
           CASE r.[Level] WHEN 13 THEN YT.Title ELSE r.Level_14 END AS Level_14,
           CASE r.[Level] WHEN 14 THEN YT.Title ELSE r.Level_15 END AS Level_15,
           r.[Level] + 1 AS [Level]
    FROM dbo.YourTable YT
         JOIN rCTe r ON YT.Parent_id = r.W_ID)
SELECT r.ID,
       r.W_ID,
       r.Parent_id,
       r.Level_1,
       r.Level_2,
       r.Level_3,
       r.Level_4,
       r.Level_5,
       r.Level_6,
       r.Level_7,
       r.Level_8,
       r.Level_9,
       r.Level_10,
       r.Level_11,
       r.Level_12,
       r.Level_13,
       r.Level_14,
       r.Level_15
FROM rCTE r;
GO

DROP TABLE dbo.YourTable;

db<>fiddle

【讨论】:

感谢您的帮助,我的问题已解决,但速度很慢。请投票给我的问题。 分层数据对于大型数据集总是很慢。如果性能很重要,请使用 hierachyid, @Javadadebi 。我不确定你对你的问题投票是什么意思,你是要我投票吗?如果这确实解决了问题,您应该将其标记为解决方案,以便未来的读者知道答案是有帮助的。 是的,你说得对。我不知道为什么有些人拒绝投票给某个问题,但你的答案太接近了,但我应该说它太慢了,例如 100 条记录需要 20 秒,想象一下 150 万条记录。感谢您的帮助。 那么你应该看看你的索引。如果 100 行需要 100 秒,那么您有一些索引严重不良的表,或者硬件严重不足的服务器。 至于否决票,我怀疑是在您添加尝试之前。不过,只有投反对票的人才能告诉你。

以上是关于计算某些字段后如何从另一个表创建新表的主要内容,如果未能解决你的问题,请参考以下文章

创建表,然后将数据从另一个表插入到新表中

如何在PBI中将计算得出的度量值放入“新表”中

如何在 PL/SQL 中连接两个表而不创建新表

如何将自动填充的表单记录(从另一条记录创建)添加到访问表中?

MySQL - 如何创建一个新表,该表是两个现有表的主键的连接

在SQL数据库中如何把一个表的同一个字段复制到同一个表的另一个字段?