2010-06-16 2 views
0

Возможно, я требую слишком многого от 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' 

и другие.

В этом общем случае я хотел бы найти способ взять таблицу результатов из трех столбцов и как-то развернуть ее, чтобы иметь одну строку на запись и один столбец на часть ценности.

Так что я хочу, чтобы превратить это:

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 для обработки, или есть способ? Сводки (даже сложные динамические опорные точки) кажутся ответом, но я не могу понять, как заставить это работать.

ответ

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 и зачисляется на «гуру». Это отличная таблица с десятью тысячами строк, идеально подходящая для разделения строк.

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 

Хорошо, это первая действительно плотная мясистая часть. Внутренний выбор разбивает мою строку вдоль разделителя элемента (запятая), используя метод разделения строк гуру. Затем эта таблица передается внешнему элементу, который делает то же самое, но на этот раз использует разделитель значений (двоеточие) для каждой строки.Внутренний RowNumber (EntryID) и внешний RowNumber over Partition (Rank) являются ключевыми для оси. EntryID показывает, к какому элементу относятся значения, а 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, который дает возможность. Получив список отличительных рангов, мы создали список столбцов. Затем он записывается в динамический стержень, который наклоняет значения и разбивает каждое значение на соответствующий столбец, каждый из которых имеет общий заголовок «Value #».

Таким образом, позвонив по номеру EXEC ParseValueList с правильно отформатированной строкой значений, мы можем разбить его на таблицу, которая будет использоваться в наших целях! Он работает (но, вероятно, слишком много) для простых ключей: пары значений и масштабируется до достаточного количества столбцов (примерно 50, я думаю, но это было бы очень глупо).

В любом случае, надеемся, что помогает любому, у кого есть аналогичная проблема.

(Да, это, вероятно, можно было бы сделать в чем-то вроде SQLCLR как хорошо, но я нахожу большую радость в решении проблем с чистым SQL.)

1

Нет, в SQL вы не можете динамически выводить столбцы на основе данных, найденных во время одного и того же запроса.

Даже используя функцию PIVOT в Microsoft SQL Server, вы должны знать столбцы при написании запроса, и вам нужно их жестко закодировать.

Вам нужно много работать, чтобы избежать хранения данных в нормальной форме.

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

Я был бы упущен, если бы не упомянул, что я использовал для этого решения концепции Брэда Шульца и Адама Мачанича. – etliens

+0

Это довольно изящная концепция, использующая преобразование XML для обработки перевода. Я подумал об этом. Мне, возможно, придется более внимательно изучить ваше предложение, чтобы разобраться во всем, что происходит. Он не совсем делает то, что я надеялся, хотя и учитывает любое количество значений. Однако он представляет собой легко расширяемый запрос для выполнения известного числа значений. – CodexArcanum

0

Надежда еще не слишком поздно.

Вы можете использовать функцию RANK для определения положения каждого элемента на парный номер. А затем используйте 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