You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

Oracle 12c R1透视表行转列及总计、预订统计等需求咨询

Oracle 12c R1 PIVOT透视表:行总计、列总计、预订数统计及动态列问题解答

嘿,我来帮你搞定这几个Oracle透视表的问题,刚好对12c R1的PIVOT特性挺熟的!咱们一个个来解决:

1. 如何添加行总计

行总计就是每个客户在所有酒店的预订总数,最简单的方式是在PIVOT查询结果里直接计算各酒店列的和。假设你的CUST_HOTEL_VIEW包含CUST_ID(客户ID)、HOTEL(酒店名称)、BOOKING_DATE(预订日期,用来计数)这类字段,示例代码如下:

SELECT 
    CUST_ID,
    NVL(HOTEL_A, 0) AS HOTEL_A,  -- 用NVL把NULL转成0,避免求和出错
    NVL(HOTEL_B, 0) AS HOTEL_B,
    NVL(HOTEL_C, 0) AS HOTEL_C,
    -- 计算行总计:所有酒店预订数相加
    (NVL(HOTEL_A, 0) + NVL(HOTEL_B, 0) + NVL(HOTEL_C, 0)) AS 行总计
FROM (
    -- 先按客户和酒店分组,统计每个客户在每个酒店的预订数
    SELECT CUST_ID, HOTEL, COUNT(*) AS BOOKINGS 
    FROM CUST_HOTEL_VIEW 
    GROUP BY CUST_ID, HOTEL
)
PIVOT (
    SUM(BOOKINGS)  -- 聚合函数,这里用SUM是因为分组后每个组的BOOKINGS是1,SUM和COUNT效果一样
    FOR HOTEL IN ('HOTEL_A' AS HOTEL_A, 'HOTEL_B' AS HOTEL_B, 'HOTEL_C' AS HOTEL_C)
);

2. 如何添加列总计

列总计是所有客户在单个酒店的预订总数,以及所有酒店的总预订数。可以用CTE先生成基础的PIVOT结果,再用UNION ALL追加总计行:

WITH PIVOT_BASE AS (
    SELECT 
        CUST_ID,
        NVL(HOTEL_A, 0) AS HOTEL_A,
        NVL(HOTEL_B, 0) AS HOTEL_B,
        NVL(HOTEL_C, 0) AS HOTEL_C,
        (NVL(HOTEL_A, 0) + NVL(HOTEL_B, 0) + NVL(HOTEL_C, 0)) AS 行总计
    FROM (
        SELECT CUST_ID, HOTEL, COUNT(*) AS BOOKINGS 
        FROM CUST_HOTEL_VIEW 
        GROUP BY CUST_ID, HOTEL
    )
    PIVOT (
        SUM(BOOKINGS)
        FOR HOTEL IN ('HOTEL_A' AS HOTEL_A, 'HOTEL_B' AS HOTEL_B, 'HOTEL_C' AS HOTEL_C)
    )
)
-- 先输出普通客户数据,再追加列总计行
SELECT * FROM PIVOT_BASE
UNION ALL
SELECT 
    '列总计' AS CUST_ID,  -- 标记总计行
    SUM(HOTEL_A) AS HOTEL_A,
    SUM(HOTEL_B) AS HOTEL_B,
    SUM(HOTEL_C) AS HOTEL_C,
    SUM(行总计) AS 行总计
FROM PIVOT_BASE
-- 让总计行排在最后
ORDER BY CASE WHEN CUST_ID = '列总计' THEN 1 ELSE 0 END, CUST_ID;

3. 如何统计客户已预订酒店数量

这个是统计每个客户有多少个不同的酒店有过预订(即预订数>0的酒店数量),可以用窗口函数在PIVOT前先计算好,或者在PIVOT后用CASE语句统计非零列:

方法1:PIVOT前用窗口函数(推荐)

SELECT 
    CUST_ID,
    NVL(HOTEL_A, 0) AS HOTEL_A,
    NVL(HOTEL_B, 0) AS HOTEL_B,
    NVL(HOTEL_C, 0) AS HOTEL_C,
    (NVL(HOTEL_A, 0) + NVL(HOTEL_B, 0) + NVL(HOTEL_C, 0)) AS 行总计,
    已预订酒店数量
FROM (
    SELECT 
        CUST_ID,
        HOTEL,
        COUNT(*) AS BOOKINGS,
        -- 窗口函数:按客户分组,统计不同酒店的数量
        COUNT(DISTINCT HOTEL) OVER (PARTITION BY CUST_ID) AS 已预订酒店数量
    FROM CUST_HOTEL_VIEW 
    GROUP BY CUST_ID, HOTEL
)
PIVOT (
    SUM(BOOKINGS)
    FOR HOTEL IN ('HOTEL_A' AS HOTEL_A, 'HOTEL_B' AS HOTEL_B, 'HOTEL_C' AS HOTEL_C)
)
-- 去重,因为每个客户的多行数据里已预订酒店数量是一样的
GROUP BY CUST_ID, HOTEL_A, HOTEL_B, HOTEL_C, 已预订酒店数量;

方法2:PIVOT后用CASE统计

SELECT 
    *,
    -- 统计非零的酒店列数量
    CASE WHEN HOTEL_A > 0 THEN 1 ELSE 0 END +
    CASE WHEN HOTEL_B > 0 THEN 1 ELSE 0 END +
    CASE WHEN HOTEL_C > 0 THEN 1 ELSE 0 END AS 已预订酒店数量
FROM (
    SELECT 
        CUST_ID,
        NVL(HOTEL_A, 0) AS HOTEL_A,
        NVL(HOTEL_B, 0) AS HOTEL_B,
        NVL(HOTEL_C, 0) AS HOTEL_C,
        (NVL(HOTEL_A, 0) + NVL(HOTEL_B, 0) + NVL(HOTEL_C, 0)) AS 行总计
    FROM (
        SELECT CUST_ID, HOTEL, COUNT(*) AS BOOKINGS 
        FROM CUST_HOTEL_VIEW 
        GROUP BY CUST_ID, HOTEL
    )
    PIVOT (
        SUM(BOOKINGS)
        FOR HOTEL IN ('HOTEL_A' AS HOTEL_A, 'HOTEL_B' AS HOTEL_B, 'HOTEL_C' AS HOTEL_C)
    )
);

4. PIVOT的FOR HOTEL IN子句能否使用子查询?

很遗憾,Oracle 12c R1的静态PIVOT不支持在IN子句里用子查询,必须指定静态的字面量值(比如'HOTEL_A''HOTEL_B')。如果你的酒店列表是动态变化的,需要用动态SQL来生成PIVOT查询:

DECLARE
    v_hotel_list VARCHAR2(1000);  -- 存储拼接好的酒店列表
    v_sql VARCHAR2(2000);         -- 存储动态生成的SQL语句
BEGIN
    -- 第一步:从视图中获取所有不同的酒店名称,拼接成PIVOT需要的格式
    SELECT LISTAGG('''' || HOTEL || ''' AS ' || HOTEL, ', ') WITHIN GROUP (ORDER BY HOTEL)
    INTO v_hotel_list
    FROM (SELECT DISTINCT HOTEL FROM CUST_HOTEL_VIEW);
    
    -- 第二步:构建完整的动态PIVOT SQL
    v_sql := '
        SELECT 
            CUST_ID,
            ' || v_hotel_list || ',
            -- 动态计算行总计:把所有酒店列相加
            ' || LISTAGG('NVL(' || HOTEL || ', 0)', ' + ') WITHIN GROUP (ORDER BY HOTEL) || ' AS 行总计
        FROM (
            SELECT CUST_ID, HOTEL, COUNT(*) AS BOOKINGS 
            FROM CUST_HOTEL_VIEW 
            GROUP BY CUST_ID, HOTEL
        )
        PIVOT (
            SUM(BOOKINGS)
            FOR HOTEL IN (' || v_hotel_list || ')
        )';
    
    -- 输出生成的SQL(可以复制到SQL*Plus里执行)
    DBMS_OUTPUT.PUT_LINE(v_sql);
    
    -- 如果要直接执行并返回结果,可以用REF CURSOR:
    -- OPEN :your_ref_cursor FOR v_sql;
END;
/

执行这段PL/SQL后,会输出完整的动态PIVOT查询语句,你可以直接复制执行,或者用REF CURSOR在程序里返回结果。

内容的提问来源于stack exchange,提问作者Richa

火山引擎 最新活动