如何将这些键+值行旋转到完整条目表中?

时间:2010-06-16 19:43:46

标签: sql sql-server-2005 tsql pivot

也许我对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来说,处理这个问题太多了,还是有办法? Pivots(甚至是棘手的动态支点)似乎是一个答案,但我无法想象如何让它发挥作用。

4 个答案:

答案 0 :(得分:1)

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

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

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

答案 1 :(得分:0)

好吧,我找到了一种方法来完成我的目标。收起来,这会变得坎坷。

所以基本的问题是带有两种分隔符的字符串:条目和值。每个条目代表一组值,我想将字符串转换为一个表,每个条目的每个值都有一列。我试图使这成为一个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的字符串拆分方法沿项目分隔符(逗号)分解我的字符串。然后将该表传递给外部select,它执行相同的操作,但这次使用值分隔符(冒号)到每一行。内部RowNumber(EntryID)和外部RowNumber over Partition(Rank)是pivot的关键。 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列表,我们设置了列列表。然后将其写入动态轴,将值倾斜并将每个值插入正确的列,每个列都带有一个通用的“值#”标题。

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

无论如何,希望能帮助任何有类似问题的人。

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

答案 2 :(得分:0)

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

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]);

答案 3 :(得分:0)

希望还不算太晚。

您可以使用函数RANK来了解每个PairNumber的每个项目的位置。然后使用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