Xoay hàng thành nhiều cột


21

Tôi có một phiên bản SQL Server có máy chủ được liên kết với máy chủ Oracle. Có một bảng trên máy chủ Oracle được gọi PersonOptionschứa dữ liệu sau:

╔══════════╦══════════╗
║ PersonID ║ OptionID ║
╠══════════╬══════════╣
║        1 ║ A        ║
║        1 ║ B        ║
║        2 ║ C        ║
║        3 ║ B        ║
║        4 ║ A        ║
║        4 ║ C        ║
╚══════════╩══════════╝

Tôi cần xoay trục dữ liệu đó để có kết quả:

╔══════════╦═════════╦══════════╦══════════╗
║ PersonID ║ OptionA ║ Option B ║ Option C ║
╠══════════╬═════════╬══════════╬══════════╣
║        1 ║       1 ║        1 ║          ║
║        2 ║         ║          ║        1 ║
║        3 ║         ║        1 ║          ║
║        4 ║       1 ║          ║        1 ║
╚══════════╩═════════╩══════════╩══════════╝

Bất kỳ đề xuất?

Câu trả lời:


20

Có một vài cách mà bạn có thể thực hiện chuyển đổi dữ liệu này. Bạn có quyền truy cập vào PIVOThàm thì đó sẽ là cách dễ nhất, nhưng nếu không thì bạn có thể sử dụng hàm tổng hợp và a CASE.

Phiên bản tổng hợp / trường hợp:

select personid,
  max(case when optionid = 'A' then 1 else 0 end) OptionA,
  max(case when optionid = 'B' then 1 else 0 end) OptionB,
  max(case when optionid = 'C' then 1 else 0 end) OptionC
from PersonOptions
group by personid
order by personid;

Xem SQL Fiddle với bản demo

Pivot tĩnh:

select *
from
(
  select personid, optionid
  from PersonOptions
) src
pivot
(
  count(optionid)
  for optionid in ('A' as OptionA, 'B' OptionB, 'C' OptionC)
) piv
order by personid

Xem SQL Fiddle với bản demo

Phiên bản động:

Hai phiên bản trên hoạt động rất tốt nếu bạn có số lượng giá trị đã biết, nhưng nếu giá trị của bạn không xác định, thì bạn sẽ muốn triển khai sql động và trong Oracle, bạn có thể sử dụng quy trình:

CREATE OR REPLACE procedure dynamic_pivot_po(p_cursor in out sys_refcursor)
as
    sql_query varchar2(1000) := 'select personid ';

    begin
        for x in (select distinct OptionID from PersonOptions order by 1)
        loop
            sql_query := sql_query ||
                ' , min(case when OptionID = '''||x.OptionID||''' then 1 else null end) as Option_'||x.OptionID;

                dbms_output.put_line(sql_query);
        end loop;

        sql_query := sql_query || ' from PersonOptions group by personid order by personid';
        dbms_output.put_line(sql_query);

        open p_cursor for sql_query;
    end;
/

Sau đó, bạn trả lại kết quả, bạn sẽ sử dụng:

variable x refcursor
exec dynamic_pivot_po(:x)
print x

Kết quả giống nhau với tất cả các phiên bản:

| PERSONID | OPTIONA | OPTIONB | OPTIONC |
------------------------------------------
|        1 |       1 |       1 |       0 |
|        2 |       0 |       0 |       1 |
|        3 |       0 |       1 |       0 |
|        4 |       1 |       0 |       1 |

Tuy nhiên, giải pháp Pivot tĩnh giả định chỉ có ba tùy chọn. Điều gì nếu bạn có một số lượng tùy chọn không giới hạn? ABCDEFGHIJK chẳng hạn? Không có cách nào để làm cho trục động với sql thông thường? Thay vì thực hiện các tùy chọn làm tiêu đề cột, chúng ta có thể đặt chúng vào các cột không? Vì vậy, nó sẽ trông như thế này: | CÁ NHÂN | Cột2 | Cột3 | Cột4 | ------------------------------------------ | 1 | Một | B | null | | 2 | C | null | null | | 3 | null | C | null |
Matthew

1
@Matthew bạn phải sử dụng Dynamic Sql như tôi trình bày trong phần cuối của câu trả lời.
Taryn

Cảm ơn đã phản ứng nhanh chóng! Tôi thực sự làm điều này bằng cách tạo một cột mới và nhồi tất cả các tùy chọn trong đó được phân tách bằng dấu phẩy. Col tạo ra từ một truy vấn con chọn từ cùng một bảng where a.personId = a2.personId order by a2.personId for xml path(''). a2 là bảng trong truy vấn con. Sau đó, tôi tách dữ liệu trong excel bằng cách sử dụng văn bản thành các cột với dấu phẩy là dấu phân cách. Tôi đã hy vọng tìm ra cách để làm điều này trong sql thông thường mà không phải viết một thủ tục, nhưng có lẽ không có cách nào. Phải chạy vào lúc này nhưng tôi sẽ cố gắng đăng một ví dụ về điều đó để giải thích rõ hơn.
Matthew

9

Đây sẽ là tương đương trong cú pháp SQL Server. Dựa trên việc tôi đọc các tài liệu Oracle, NULLIFPIVOT dường như có cùng định dạng với SQL Server kin của họ. Thách thức sẽ là danh sách trục cần tĩnh, trừ khi bạn tạo truy vấn động như Itzik thể hiện nhưng tôi không biết liệu điều đó có thể được dịch sang P / SQL không

WITH PersonOptions(PersonID, OptionId) AS
(
    SELECT 1, 'A'
    UNION ALL SELECT 1, 'B'
    UNION ALL SELECT 2, 'C'
    UNION ALL SELECT 3, 'B'
    UNION ALL SELECT 4, 'A'
    UNION ALL SELECT 4, 'C'
)
SELECT
    P.PersonId
,   NULLIF(P.A, 0) AS OptionA
,   NULLIF(P.B, 0) AS OptionB
,   NULLIF(P.C, 0) AS OptionC
FROM
    PersonOptions  PO
    PIVOT 
    (
        COUNT(PO.OptionId)
        FOR OPtionId IN (A, B, C)
    )  P;

5

Tôi thích xoay vòng truy vấn bằng tay, nhưng bạn cũng có thể sử dụng PIVOT.

SELECT PersonID,
MAX(CASE WHEN OptionId ='A' THEN 1 END) AS OptionA,
MAX(CASE WHEN OptionId ='B' THEN 1 END) AS OptionB, 
MAX(CASE WHEN OptionId ='C' THEN 1 END) AS OptionC
FROM PersonOptions
GROUP BY PersonID

1
Hãy giải thích điều này chỉ một chút nữa. Trục này cung cấp những gì mà những người khác có thể không? Và khi nào thì phá vỡ? Hãy nhớ rằng bạn đang trả lời cho hậu thế, không phải cho người có chuyên môn về tên miền cụ thể trong chính những điều bạn biết.
jcolebrand

2
@jcolebrand: Đó là nhiều hơn về sở thích cá nhân - Bản thân tôi nghĩ rằng PIVOTcú pháp này phức tạp hơn so với cách tiếp cận tôi sử dụng. Tuy nhiên, tôi biết cả hai đều cho kết quả như nhau và tôi đồng ý những người khác có thể nghĩ ngược lại.
a1ex07

1
Gợi ý: Sử dụng nút chỉnh sửa ;-) ~ Chúng tôi muốn khuyến khích nhiều hơn phản hồi trả lời mã.
jcolebrand
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.