Máy chủ SQL: Cột để hàng


128

Tìm kiếm giải pháp (hoặc bất kỳ) thanh lịch để chuyển đổi cột thành hàng.

Đây là một ví dụ: Tôi có một bảng với lược đồ sau:

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

Đây là những gì tôi muốn nhận được kết quả:

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

Và các giá trị kết quả sẽ là:

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'

Và như thế..

Điều này có nghĩa không? Bạn có bất cứ đề xuất nào về nơi để tìm và làm thế nào để hoàn thành nó trong T-SQL không?


2
Bạn đã xem xét Pivot / Unp Pivot chưa?
Josh Jay

Vào cuối của nó đã đi với giải pháp của bluefeet. Thanh lịch và chức năng. Cảm ơn mọi người rất nhiều.
Sergei

Câu trả lời:


247

Bạn có thể sử dụng hàm UNPIVOT để chuyển đổi các cột thành các hàng:

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

Lưu ý, các kiểu dữ liệu của các cột mà bạn không xoay vòng phải giống nhau để bạn có thể phải chuyển đổi các kiểu dữ liệu trước khi áp dụng unpOLL.

Bạn cũng có thể sử dụng CROSS APPLYvới UNION ALL để chuyển đổi các cột:

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

Tùy thuộc vào phiên bản SQL Server của bạn, bạn thậm chí có thể sử dụng CROSS ỨNG DỤNG với mệnh đề GIÁ TRỊ:

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

Cuối cùng, nếu bạn có 150 cột để hủy xoay vòng và bạn không muốn mã hóa toàn bộ truy vấn, thì bạn có thể tạo câu lệnh sql bằng SQL động:

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
Đối với những người muốn nhiều hạt và bu lông về UNPIVOTvà / vs. APPLY, bài đăng trên blog năm 2010 này của Brad Schulz (và phần tiếp theo ) là (đẹp).
ruffin

2
Msg 8167, Cấp 16, Trạng thái 1, Dòng 147 Loại cột "blahblah" xung đột với loại cột khác được chỉ định trong danh sách UNPIVOT.
JDPeckham

@JDPeckham Nếu bạn có các kiểu dữ liệu khác nhau, thì bạn cần chuyển đổi chúng thành cùng loại và độ dài trước khi thực hiện hủy trục. Dưới đây là thông tin thêm về điều đó .
Taryn

phương thức xml có một lỗ hổng vì nó không thể giải mã các mã xml như & gt;, & lt; và & amp;. Ngoài ra, hiệu suất có thể được cải thiện đáng kể bằng cách viết lại như sau: select @colsUnp Pivot = Stuff ((select ',' + quotename (C.column_name) là [text ()] từ information_schema.columns là C trong đó C.table_name = 'yourtable' và C.column_name như 'Chỉ số%' cho đường dẫn xml (''), loại) .value ('text () [1]', 'nvarchar (max)'), 1, 1, '')
rrozema

24

tốt Nếu bạn có 150 cột thì tôi nghĩ rằng UNPIVOT không phải là một lựa chọn. Vì vậy, bạn có thể sử dụng thủ thuật 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

Bạn cũng có thể viết SQL động, nhưng tôi thích xml hơn - đối với SQL động, bạn phải có quyền chọn dữ liệu trực tiếp từ bảng và đó không phải lúc nào cũng là một tùy chọn.

CẬP NHẬT
Khi có một ngọn lửa lớn trong các bình luận, tôi nghĩ rằng tôi sẽ thêm một số ưu và nhược điểm của xml / SQL động. Tôi sẽ cố gắng trở nên khách quan nhất có thể và không đề cập đến sự thanh lịch và xấu xí. Nếu bạn có bất kỳ ưu và nhược điểm nào khác, hãy chỉnh sửa câu trả lời hoặc viết bình luận

khuyết điểm

  • không nhanh như SQL động, các thử nghiệm thô đã cho tôi rằng xml chậm hơn khoảng 2,5 lần so với động (đó là một truy vấn trên bảng ~ 250000 hàng, vì vậy ước tính này không chính xác). Bạn có thể tự so sánh nó nếu bạn muốn, đây là ví dụ sqlfiddle , trên 100000 hàng, nó là 29s (xml) so với 14s (động);
  • có thể khó hiểu hơn đối với những người không quen thuộc với xpath;

ưu

  • đó là phạm vi tương tự như các truy vấn khác của bạn và điều đó có thể rất tiện dụng. Một vài ví dụ xuất hiện trong tâm trí
    • bạn có thể truy vấn inserteddeletedcác bảng bên trong trình kích hoạt của mình (hoàn toàn không thể với động);
    • người dùng không phải có quyền trên bảng chọn trực tiếp. Ý tôi là nếu bạn đã lưu trữ lớp thủ tục và người dùng có quyền chạy sp, nhưng không có quyền truy vấn trực tiếp các bảng, bạn vẫn có thể sử dụng truy vấn này trong quy trình được lưu trữ;
    • bạn có thể truy vấn biến bảng bạn đã nhập trong phạm vi của mình (để chuyển nó vào trong SQL động, bạn phải tạo bảng tạm thời thay thế hoặc tạo kiểu và chuyển nó dưới dạng tham số thành SQL động;
  • bạn có thể thực hiện truy vấn này bên trong hàm (giá trị vô hướng hoặc giá trị bảng). Không thể sử dụng SQL động bên trong các hàm;

2
Dữ liệu nào bạn đang chọn với XML không yêu cầu chọn dữ liệu từ bảng?
Aaron Bertrand

1
Ví dụ: bạn có thể quyết định không cấp cho người dùng quyền chọn dữ liệu từ các bảng, mà chỉ trên các quy trình được lưu trữ làm việc với các bảng, vì vậy tôi có thể chọn xml bên trong quy trình, nhưng tôi phải sử dụng một số cách giải quyết nếu tôi muốn sử dụng SQL động
Roman Pekar

3
Nếu bạn muốn người dùng của mình có thể thực thi mã, bạn phải cung cấp cho họ bất kỳ quyền truy cập nào họ cần để thực thi mã. Đừng đưa ra các yêu cầu không tồn tại để làm cho câu trả lời của bạn nghe tốt hơn (bạn cũng không phải nhận xét về câu trả lời cạnh tranh để xem câu trả lời của bạn - nếu họ tìm thấy câu trả lời đó, họ cũng có thể tìm thấy câu trả lời của bạn).
Aaron Bertrand

2
Ngoài ra, nếu biện minh cho việc sử dụng XML của bạn là bạn có thể đặt nó trong một thủ tục được lưu trữ để tránh cấp quyền truy cập trực tiếp vào bảng, có thể ví dụ của bạn sẽ chỉ ra cách đưa nó vào một thủ tục được lưu trữ và cách cấp quyền cho người dùng để họ có thể thực thi nó mà không cần đọc quyền truy cập vào bảng bên dưới. Đối với tôi đó là phạm vi leo, bởi vì hầu hết mọi người viết truy vấn đối với một bảng đã đọc quyền truy cập vào bảng.
Aaron Bertrand

2
Tôi muốn nói rằng một sự khác biệt gấp 10 lần trong thời gian có vấn đề, vâng. Và ~ 8.000 hàng không phải là "lượng dữ liệu lớn" - chúng ta có nên xem điều gì xảy ra so với 800.000 hàng không?
Aaron Bertrand

7

Chỉ để giúp những độc giả mới, tôi đã tạo một ví dụ để hiểu rõ hơn câu trả lời của @ bluefeet về 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

Tôi cần một giải pháp để chuyển đổi các cột thành các hàng trong Microsoft SQL Server, mà không cần biết tên colum (được sử dụng trong trình kích hoạt) và không có sql động (sql động quá chậm để sử dụng trong trình kích hoạt).

Cuối cùng tôi đã tìm thấy giải pháp này, hoạt động tốt:

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)

Như bạn có thể thấy, tôi chuyển đổi hàng thành XML (Subquery select i, * cho xml raw, điều này chuyển đổi tất cả các cột thành một cột xml)

Sau đó, tôi CROSS ÁP DỤNG một hàm cho từng thuộc tính XML của cột này, để tôi nhận được một hàng cho mỗi thuộc tính.

Nhìn chung, điều này chuyển đổi các cột thành các hàng, mà không biết tên cột và không sử dụng sql động. Nó đủ nhanh cho mục đích của tôi.

(Chỉnh sửa: Tôi chỉ thấy câu trả lời của Roman Pekar ở trên, người đang làm điều tương tự. Tôi đã sử dụng trình kích hoạt sql động với các con trỏ trước, chậm hơn 10 đến 100 lần so với giải pháp này, nhưng có thể do con trỏ gây ra, không phải do Dù sao đi nữa, giải pháp này rất đơn giản, vì vậy nó chắc chắn là một lựa chọn).

Tôi để lại nhận xét này tại nơi này, vì tôi muốn tham khảo lời giải thích này trong bài đăng của mình về trình kích hoạt kiểm toán đầy đủ, mà bạn có thể tìm thấy ở đây: 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

Bạn có thể lấy danh sách các bảng có số lượng hàng> 350. Bạn có thể thấy trong danh sách giải pháp của bảng dưới dạng hàng.


2

Chỉ vì tôi không thấy nó được đề cập.

Nếu 2016+, đây vẫn là một tùy chọn khác để tự động hủy dữ liệu mà không sử dụng SQL động.

Thí dụ

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

Trả về

ID  Item    Value
1   Col1    A
1   Col2    B
2   Col1    R
2   Col2    C
3   Col1    X
3   Col2    D
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.