SQL Server: Kolom ke Baris

129

Mencari solusi elegan (atau apa saja) untuk mengonversi kolom menjadi baris.

Berikut ini sebuah contoh: Saya punya tabel dengan skema berikut:

[ID] [EntityID] [Indicator1] [Indicator2] [Indicator3] ... [Indicator150]

Inilah yang ingin saya dapatkan sebagai hasilnya:

[ID] [EntityId] [IndicatorName] [IndicatorValue]

Dan nilai hasilnya adalah:

1 1 'Indicator1' 'Value of Indicator 1 for entity 1'
2 1 'Indicator2' 'Value of Indicator 2 for entity 1'
3 1 'Indicator3' 'Value of Indicator 3 for entity 1'
4 2 'Indicator1' 'Value of Indicator 1 for entity 2'

Dan seterusnya..

Apakah ini masuk akal? Apakah Anda memiliki saran tentang ke mana harus mencari dan bagaimana menyelesaikannya dalam T-SQL?

Sergei
sumber
2
Sudahkah Anda melihat Pivot / Unpivot ?
Josh Jay
Pada akhirnya itu pergi dengan solusi bluefeet. Elegan dan fungsional. Terima kasih banyak semuanya.
Sergei

Jawaban:

248

Anda dapat menggunakan fungsi UNPIVOT untuk mengubah kolom menjadi baris:

select id, entityId,
  indicatorname,
  indicatorvalue
from yourtable
unpivot
(
  indicatorvalue
  for indicatorname in (Indicator1, Indicator2, Indicator3)
) unpiv;

Catatan, tipe data dari kolom yang Anda tidakivot harus sama sehingga Anda mungkin harus mengonversi tipe data sebelum menerapkan unpivot.

Anda juga dapat menggunakan CROSS APPLYdengan UNION ALL untuk mengonversi kolom:

select id, entityid,
  indicatorname,
  indicatorvalue
from yourtable
cross apply
(
  select 'Indicator1', Indicator1 union all
  select 'Indicator2', Indicator2 union all
  select 'Indicator3', Indicator3 union all
  select 'Indicator4', Indicator4 
) c (indicatorname, indicatorvalue);

Bergantung pada versi SQL Server Anda, Anda bahkan bisa menggunakan CROSS APPLY dengan klausa VALUES:

select id, entityid,
  indicatorname,
  indicatorvalue
from yourtable
cross apply
(
  values
  ('Indicator1', Indicator1),
  ('Indicator2', Indicator2),
  ('Indicator3', Indicator3),
  ('Indicator4', Indicator4)
) c (indicatorname, indicatorvalue);

Terakhir, jika Anda memiliki 150 kolom untuk tidak diproteksi dan Anda tidak ingin meng-hard-code seluruh kueri, maka Anda bisa membuat pernyataan sql menggunakan SQL dinamis:

DECLARE @colsUnpivot AS NVARCHAR(MAX),
   @query  AS NVARCHAR(MAX)

select @colsUnpivot 
  = stuff((select ','+quotename(C.column_name)
           from information_schema.columns as C
           where C.table_name = 'yourtable' and
                 C.column_name like 'Indicator%'
           for xml path('')), 1, 1, '')

set @query 
  = 'select id, entityId,
        indicatorname,
        indicatorvalue
     from yourtable
     unpivot
     (
        indicatorvalue
        for indicatorname in ('+ @colsunpivot +')
     ) u'

exec sp_executesql @query;
Taryn
sumber
4
Bagi mereka yang menginginkan lebih banyak mur dan baut tentang UNPIVOTdan / vs. APPLY, posting blog 2010 ini dari Brad Schulz (dan tindak lanjutnya ) indah.
ruffin
2
Msg 8167, Level 16, State 1, Line 147 Jenis kolom "blahblah" bertentangan dengan jenis kolom lainnya yang ditentukan dalam daftar UNPIVOT.
JDPeckham
@JDPeckham Jika Anda memiliki tipe data yang berbeda, maka Anda harus mengonversinya dengan tipe dan panjang yang sama sebelum melakukan unpivot. Berikut ini informasi lebih lanjut tentang itu .
Taryn
metode xml memiliki kelemahan karena gagal menghapus kode xml seperti & gt ;, & lt; dan & amp ;. Plus kinerjanya dapat ditingkatkan secara signifikan dengan menulis ulang sebagai berikut: pilih @colsUnpivot = barang ((pilih ',' + quotename (C.column_name) sebagai [teks ()] dari information_schema.columns sebagai C di mana C.table_name = 'yourtable' dan C.column_name seperti 'Indikator%' untuk jalur xml (''), ketik) .value ('text () [1]', 'nvarchar (max)'), 1, 1, '')
rrozema
24

baik Jika Anda memiliki 150 kolom maka saya pikir UNPIVOT bukanlah suatu pilihan. Jadi Anda bisa menggunakan trik xml

;with CTE1 as (
    select ID, EntityID, (select t.* for xml raw('row'), type) as Data
    from temp1 as t
), CTE2 as (
    select
         C.id, C.EntityID,
         F.C.value('local-name(.)', 'nvarchar(128)') as IndicatorName,
         F.C.value('.', 'nvarchar(max)') as IndicatorValue
    from CTE1 as c
        outer apply c.Data.nodes('row/@*') as F(C)
)
select * from CTE2 where IndicatorName like 'Indicator%'

sql fiddle demo

Anda juga bisa menulis SQL dinamis, tetapi saya lebih suka xml - untuk SQL dinamis Anda harus memiliki izin untuk memilih data langsung dari tabel dan itu tidak selalu menjadi pilihan.

MEMPERBARUI
Karena ada nyala besar dalam komentar, saya pikir saya akan menambahkan beberapa pro dan kontra dari xml / SQL dinamis. Saya akan berusaha seobjektif mungkin dan tidak menyebutkan keanggunan dan keburukan. Jika Anda mendapatkan pro dan kontra lainnya, edit jawabannya atau tulis komentar

kontra

  • itu tidak secepat SQL dinamis, tes kasar memberi saya bahwa xml adalah sekitar 2,5 kali lebih lambat dinamis (itu adalah satu permintaan pada ~ 250000 baris tabel, jadi perkiraan ini bukan cara yang tepat). Anda dapat membandingkannya sendiri jika Anda mau, ini sqlfiddle contoh , pada 100000 baris itu adalah 29s (xml) vs 14s (dynamic);
  • mungkin bisa lebih sulit untuk dipahami bagi orang yang tidak terbiasa dengan xpath;

pro

  • itu cakupan yang sama dengan pertanyaan Anda yang lain, dan itu bisa sangat berguna. Beberapa contoh muncul di benak saya
    • Anda dapat meminta inserteddan deletedtabel di dalam pemicu Anda (tidak mungkin dengan dinamis sama sekali);
    • pengguna tidak harus memiliki izin pilih langsung dari tabel. Maksud saya adalah jika Anda telah menyimpan lapisan prosedur dan pengguna memiliki izin untuk menjalankan sp, tetapi tidak memiliki izin untuk tabel kueri secara langsung, Anda masih bisa menggunakan kueri ini di dalam prosedur tersimpan;
    • Anda bisa kueri variabel tabel yang telah Anda isi dalam ruang lingkup Anda (untuk meneruskannya di dalam SQL dinamis, Anda harus membuatnya menjadi tabel sementara atau membuat tipe dan meneruskannya sebagai parameter ke dalam SQL dinamis;
  • Anda dapat melakukan kueri ini di dalam fungsi (skalar atau nilai tabel). Tidak mungkin menggunakan SQL dinamis di dalam fungsinya;
Roman Pekar
sumber
2
Data apa yang Anda pilih dengan XML yang tidak memerlukan pemilihan data dari tabel?
Aaron Bertrand
1
Sebagai contoh, Anda dapat memutuskan untuk tidak memberikan izin kepada pengguna untuk memilih data dari tabel, tetapi hanya pada prosedur tersimpan yang bekerja dengan tabel, jadi saya dapat memilih untuk xml di dalam prosedur, tetapi saya harus menggunakan beberapa solusi jika saya ingin menggunakan SQL dinamis
Roman Pekar
3
Jika Anda ingin pengguna Anda dapat mengeksekusi kode, Anda harus memberi mereka akses apa pun yang mereka butuhkan untuk mengeksekusi kode. Jangan membuat persyaratan yang tidak ada untuk membuat jawaban Anda terdengar lebih baik (Anda juga tidak perlu mengomentari jawaban yang bersaing untuk melihat jawaban Anda - jika mereka menemukan jawaban itu, mereka juga dapat menemukan jawaban Anda).
Aaron Bertrand
2
Juga jika pembenaran Anda untuk menggunakan XML adalah bahwa Anda bisa meletakkannya di prosedur tersimpan untuk menghindari memberikan akses langsung ke tabel, mungkin contoh Anda harus menunjukkan bagaimana memasukkannya ke dalam prosedur tersimpan dan bagaimana memberikan hak kepada pengguna sehingga mereka dapat menjalankannya tanpa memiliki akses baca ke tabel yang mendasarinya. Bagi saya itu cakupan creep, karena kebanyakan orang menulis kueri terhadap tabel telah membaca akses ke tabel.
Aaron Bertrand
2
Saya akan mengatakan perbedaan 10x dalam durasi tidak masalah, ya. Dan ~ 8.000 baris bukanlah "data dalam jumlah besar" - haruskah kita melihat apa yang terjadi terhadap 800.000 baris?
Aaron Bertrand
7

Hanya untuk membantu pembaca baru, saya telah membuat contoh untuk lebih memahami jawaban @ bluefeet tentang UNPIVOT.

 SELECT id
        ,entityId
        ,indicatorname
        ,indicatorvalue
  FROM (VALUES
        (1, 1, 'Value of Indicator 1 for entity 1', 'Value of Indicator 2 for entity 1', 'Value of Indicator 3 for entity 1'),
        (2, 1, 'Value of Indicator 1 for entity 2', 'Value of Indicator 2 for entity 2', 'Value of Indicator 3 for entity 2'),
        (3, 1, 'Value of Indicator 1 for entity 3', 'Value of Indicator 2 for entity 3', 'Value of Indicator 3 for entity 3'),
        (4, 2, 'Value of Indicator 1 for entity 4', 'Value of Indicator 2 for entity 4', 'Value of Indicator 3 for entity 4')
       ) AS Category(ID, EntityId, Indicator1, Indicator2, Indicator3)
UNPIVOT
(
    indicatorvalue
    FOR indicatorname IN (Indicator1, Indicator2, Indicator3)
) UNPIV;
Dmyan
sumber
3

Saya membutuhkan solusi untuk mengonversi kolom ke baris di Microsoft SQL Server, tanpa mengetahui nama colum (digunakan dalam trigger) dan tanpa sql dinamis (sql dinamis terlalu lambat untuk digunakan dalam trigger).

Saya akhirnya menemukan solusi ini, yang berfungsi dengan baik:

SELECT
    insRowTbl.PK,
    insRowTbl.Username,
    attr.insRow.value('local-name(.)', 'nvarchar(128)') as FieldName,
    attr.insRow.value('.', 'nvarchar(max)') as FieldValue 
FROM ( Select      
          i.ID as PK,
          i.LastModifiedBy as Username,
          convert(xml, (select i.* for xml raw)) as insRowCol
       FROM inserted as i
     ) as insRowTbl
CROSS APPLY insRowTbl.insRowCol.nodes('/row/@*') as attr(insRow)

Seperti yang Anda lihat, saya mengubah baris menjadi XML (Subquery select i, * untuk xml raw, ini mengubah semua kolom menjadi satu kolom xml)

Kemudian saya SALINKAN fungsi untuk setiap atribut XML kolom ini, sehingga saya mendapatkan satu baris per atribut.

Secara keseluruhan, ini mengubah kolom menjadi baris, tanpa mengetahui nama kolom dan tanpa menggunakan sql dinamis. Cukup cepat untuk tujuan saya.

(Sunting: Saya baru saja melihat Roman Pekar menjawab di atas, yang melakukan hal yang sama. Saya menggunakan pemicu sql dinamis dengan kursor pertama, yang 10 hingga 100 kali lebih lambat dari solusi ini, tapi mungkin itu disebabkan oleh kursor, bukan oleh sql dinamis. Pokoknya, solusi ini sangat sederhana dan universal, jadi ini merupakan pilihan).

Saya meninggalkan komentar ini di tempat ini, karena saya ingin merujuk penjelasan ini di posting saya tentang pemicu audit penuh, yang dapat Anda temukan di sini: https://stackoverflow.com/a/43800286/4160788

mengepak
sumber
3
DECLARE @TableName varchar(max)=NULL
SELECT @TableName=COALESCE(@TableName+',','')+t.TABLE_CATALOG+'.'+ t.TABLE_SCHEMA+'.'+o.Name
  FROM sysindexes AS i
  INNER JOIN sysobjects AS o ON i.id = o.id
  INNER JOIN INFORMATION_SCHEMA.TABLES T ON T.TABLE_NAME=o.name
 WHERE i.indid < 2
  AND OBJECTPROPERTY(o.id,'IsMSShipped') = 0
  AND i.rowcnt >350
  AND o.xtype !='TF'
 ORDER BY o.name ASC

 print @tablename

Anda bisa mendapatkan daftar tabel yang memiliki jumlah baris> 350. Anda dapat melihat pada daftar solusi tabel sebagai baris.

cunay
sumber
2

Hanya karena saya tidak melihatnya disebutkan.

Jika 2016+, berikut ini adalah opsi lain untuk secara dinamis tidak memproteksi data tanpa benar-benar menggunakan Dynamic SQL.

Contoh

Declare @YourTable Table ([ID] varchar(50),[Col1] varchar(50),[Col2] varchar(50))
Insert Into @YourTable Values 
 (1,'A','B')
,(2,'R','C')
,(3,'X','D')

Select A.[ID]
      ,Item  = B.[Key]
      ,Value = B.[Value]
 From  @YourTable A
 Cross Apply ( Select * 
                From  OpenJson((Select A.* For JSON Path,Without_Array_Wrapper )) 
                Where [Key] not in ('ID','Other','Columns','ToExclude')
             ) B

Kembali

ID  Item    Value
1   Col1    A
1   Col2    B
2   Col1    R
2   Col2    C
3   Col1    X
3   Col2    D
John Cappelletti
sumber