Pivot แถวในหลายคอลัมน์


21

ฉันมีอินสแตนซ์ SQL Server ที่มีเซิร์ฟเวอร์ที่เชื่อมโยงกับเซิร์ฟเวอร์ Oracle มีตารางบนเซิร์ฟเวอร์ Oracle ที่เรียกว่าPersonOptionsซึ่งมีข้อมูลต่อไปนี้:

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

ฉันต้องหมุนข้อมูลนั้นเพื่อผลลัพธ์คือ:

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

ข้อเสนอแนะใด ๆ

คำตอบ:


20

มีสองสามวิธีที่คุณสามารถทำการแปลงข้อมูลนี้ได้ คุณมีการเข้าถึงPIVOTฟังก์ชั่นแล้วที่จะเป็นวิธีที่ง่ายที่สุด CASEแต่ถ้าไม่แล้วคุณสามารถใช้ฟังก์ชันการรวมและ

รุ่นรวม / เคส:

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;

ดูSQL Fiddle พร้อมเดโม

Pivot แบบคงที่:

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

ดูSQL Fiddle พร้อมเดโม

รุ่นแบบไดนามิก:

ทั้งสองเวอร์ชันด้านบนใช้งานได้ดีถ้าคุณมีจำนวนค่าที่ทราบ แต่ถ้าค่าของคุณไม่เป็นที่รู้จักคุณจะต้องใช้ dynamic sql และใน Oracle คุณสามารถใช้โพรซีเดอร์:

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

จากนั้นคุณส่งคืนผลลัพธ์คุณจะใช้:

variable x refcursor
exec dynamic_pivot_po(:x)
print x

ผลลัพธ์จะเหมือนกันกับทุกรุ่น:

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

อย่างไรก็ตามวิธีแก้ไขปัญหา Pivot แบบคงที่ถือว่ามีเพียงสามตัวเลือก ถ้าคุณมีตัวเลือกไม่ จำกัด จำนวนล่ะ ตัวอย่างเช่น ABCDEFGHIJK? ไม่มีวิธีที่จะทำให้ pivot ไดนามิกด้วย sql ปกติหรือไม่? แทนที่จะทำให้ตัวเลือกเป็นส่วนหัวของคอลัมน์เราสามารถใส่มันลงในคอลัมน์ได้หรือไม่? ดังนั้นจะมีลักษณะเช่นนี้: | PERSONID | คอลัมน์ 2 | คอลัมน์ 3 | คอลัมน์ 4 | ------------------------------------------ | | 1 | A | B | null | | 2 | C | null | null | | 3 | null | C | null |
แมทธิว

1
@ Matewew คุณจะต้องใช้ Dynamic Sql ตามที่ฉันแสดงในส่วนสุดท้ายของคำตอบ
Taryn

ขอบคุณสำหรับการตอบสนองอย่างรวดเร็ว! จริงๆแล้วฉันทำสิ่งนี้ด้วยการสร้างคอลัมน์ใหม่และการบรรจุตัวเลือกทั้งหมดในนั้นคั่นด้วยเครื่องหมายจุลภาค where a.personId = a2.personId order by a2.personId for xml path('')เทือกเขาสร้างจากแบบสอบถามย่อยเลือกจากตารางเดียวกัน a2 เป็นตารางในแบบสอบถามย่อย จากนั้นฉันแยกข้อมูลใน excel โดยใช้ข้อความเป็นคอลัมน์ที่มีเครื่องหมายจุลภาคเป็นตัวคั่น ฉันหวังว่าจะหาวิธีทำเช่นนี้ใน sql ปกติโดยไม่ต้องเขียนขั้นตอน แต่อาจจะไม่มีวิธี ต้องทำงานในขณะนี้ แต่ฉันจะพยายามโพสต์ตัวอย่างเพื่ออธิบายให้ดีขึ้น
แมทธิว

9

นี่จะเทียบเท่าในไวยากรณ์ของ SQL Server จากการอ่านเอกสาร Oracle ของฉันNULLIFและPIVOTดูเหมือนจะมีรูปแบบเดียวกันกับ SQL Server เครือญาติ ความท้าทายจะเป็นรายการสาระสำคัญซึ่งต้องเป็นแบบคงที่จนกว่าคุณจะสร้างแบบสอบถามแบบไดนามิกตามที่ Itzik สาธิตแต่ฉันไม่รู้ว่าจะแปลเป็น P / SQL ได้หรือไม่

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

ฉันชอบที่จะสอบถามแบบสอบถามด้วยตนเอง แต่คุณก็สามารถใช้ได้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
อย่าลังเลที่จะอธิบายเรื่องนี้เพียงเล็กน้อย เดือยทำอะไรให้คนอื่นไม่ได้? และเมื่อไรที่จะพัง? จำไว้ว่าคุณกำลังตอบคำถามรุ่นหลังไม่ใช่สำหรับคนที่มีความเชี่ยวชาญเฉพาะด้านในสิ่งที่คุณรู้จักเช่นกัน
jcolebrand

2
@ jcolebrand: มันเป็นเรื่องของความชอบส่วนบุคคลมากกว่า - ฉันคิดว่าPIVOTมันซับซ้อนกว่าเมื่อเปรียบเทียบกับวิธีที่ฉันใช้ อย่างไรก็ตามฉันรู้ว่าพวกเขาทั้งคู่ให้ผลเหมือนกันและฉันก็เห็นด้วยที่คนอื่นอาจคิดตรงกันข้าม
a1ex07

1
คำแนะนำ: ใช้ปุ่มแก้ไข ;-) ~ เราต้องการที่จะส่งเสริมให้มากขึ้นกว่าการตอบสนองรหัสคำตอบ
jcolebrand
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.