2011-09-07 16 views
9

Jaki jest najlepszy sposób uzyskania zestawu wyników par klucz-wartość, który reprezentuje wartość kolumny w wierszu?Spłaszczanie tabeli z jednym wierszem w tabeli par klucz-wartość

Biorąc pod uwagę następujące tabela A tylko 1 wiersz


Column1 Column2 Column3... 
Value1 Value2 Value3 

Chcę zapytać go i wstawić do innej tabeli B:


Key Value 
Column1    Value1 
Column2    Value2 
Column3    Value3 

Zestaw kolumn w tabeli A nie jest znany w postęp.

UWAGA: Szukałem w przypadku obiektów XML i przestawnych oraz dynamicznego SQL zrobić coś takiego:


    DECLARE @sql nvarchar(max) 
    SET @sql = (SELECT STUFF((SELECT ',' + column_name 
           FROM INFORMATION_SCHEMA.COLUMNS 
           WHERE table_name='TableA' 
           ORDER BY column_name FOR XML PATH('')), 1, 1, '')) 
    SET @sql = 'SELECT ' + @sql + ' FROM TableA' 
    EXEC(@sql) 
+3

To 'UNPIVOT' nie' PIVOT' ale trzeba jeszcze dynamicznego SQL. –

+0

+1 za komentarz Martina. Ponieważ nie wiesz, że kolumny są niepijane, potrzebujesz dynamicznego SQL. – MatBailie

+0

@Aaron - tylko literówka, naprawiona teraz – kateroh

Odpowiedz

18

wersja gdzie nie ma dynamiczny zaangażowany. Jeśli masz niepoprawne nazwy kolumn, które będą używane jako nazwy elementów w XML, to się nie powiedzie.

select T2.N.value('local-name(.)', 'nvarchar(128)') as [Key], 
     T2.N.value('text()[1]', 'nvarchar(max)') as Value 
from (select * 
     from TableA 
     for xml path(''), type) as T1(X) 
    cross apply T1.X.nodes('/*') as T2(N) 

Próbka pracy:

declare @T table 
(
    Column1 varchar(10), 
    Column2 varchar(10), 
    Column3 varchar(10) 
) 

insert into @T values('V1','V2','V3') 

select T2.N.value('local-name(.)', 'nvarchar(128)') as [Key], 
     T2.N.value('text()[1]', 'nvarchar(max)') as Value 
from (select * 
     from @T 
     for xml path(''), type) as T1(X) 
    cross apply T1.X.nodes('/*') as T2(N) 

Wynik:

Key     Value 
-------------------- ----- 
Column1    V1 
Column2    V2 
Column3    V3 

Aktualizacja

Dla zapytania z więcej niż jednej tabeli można użyć for xml auto dostać stolik nazwy w XML. Uwaga: jeśli użyjesz aliasu dla nazw tabel w zapytaniu, otrzymasz alias.

select X2.N.value('local-name(..)', 'nvarchar(128)') as TableName, 
     X2.N.value('local-name(.)', 'nvarchar(128)') as [Key], 
     X2.N.value('text()[1]', 'nvarchar(max)') as Value 
from (
    -- Your query starts here 
    select T1.T1ID, 
      T1.T1Col, 
      T2.T2ID, 
      T2.T2Col 
    from T1 
     inner join T2 
     on T1.T1ID = T2.T1ID 
    -- Your query ends here 
    for xml auto, elements, type  
    ) as X1(X) 
    cross apply X1.X.nodes('//*[text()]') as X2(N) 

SQL Fiddle

+0

Nice ................! –

+0

Muszę przyznać, że to intrygujące podejście. – 8kb

+0

local-name (.) Zrobił lewę dla nazw kolumn. fajnie dzięki! – kateroh

3

Może robisz to bardziej skomplikowane niż to musi być. Częściowo dlatego, że nie mogłem owijać mojego małego mózgu wokół numeru PIVOT/UNPIVOT/cokolwiek kombinacje i dynamiczne "morze czerwieni" SQL byłoby konieczne, aby to zrobić. Ponieważ wiesz, że tabela ma dokładnie jeden wiersz, ciągnięcie wartości dla każdej kolumny może być po prostu podkwerendą jako częścią zestawu zapytań UNION ed.

DECLARE @sql NVARCHAR(MAX) = N'INSERT dbo.B([Key], Value) ' 

SELECT @sql += CHAR(13) + CHAR(10) 
     + ' SELECT [Key] = ''' + REPLACE(name, '''', '''''') + ''', 
     Value = (SELECT ' + QUOTENAME(name) + ' FROM dbo.A) UNION ALL' 
FROM sys.columns 
WHERE [object_id] = OBJECT_ID('dbo.A'); 

SET @sql = LEFT(@sql, LEN(@sql)-9) + ';'; 

PRINT @sql; 
-- EXEC sp_executesql @sql; 

Wynik (I tylko stworzył 4 kolumny, ale to będzie działać na dowolną liczbę):

INSERT dbo.B([Key], Value) 
SELECT [Key] = 'Column1', 
     Value = (SELECT [Column1] FROM dbo.A) UNION ALL 
SELECT [Key] = 'Column2', 
     Value = (SELECT [Column2] FROM dbo.A) UNION ALL 
SELECT [Key] = 'Column3', 
     Value = (SELECT [Column3] FROM dbo.A) UNION ALL 
SELECT [Key] = 'Column4', 
     Value = (SELECT [Column4] FROM dbo.A); 

Najbardziej wydajne rzeczą na świecie? Prawdopodobnie nie. Ale znowu, dla tabeli jednorzędowej, i mam nadzieję, że jednorazowe zadanie, myślę, że będzie dobrze. Po prostu uważaj na nazwy kolumn zawierające apostrofy, jeśli pozwolisz na te rzeczy w swoim sklepie ...

EDIT Przepraszam, nie mogłem tego tak zostawić. Teraz będzie obsługiwać apostrofy w nazwach kolumn i inne nieoptymalne opcje nazewnictwa.

6

Myślę, że jesteś w połowie drogi. Wystarczy użyć UNPIVOT i dynamic SQL jak Martin poleca:

CREATE TABLE TableA (
    Code VARCHAR(10), 
    Name VARCHAR(10), 
    Details VARCHAR(10) 
) 

INSERT TableA VALUES ('Foo', 'Bar', 'Baz') 
GO 

DECLARE @sql nvarchar(max) 
SET @sql = (SELECT STUFF((SELECT ',' + column_name 
          FROM INFORMATION_SCHEMA.COLUMNS 
          WHERE table_name='TableA' 
          ORDER BY ordinal_position FOR XML PATH('')), 1, 1, '')) 

SET @sql = N'SELECT [Key], Val FROM (SELECT ' + @sql + ' FROM TableA) x ' 
+ 'UNPIVOT (Val FOR [Key] IN (' + @sql + ')) AS unpiv' 
EXEC (@sql) 

Wyniki:

Key   Val 
------------ ------------ 
Code   Foo 
Name   Bar 
Details  Baz 

Jest zastrzeżenie, oczywiście. Wszystkie powyższe kolumny będą musiały być takie same, aby powyższy kod zadziałał. Jeśli tak nie jest, pojawi się następujący błąd:

Msg 8167, Level 16, State 1, Line 1 
The type of column "Col" conflicts with the type of 
other columns specified in the UNPIVOT list. 

Aby to obejść, należy utworzyć dwie instrukcje kolumnowe. Jeden, aby uzyskać kolumny i jeden, aby rzucić je wszystkie jako typ danych dla twojej kolumny Val.

przypadku wielu typów kolumn:

CREATE TABLE TableA (
    Code INT, 
    Name VARCHAR(10), 
    Details VARCHAR(10) 
) 

INSERT TableA VALUES (1, 'Foo', 'Baf') 
GO 

DECLARE 
    @sql nvarchar(max), 
    @cols nvarchar(max), 
    @conv nvarchar(max) 

SET @cols = (SELECT STUFF((SELECT ',' + column_name 
          FROM INFORMATION_SCHEMA.COLUMNS 
          WHERE table_name='TableA' 
          ORDER BY ordinal_position FOR XML PATH('')), 1, 1, '')) 

SET @conv = (SELECT STUFF((SELECT ', CONVERT(VARCHAR(50), ' 
          + column_name + ') AS ' + column_name 
          FROM INFORMATION_SCHEMA.COLUMNS 
          WHERE table_name='TableA' 
          ORDER BY ordinal_position FOR XML PATH('')), 1, 1, '')) 


SET @sql = N'SELECT [Key], Val FROM (SELECT ' + @conv + ' FROM TableA) x ' 
+ 'UNPIVOT (Val FOR [Key] IN (' + @cols + ')) AS unpiv' 
EXEC (@sql) 
Powiązane problemy