也许我对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(甚至是棘手的动态支点)似乎是一个答案,但我无法想象如何让它发挥作用。
答案 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