【问题标题】:How can I pivot these key+values rows into a table of complete entries?如何将这些键+值行转换为完整条目表?
【发布时间】:2011-03-04 15:40:06
【问题描述】:

也许我对 SQL 的要求太高了,但我觉得这应该是可能的。我从键值对列表开始,如下所示:

'0:First, 1:Second, 2:Third, 3:Fourth'

等等。我可以很容易地通过两步解析将其拆分为如下表格:

EntryNumber  PairNumber  Item
0            0           0
1            0           First
2            1           1
3            1           Second

等等

现在,在将这些对拆分为一对列的简单情况下,这相当容易。我对每个条目可能有多个值的更高级的情况感兴趣,例如:

'0:First:Fishing, 1:Second:Camping, 2:Third:Hiking' 

等等。

在这种一般情况下,我想找到一种方法来获取我的 3 列结果表,并以某种方式将其旋转为每个条目一行,每个值部分一列。

所以我想转这个:

EntryNumber  PairNumber  Item
0            0           0
1            0           First
2            0           Fishing
3            1           1
4            1           Second
5            1           Camping

进入这个:

Entry   [1]   [2]      [3]
0       0     First    Fishing
1       1     Second   Camping

SQL 处理的太多了,还是有办法?枢轴(甚至是棘手的动态枢轴)似乎是一个答案,但我不知道如何让它发挥作用。

【问题讨论】:

    标签: sql sql-server-2005 tsql pivot


    【解决方案1】:

    不,在 SQL 中,您不能根据在同一查询中找到的数据动态推断列。

    即使使用 Microsoft SQL Server 中的 PIVOT 功能,您在编写查询时也必须知道列,并且必须对它们进行硬编码。

    您必须做很多工作才能避免以关系范式存储数据。

    【讨论】:

      【解决方案2】:

      好的,我找到了一种方法来完成我所追求的。系好安全带,这会变得颠簸。

      所以基本的问题是获取具有两种分隔符的字符串:条目和值。每个条目代表一组值,我想将字符串转换为一个表格,每个条目的每个值对应一列。我试图使它成为一个 UDF,但临时表和动态 SQL 的必要性意味着它必须是一个存储过程。

      CREATE PROCEDURE [dbo].[ParseValueList] 
      (   
          @parseString varchar(8000),
          @itemDelimiter CHAR(1),
          @valueDelimiter CHAR(1)
      )
      AS
      BEGIN
      
      SET NOCOUNT ON;
      
          IF object_id('tempdb..#ParsedValues') IS NOT NULL
          BEGIN
             DROP TABLE #ParsedValues
          END
          CREATE TABLE #ParsedValues 
         ( 
              EntryID int,
             [Rank] int, 
             Pair varchar(200)
         )
      

      所以这只是基本设置,建立临时表来保存我的中间结果。

      ;WITH
          E1(N) AS (SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL 
              SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL 
              SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1),--Brute forces 10 rows
          E2(N) AS (SELECT 1 FROM E1 a, E1 b),   --Uses a cross join to generate 100 rows (10 * 10)
          E4(N) AS (SELECT 1 FROM E2 a, E2 b),   --Uses a cross join to generate 10,000 rows (100 * 100)
      cteTally(N) AS (SELECT ROW_NUMBER() OVER (ORDER BY N) FROM E4)
      

      那段漂亮的 SQL 来自 SQL Server Central's Forums,被称为“大师”。这是一个很棒的小 10,000 行计数表,非常适合字符串拆分。

      INSERT INTO #ParsedValues
          SELECT ItemNumber AS EntryID, ROW_NUMBER() OVER (PARTITION BY ItemNumber ORDER BY ItemNumber) AS [Rank],  
              SUBSTRING(Items.Item, T1.N, CHARINDEX(@valueDelimiter, Items.Item + @valueDelimiter, T1.N) - T1.N) AS [Value]
          FROM(
              SELECT ROW_NUMBER() OVER (ORDER BY T2.N) AS ItemNumber,
                  SUBSTRING(@parseString, T2.N, CHARINDEX(@itemDelimiter, @parseString + @itemDelimiter, T2.N) - T2.N) AS Item
              FROM cteTally T2
              WHERE T2.N < LEN(@parseString) + 2 --Ensures we cut out once the entire string is done
                  AND SUBSTRING(@itemDelimiter + @parseString, T2.N, 1) = @itemDelimiter
              ) AS Items, cteTally T1
          WHERE T1.N < LEN(@parseString) + 2 --Ensures we cut out once the entire string is done
              AND SUBSTRING(@valueDelimiter + Items.Item, T1.N, 1) = @valueDelimiter
      

      好的,这是第一个真正密集的肉质部分。内部选择正在使用 guru 的字符串拆分方法沿项目分隔符(逗号)拆分我的字符串。然后将该表传递给执行相同操作的外部选择,但这次使用值分隔符(冒号)到每一行。内部 RowNumber (EntryID) 和外部 RowNumber over Partition (Rank) 是枢轴的关键。 EntryID 显示值属于哪个 Item,Rank 显示值的序号。

          DECLARE @columns varchar(200)
          DECLARE @columnNames varchar(2000)
          DECLARE @query varchar(8000)
      
          SELECT @columns = COALESCE(@columns + ',[' + CAST([Rank] AS varchar) + ']', '[' + CAST([Rank] AS varchar)+ ']'),
          @columnNames = COALESCE(@columnNames + ',[' + CAST([Rank] AS varchar) + '] AS Value' + CAST([Rank] AS varchar)
                                  , '[' + CAST([Rank] AS varchar)+ '] AS Value' + CAST([Rank] AS varchar))
          FROM (SELECT DISTINCT [Rank] FROM #ParsedValues) AS Ranks
      
          SET @query = '
          SELECT '+ @columnNames +'
          FROM #ParsedValues
          PIVOT 
          (
              MAX([Value]) FOR [Rank]
              IN (' + @columns + ')
          ) AS pvt'
      
          EXECUTE(@query)
      
          DROP TABLE #ParsedValues
      
      END
      

      最后,使之成为可能的动态 sql。通过获取 Distinct Ranks 列表,我们设置了列列表。然后将其写入动态轴,该轴将值倾斜并将每个值插入正确的列,每个列都有一个通用的“Value#”标题。

      因此,通过使用格式正确的值字符串调用EXEC ParseValueList,我们可以将其分解成一个表格来满足我们的目的!它适用于简单的键:值对(但可能是矫枉过正),并且可以扩展到相当多的列(我认为最多大约 50 列,但这真的很愚蠢。)

      无论如何,希望对遇到类似问题的人有所帮助。

      (是的,它可能也可以在 SQLCLR 之类的东西中完成,但我发现用纯 SQL 解决问题非常高兴。)

      【讨论】:

        【解决方案3】:

        虽然可能不是最佳的,但这里有一个更简洁的解决方案。

        DECLARE @DATA varchar(max);
        SET @DATA = '0:First:Fishing, 1:Second:Camping, 2:Third:Hiking';
        
        SELECT
                DENSE_RANK() OVER (ORDER BY [Data].[row]) AS [Entry]
              , [Data].[row].value('(./B/text())[1]', 'int') as "[1]"
              , [Data].[row].value('(./B/text())[2]', 'varchar(64)') as "[2]"
              , [Data].[row].value('(./B/text())[3]', 'varchar(64)') as "[3]"
        FROM
            (
                SELECT
                    CONVERT(XML, '<A><B>' + REPLACE(REPLACE(@DATA , ',', '</B></A><A><B>'), ':', '</B><B>') + '</B></A>').query('.')
             ) AS [T]([c])
        CROSS APPLY [T].[c].nodes('/A') AS [Data]([row]);
        

        【讨论】:

        • 如果我没有提到我为这个解决方案借鉴了 Brad Schulz 和 Adam Machanic 的概念,那我就失职了。
        • 这是一个非常漂亮的概念,使用 XML 转换来处理翻译。我曾考虑过类似的事情。我可能需要更仔细地检查您的建议,才能弄清楚正在发生的一切。不过,它并没有完全达到我的预期,这说明了任何数量的值。不过,它确实提供了一个易于扩展的查询来执行已知数量的值。
        【解决方案4】:

        希望还为时不晚。

        您可以使用函数 RANK 来了解每个 PairNumber 中每个 Item 的位置。然后使用 Pivot

        SELECT PairNumber, [1] ,[2] ,[3] 
        FROM
        (
        SELECT  PairNumber, Item, RANK() OVER (PARTITION BY PairNumber order by EntryNumber) as RANKing
        from tabla) T
        PIVOT 
        (MAX(Item)
        FOR RANKing in ([1],[2],[3])
        )as PVT
        

        【讨论】:

          猜你喜欢
          • 2012-03-07
          • 1970-01-01
          • 2019-12-11
          • 2022-01-18
          • 2021-05-08
          • 2021-09-05
          • 1970-01-01
          • 2011-06-01
          • 1970-01-01
          相关资源
          最近更新 更多