SQL Server: คอลัมน์ถึงแถว


129

กำลังมองหาโซลูชันที่หรูหรา (หรือใด ๆ ) เพื่อแปลงคอลัมน์เป็นแถว

นี่คือตัวอย่าง: ฉันมีตารางที่มีสคีมาต่อไปนี้:

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

นี่คือสิ่งที่ฉันต้องการได้รับจากผลลัพธ์:

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

และค่าผลลัพธ์จะเป็น:

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'

ไปเรื่อย ๆ ..

สิ่งนี้สมเหตุสมผลไหม คุณมีคำแนะนำเกี่ยวกับตำแหน่งที่จะดูและทำอย่างไรใน T-SQL หรือไม่?


2
คุณได้ดูPivot / Unpivot แล้วหรือยัง?
Josh Jay

ในตอนท้ายของมันไปด้วยโซลูชันของ bluefeet หรูหราและใช้งานได้จริง ขอบคุณทุกคนมาก
Sergei

คำตอบ:


249

คุณสามารถใช้ฟังก์ชันUNPIVOTเพื่อแปลงคอลัมน์เป็นแถว:

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

โปรดทราบว่าประเภทข้อมูลของคอลัมน์ที่คุณกำลังยกเลิกการใช้งานต้องเหมือนกันดังนั้นคุณอาจต้องแปลงประเภทข้อมูลก่อนที่จะใช้การไม่ได้ใช้งาน

คุณยังสามารถใช้CROSS APPLYกับ UNION ALL เพื่อแปลงคอลัมน์:

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 ของคุณคุณสามารถใช้ CROSS APPLY กับส่วนคำสั่ง VALUES:

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

สุดท้ายหากคุณมี 150 คอลัมน์ที่จะยกเลิกการใช้งานและคุณไม่ต้องการฮาร์ดโค้ดคิวรีทั้งหมดคุณสามารถสร้างคำสั่ง sql โดยใช้ไดนามิก SQL:

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
สำหรับผู้ที่ต้องการน็อตและสลักเกลียวเพิ่มเติมเกี่ยวกับUNPIVOTและ / vs. APPLY, บล็อกโพสต์ 2010 จากแบรดชูลซ์ (และที่ตามมา ) คือ (เป็น) ที่สวยงาม
ruffin

2
ข่าวสารเกี่ยวกับ 8167 ระดับ 16 สถานะ 1 บรรทัด 147 ประเภทของคอลัมน์ "blahblah" ขัดแย้งกับประเภทของคอลัมน์อื่น ๆ ที่ระบุในรายการ UNPIVOT
JDPeckham

@JDPeckham หากคุณมีประเภทข้อมูลที่แตกต่างกันคุณจะต้องแปลงให้เป็นประเภทและความยาวเดียวกันก่อนที่จะดำเนินการยกเลิกการใช้งาน นี่คือข้อมูลเพิ่มเติมเกี่ยวกับว่า
Taryn

วิธี xml มีข้อบกพร่องเนื่องจากไม่สามารถ unescape โค้ด xml เช่น & gt ;, & lt; และ & amp; นอกจากนี้ยังสามารถปรับปรุงประสิทธิภาพได้อย่างมากโดยการเขียนใหม่ดังนี้: เลือก @colsUnpivot = stuff ((select ',' + quotename (C.column_name) เป็น [text ()] จาก information_schema.columns เป็น C โดยที่ C.table_name = 'yourtable' และ C.column_name เช่น 'Indicator%' สำหรับ xml path (''), type) .value ('text () [1]', 'nvarchar (max)'), 1, 1, '')
rrozema

24

ถ้าคุณมี 150 คอลัมน์ฉันคิดว่า UNPIVOT ไม่ใช่ตัวเลือก คุณสามารถใช้เคล็ดลับ 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

คุณสามารถเขียนไดนามิก SQL ได้ แต่ฉันชอบ xml มากกว่า - สำหรับไดนามิก SQL คุณต้องมีสิทธิ์ในการเลือกข้อมูลโดยตรงจากตารางและนั่นไม่ใช่ตัวเลือกเสมอไป

อัปเดต
เนื่องจากมีความคิดเห็นจำนวนมากฉันคิดว่าฉันจะเพิ่มข้อดีข้อเสียของ xml / dynamic SQL ฉันจะพยายามตั้งใจให้มากที่สุดเท่าที่จะทำได้และไม่พูดถึงความสง่างามและความน่าเกลียด หากคุณมีข้อดีข้อเสียอื่น ๆ ให้แก้ไขคำตอบหรือเขียนในความคิดเห็น

ข้อเสีย

  • มันไม่เร็วเท่าไดนามิก SQL การทดสอบคร่าวๆทำให้ฉันรู้ว่า xml ช้ากว่าไดนามิกประมาณ 2.5 เท่า (เป็นแบบสอบถามเดียวในตาราง ~ 250000 แถวดังนั้นการประมาณนี้จึงไม่มีทางแน่นอน) คุณสามารถเปรียบเทียบได้ด้วยตัวคุณเองถ้าคุณต้องการนี่คือตัวอย่างsqlfiddleบน 100000 แถวมันเป็น 29s (xml) เทียบกับ 14s (ไดนามิก);
  • อาจเป็นเรื่องยากที่จะเข้าใจสำหรับคนที่ไม่คุ้นเคยกับ xpath

ข้อดี

  • เป็นขอบเขตเดียวกับคำถามอื่น ๆ ของคุณและอาจเป็นประโยชน์มาก ตัวอย่างบางส่วนที่อยู่ในใจ
    • คุณสามารถสอบถามinsertedและdeletedตารางภายในทริกเกอร์ของคุณ(ไม่สามารถทำได้ด้วยไดนามิกเลย)
    • ผู้ใช้ไม่จำเป็นต้องมีสิทธิ์ในการเลือกโดยตรงจากตาราง สิ่งที่ฉันหมายถึงคือถ้าคุณเก็บเลเยอร์โพรซีเดอร์ไว้และผู้ใช้มีสิทธิ์เรียกใช้ sp แต่ไม่มีสิทธิ์ในการสืบค้นตารางโดยตรงคุณยังคงสามารถใช้คิวรีนี้ในโพรซีเดอร์ที่เก็บไว้ได้
    • คุณสามารถสอบถามตัวแปรตารางที่คุณเติมไว้ในขอบเขตของคุณ (เพื่อส่งผ่านภายในไดนามิก SQL คุณต้องทำให้เป็นตารางชั่วคราวแทนหรือสร้างประเภทและส่งเป็นพารามิเตอร์ไปยัง SQL แบบไดนามิก
  • คุณสามารถทำแบบสอบถามนี้ภายในฟังก์ชัน (สเกลาร์หรือค่าตาราง) ไม่สามารถใช้ไดนามิก SQL ภายในฟังก์ชันได้

2
คุณกำลังเลือกข้อมูลใดด้วย XML ที่ไม่ต้องเลือกข้อมูลจากตาราง
Aaron Bertrand

1
ตัวอย่างเช่นคุณสามารถตัดสินใจที่จะไม่ให้สิทธิ์ผู้ใช้ในการเลือกข้อมูลจากตาราง แต่เฉพาะในโพรซีเดอร์ที่จัดเก็บที่ทำงานกับตารางดังนั้นฉันสามารถเลือก xml ภายในโพรซีเดอร์ได้ แต่ฉันต้องใช้วิธีแก้ปัญหาบางอย่างหากฉันต้องการใช้ไดนามิก SQL
Roman Pekar

3
หากคุณต้องการให้ผู้ใช้ของคุณสามารถรันโค้ดได้คุณจะต้องให้สิทธิ์เข้าถึงทุกอย่างที่ต้องการเพื่อเรียกใช้โค้ด อย่าสร้างข้อกำหนดที่ไม่มีอยู่เพื่อให้คำตอบของคุณฟังดูดีขึ้น (คุณไม่จำเป็นต้องแสดงความคิดเห็นเกี่ยวกับคำตอบที่แข่งขันกันเพื่อดูคำตอบของคุณหากพวกเขาพบคำตอบนั้นพวกเขาก็สามารถหาคำตอบของคุณได้เช่นกัน)
Aaron Bertrand

2
นอกจากนี้หากเหตุผลของคุณในการใช้ XML คือคุณสามารถวางไว้ในขั้นตอนการจัดเก็บเพื่อหลีกเลี่ยงการให้การเข้าถึงโดยตรงไปยังตารางบางทีตัวอย่างของคุณควรแสดงวิธีใส่ลงในขั้นตอนการจัดเก็บและวิธีการให้สิทธิ์แก่ผู้ใช้เพื่อให้พวกเขา สามารถเรียกใช้งานได้โดยไม่ต้องมีสิทธิ์อ่านตารางพื้นฐาน สำหรับฉันนั่นคือขอบเขตการคืบคลานเพราะคนส่วนใหญ่ที่เขียนข้อความค้นหาบนตารางมีสิทธิ์อ่านตาราง
Aaron Bertrand

2
ฉันจะบอกว่าระยะเวลาที่แตกต่างกัน 10 เท่ามีความสำคัญใช่ และ ~ 8,000 แถวไม่ใช่ "ข้อมูลจำนวนมหาศาล" เราควรดูว่าเกิดอะไรขึ้นกับ 800,000 แถวหรือไม่
Aaron Bertrand

7

เพื่อช่วยผู้อ่านใหม่ฉันได้สร้างตัวอย่างเพื่อให้เข้าใจคำตอบของ @ bluefeet เกี่ยวกับ 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;

3

ฉันต้องการวิธีแก้ปัญหาในการแปลงคอลัมน์เป็นแถวใน Microsoft SQL Server โดยไม่ทราบชื่อ colum (ใช้ในทริกเกอร์) และไม่มี dynamic sql (dynamic sql ช้าเกินไปสำหรับใช้ในทริกเกอร์)

ในที่สุดฉันก็พบวิธีแก้ปัญหานี้ซึ่งใช้งานได้ดี:

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)

อย่างที่คุณเห็นฉันแปลงแถวเป็น XML (Subquery เลือก i, * สำหรับ xml raw ซึ่งจะแปลงคอลัมน์ทั้งหมดเป็นคอลัมน์ xml เดียว)

จากนั้นฉันข้ามใช้ฟังก์ชันกับแต่ละแอตทริบิวต์ XML ของคอลัมน์นี้เพื่อให้ฉันได้หนึ่งแถวต่อแอตทริบิวต์

โดยรวมแล้วสิ่งนี้จะแปลงคอลัมน์เป็นแถวโดยไม่ทราบชื่อคอลัมน์และไม่ใช้ไดนามิก sql เร็วพอสำหรับจุดประสงค์ของฉัน

(แก้ไข: ฉันเพิ่งเห็นคำตอบของ Roman Pekar ด้านบนซึ่งกำลังทำเช่นเดียวกันฉันใช้ไดนามิก sql trigger กับเคอร์เซอร์ก่อนซึ่งช้ากว่าโซลูชันนี้ 10 ถึง 100 เท่า แต่อาจเกิดจากเคอร์เซอร์ไม่ใช่จาก sql แบบไดนามิกอย่างไรก็ตามโซลูชันนี้ง่ายมากเป็นสากลดังนั้นจึงเป็นตัวเลือกที่แน่นอน)

ฉันแสดงความคิดเห็นนี้ไว้ที่นี่เพราะฉันต้องการอ้างอิงคำอธิบายนี้ในโพสต์ของฉันเกี่ยวกับทริกเกอร์การตรวจสอบแบบเต็มซึ่งคุณสามารถดูได้ที่นี่: 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

คุณจะได้รับรายชื่อตารางที่มี rowcounts> 350 คุณสามารถดูรายการโซลูชันของตารางเป็นแถว


2

เพียงเพราะฉันไม่เห็นมันกล่าวถึง

ถ้าเป็นปี 2016+ นี่เป็นอีกทางเลือกหนึ่งในการยกเลิกการใช้งานข้อมูลแบบไดนามิกโดยไม่ต้องใช้ Dynamic SQL จริงๆ

ตัวอย่าง

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

ผลตอบแทน

ID  Item    Value
1   Col1    A
1   Col2    B
2   Col1    R
2   Col2    C
3   Col1    X
3   Col2    D
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.