将行旋转为多列


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 ║
╚══════════╩═════════╩══════════╩══════════╝

有什么建议么?

Answers:


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

静态枢轴:

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

动态版本:

如果您拥有已知数量的值,则上述两个版本非常有用,但是如果您的值未知,那么您将要实现动态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 |

但是,Static Pivot解决方案假定只有三个选项。如果您有无限数量的选择,该怎么办?例如ABCDEFGHIJK?有没有办法让常规SQL动态化数据透视表?除了将选项设为列标题之外,我们还可以将它们放在列中吗?所以它看起来像这样:人名| 第2列| 第3列| 列4 | ------------------------------------------ | 1 | A | B | null | | 2 | C | null | null | | 3 | 空| C | 空|
马修

1
@Matthew,您必须使用Dynamic Sql,如我在答案的最后一部分中所演示的。
塔林

感谢您及时的回复!实际上,我是通过创建一个新列并将所有选项填充在其中并以逗号分隔来实现的。col通过从相同表中选择的子查询生成where a.personId = a2.personId order by a2.personId for xml path('')。a2是子查询中的表。然后,我在excel中使用文本将数据分隔为以逗号作为分隔符的列。我希望找到一种无需编写过程即可在常规sql中执行此操作的方法,但也许没有办法。目前必须运行,但是我将尝试发布一个示例以更好地解释。
马修

9

这将等效于SQL Server语法。根据我对Oracle文档的阅读,NULLIFPIVOT似乎与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
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.