SQL Server: Sütunlardan Satırlara


129

Sütunları satırlara dönüştürmek için zarif (veya herhangi bir) çözüm arıyorsunuz.

İşte bir örnek: Aşağıdaki şemaya sahip bir tablom var:

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

Sonuç olarak elde etmek istediğim şey şu:

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

Ve sonuç değerleri şöyle olacaktır:

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'

Ve bunun gibi..

Bu mantıklı mı? T-SQL'de nereye bakacağınız ve nasıl yapılacağı konusunda herhangi bir öneriniz var mı?



Sonunda bluefeet'in çözümüyle gitti. Zarif ve işlevsel. Herkese çok teşekkürler.
Sergei

Yanıtlar:


248

Sütunları satırlara dönüştürmek için UNPIVOT işlevini kullanabilirsiniz :

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

Özetini çözdüğünüz sütunların veri türlerinin aynı olması gerektiğine dikkat edin, bu nedenle özetin uygulanmasından önce veri türlerini dönüştürmeniz gerekebilir.

CROSS APPLYSütunları dönüştürmek için UNION ALL ile de kullanabilirsiniz :

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

SQL Server sürümünüze bağlı olarak, CROSS APPLY'ı VALUES yan tümcesi ile bile kullanabilirsiniz:

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

Son olarak, özetlenecek 150 sütununuz varsa ve tüm sorguyu sabit kodlamak istemiyorsanız, dinamik SQL kullanarak sql ifadesini oluşturabilirsiniz:

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;

4
UNPIVOTVe / vs hakkında daha fazla somun ve cıvata isteyenler için . APPLY, Brad Schulz bu 2010 blog yazısı (ve Takip Eden ) 'dir güzel (vardır).
ruffin

2
Msg 8167, Düzey 16, Durum 1, Satır 147 "Blahblah" sütununun türü, UNPIVOT listesinde belirtilen diğer sütunların türüyle çakışıyor.
JDPeckham

@JDPeckham Farklı veri türleriniz varsa, pivotu gerçekleştirmeden önce bunları aynı tür ve uzunlukta olacak şekilde dönüştürmeniz gerekir. İşte bununla ilgili daha fazla bilgi .
Taryn

xml yönteminin bir kusuru vardır çünkü & gt ;, & lt; ve & amp ;. Ayrıca, aşağıdaki gibi yeniden yazarak performans önemli ölçüde iyileştirilebilir: @colsUnpivot = stuff ((select ',' + quotename (C.column_name) olarak [text ()] olarak C, burada C.table_name = 'yourtable' olarak information_schema.columns ve C.column_name, xml yolu ('') için 'Gösterge%', type) .value ('text () [1]', 'nvarchar (max)'), 1, 1, '')
rrozema

24

Eğer 150 sütununuz varsa, UNPIVOT'un bir seçenek olmadığını düşünüyorum. Böylece xml numarası kullanabilirsiniz

;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

Dinamik SQL de yazabilirsiniz, ancak xml'yi daha çok seviyorum - dinamik SQL için verileri doğrudan tablodan seçme izninizin olması gerekir ve bu her zaman bir seçenek değildir.

GÜNCELLEME
Yorumlarda büyük bir alev olduğu için, xml / dinamik SQL'in bazı artılarını ve eksilerini ekleyeceğimi düşünüyorum. Elimden geldiğince objektif olmaya çalışacağım ve zariflik ve çirkinlikten bahsetmeyeceğim. Başka artılarınız ve eksileriniz varsa, cevabı düzenleyin veya yorum yazın

Eksileri

  • öyle hızlı değil dinamik SQL gibi, kaba testler xml yavaş o dinamik (bu tahmin hiçbir şekilde kesin değildir bu yüzden, ~ 250000 satırlar masada bir sorgu oldu) 2.5 kat hakkındadır bana verdi. İsterseniz kendiniz karşılaştırabilirsiniz, işte sqlfiddle örneği, 100000 satırda 29s (xml) vs 14s (dinamik);
  • xpath'e aşina olmayan insanlar için anlaşılması daha zor olabilir ;

profesyoneller

  • bu kadar aynı kapsam diğer sorguları gibi ve bu çok kullanışlı olabilir. Akla birkaç örnek geliyor
    • tetikleyicinizin içinde sorgulama insertedve deletedtablolar yapabilirsiniz (dinamik ile hiç mümkün değildir);
    • kullanıcının tablodan doğrudan seçim için izinlere sahip olması gerekmez . Demek istediğim, saklı yordam katmanınız varsa ve kullanıcı sp çalıştırma iznine sahipse, ancak tabloları doğrudan sorgulama izniniz yoksa, bu sorguyu saklı yordam içinde yine de kullanabilirsiniz;
    • Eğer olabilir sorgu tablosu değişken size kapsamında doldurulan gelmiş (ya bunun yerine geçici tablo yapmak veya türünü oluşturmak ve dinamik SQL içine bir parametre olarak geçmek zorunda dinamik SQL içine geçmesine;
  • bu sorguyu işlevin içinde yapabilirsiniz (skaler veya tablo değerli). Fonksiyonların içinde dinamik SQL kullanmak mümkün değildir;

2
Tablodan veri seçmeyi gerektirmeyen XML ile hangi verileri seçiyorsunuz?
Aaron Bertrand

1
Örneğin, kullanıcılara tablolardan veri seçme izni vermemeye karar verebilirsiniz, ancak yalnızca tablolarla çalışan saklı yordamlarda, yordamın içinde xml'yi seçebilirim, ancak dinamik SQL kullanmak istiyorsam bazı geçici çözümler kullanmam gerekir
Roman Pekar

3
Kullanıcılarınızın kodu yürütebilmesini istiyorsanız, onlara kodu yürütmek için ihtiyaç duydukları her türlü erişimi vermeniz gerekir. Cevabınızın daha iyi görünmesini sağlamak için var olmayan gereksinimleri uydurmayın (cevabınıza bakmak için rakip cevaplar hakkında yorum yapmanız gerekmez - bu cevabı bulurlarsa, sizinkini de bulabilirler).
Aaron Bertrand

2
Ayrıca XML kullanma gerekçeniz, tabloya doğrudan erişim vermekten kaçınmak için onu bir saklı yordama koyabilmeniz ise, belki de örneğiniz onu bir saklı yordama nasıl koyacağınızı ve bir kullanıcıya nasıl haklar verileceğini göstermelidir. temel tabloya okuma erişimi olmadan çalıştırabilir. Bana göre bu kapsam ürkütücü, çünkü bir masaya sorgu yazan çoğu insanın tabloya okuma erişimi var.
Aaron Bertrand

2
Süre olarak 10x farkın önemli olduğunu söyleyebilirim, evet. Ve ~ 8.000 satır "büyük miktarda veri" değildir - 800.000 satıra karşı ne olacağını görmeli miyiz?
Aaron Bertrand

7

Yeni okuyuculara yardımcı olmak için @ bluefeet'in UNPIVOT hakkındaki cevabını daha iyi anlamak için bir örnek oluşturdum.

 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;

3

Sütun adlarını bilmeden (tetikleyicide kullanılır) ve dinamik sql olmadan (dinamik sql bir tetikleyicide kullanım için çok yavaş), Microsoft SQL Server'da sütunları satırlara dönüştürmek için bir çözüme ihtiyacım vardı.

Sonunda iyi çalışan bu çözümü buldum:

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)

Gördüğünüz gibi, satırı XML'e dönüştürüyorum (Ham xml için alt sorgu i, * seçin, bu tüm sütunları tek bir xml sütununa dönüştürür)

Daha sonra bu sütunun her XML özniteliğine bir işlev UYGULAYARIM, böylece öznitelik başına bir satır elde ederim.

Genel olarak bu, sütun adlarını bilmeden ve dinamik sql kullanmadan sütunları satırlara dönüştürür. Amacım için yeterince hızlı.

(Düzenleme: Yukarıda, aynısını yapan Roman Pekar cevabını gördüm. Dinamik sql tetikleyicisini önce imleçlerle kullandım, bu çözümden 10 ila 100 kat daha yavaştı, ama belki de imleçten kaynaklanıyordu, dinamik sql. Her neyse, bu çözüm çok basit ve evrensel, bu yüzden kesinlikle bir seçenek).

Bu yorumu bu yere bırakıyorum çünkü yazımda tam denetim tetikleyicisi hakkındaki bu açıklamaya atıfta bulunmak istiyorum, burada bulabileceğiniz: https://stackoverflow.com/a/43800286/4160788


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

Satır sayısı> 350 olan tabloların listesini alabilirsiniz. Tablonun çözüm listesinde satır olarak görebilirsiniz.


2

Sadece bahsettiğini görmediğim için.

2016+ ise, aslında Dinamik SQL kullanmadan verileri dinamik olarak özetlemek için başka bir seçenek daha var.

Misal

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

İadeler

ID  Item    Value
1   Col1    A
1   Col2    B
2   Col1    R
2   Col2    C
3   Col1    X
3   Col2    D
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.