Oracle 查询中的动态枢轴使用情况

Posted

技术标签:

【中文标题】Oracle 查询中的动态枢轴使用情况【英文标题】:Dynamic Pivot Usage in Oracle Query 【发布时间】:2019-08-19 07:28:36 【问题描述】:

表中的行数 = 列数 我想对每一行进行分组并将每个特征值转换为一列。 我不想手动添加pivot列值或使用XML数据。我必须写下我是如何制作它们的。因为它可能有助于理解。

第一个查询:

SELECT So.Order_No,
   So.Release_No,
   So.Sequence_No,
   So.Part_No,
   Csv.Characteristic_Id,
   Csv.Characteristic_Id || '-' || Config_Characteristic_Api.Get_Description(Csv.Characteristic_Id) Characteristic_Desc,
   Csv.Characteristic_Value,
   Csv.Characteristic_Value || ' - ' || Config_Option_Value_Api.Get_Description(Csv.Characteristic_Id, Csv.Characteristic_Value) Characteristic_Value_Desc
FROM Shop_Ord So, Config_Spec_Value Csv
 WHERE So.Part_No = Csv.Part_No AND So.Configuration_Id = Csv.Configuration_Id AND So.Configuration_Id != '*' AND So.Need_Date > '01.01.2019' AND
   So.Part_No LIKE 'XL%'
ORDER BY So.Order_No, Csv.Characteristic_Value;

结果:

ORDER_NO    RELEASE_NO  SEQUENCE_NO   PART_NO   CHARACTERISTIC_ID   CHARACTERISTIC_DESC                     CHARACTERISTIC_VALUE        CHARACTERISTIC_VALUE_DESC
---------  -----------  -----------   --------  ------------------  ------------------------------          ---------------------     ----------------------------
E1196        1             1          XL106      KK-001             KK-001-Abkant Kontrol Ünitesi           KK-001-002                  KK-001-002 - Esa S 630 CNC 2D Grafik Ekran - Dokunmatik
E1196        1             1          XL106      KK-005             KK-005-Elektrik Sistemi                 KK-005-002                  KK-005-002 - 380 V-660 V, 50-60 Hz, 3 Ph
E1196        1             1          XL106      KK-006             KK-006-Elektrik Panosu Soğutma Sistemi  KK-006-001                  KK-006-001 - Fanlı
E1196        1             1          XL106      KK-008             KK-008-Abkant Ön Destek Sayısı          KK-008-001                  KK-008-001 - Standart
...

枢轴:

WITH Pivot_ AS
 (SELECT So.Order_No,
     So.Release_No,
     So.Sequence_No,
     So.Part_No,
     Csv.Characteristic_Id,
     Csv.Characteristic_Value
FROM Shop_Ord So, Config_Spec_Value Csv
   WHERE So.Part_No = Csv.Part_No AND So.Configuration_Id = Csv.Configuration_Id
     AND So.Configuration_Id != '*' AND So.Need_Date > '01.01.2019' AND So.Part_No LIKE 'XL%'
   ORDER BY So.Order_No)

SELECT *
  FROM Pivot_
Pivot (MAX(Characteristic_Value) FOR(Characteristic_Id) IN('KK-001', 'KK-002'));

结果:

ORDER_NO    RELEASE_NO  SEQUENCE_NO     PART_NO     'KK-001'        'KK-002'
---------   ---------   ------------    -------    ---------        ---------
E1196           1           1           XL106       KK-001-002      00
E1334           1           1           XL107       KK-001-002      00
E1379           1           1           XL106       KK-001-002      KK-002-001
E1470           2           1           XL107       KK-001-002      KK-002-001
...

PIVOT XML:

WITH Pivot_ AS
 (SELECT So.Order_No, So.Release_No, So.Sequence_No, So.Part_No, Csv.Characteristic_Id, Csv.Characteristic_Value
FROM Shop_Ord So, Config_Spec_Value Csv
   WHERE So.Part_No = Csv.Part_No AND So.Configuration_Id = Csv.Configuration_Id AND So.Configuration_Id != '*' AND So.Need_Date > '01.01.2019' AND
     So.Part_No LIKE 'XL%'
   ORDER BY So.Order_No)

SELECT *
  FROM Pivot_
Pivot Xml (MAX(Characteristic_Value) FOR(Characteristic_Id) IN (SELECT Cs_.Characteristic_Id
                                                     FROM Config_Spec_Value Cs_
                                                    WHERE Cs_.Part_No = '1065821'
                                                    GROUP BY Cs_.Characteristic_Id));

结果:

ORDER_NO    RELEASE_NO  SEQUENCE_NO PART_NO     CHARACTERISTIC_ID_XML
----------  ----------  ----------- ---------   ------------
*1642           1           1       XL106       <XMLTYPE>
*1643           1           1       XL106       <XMLTYPE>
*1644           1           1       XL106       <XMLTYPE>
...

毕竟我想在XML Pivot 上直接查看这些列。

ORDER_NO    RELEASE_NO  SEQUENCE_NO PART_NO     KK-001      KK-002      KK-003      ....
----------  ----------  ----------- ---------   ---------   ---------   ---------   ---------
*1642           1           1       XL106       KK-001-01   NULL        NULL        ...
*1643           1           1       XL106       KK-001-04   KK-002-00   KK-003-08   ...
*1644           1           1       XL106       KK-001-02   KK-002-10   NULL        ...
...

最好的问候。

【问题讨论】:

SELECT 中的列数是固定的。因此,不可能在单个选择中动态地执行此操作。您必须先选择 listagg(''''||Characteristic_Id||'''',',') 以获取您的 id 列表,然后在 PIVOT 查询中使用该列表。 谢谢。我添加了 Within GROUP 和 whrere rownum=1 但 ORA-00963 您可以以列的形式提取xml值。 SELECT EXTRACTVALUE(COLUMN_VALUE, '/XPATH') AS COL1 FROM TABLE(XMLSEQUENCE(("PIVOT QUERY").EXTRACT('//'))); 谢谢@D.J.拜托,你能举个例子吗? 【参考方案1】:

我找到了解决这个问题的方法。我想分享。 由于我的问题已经很久了,我可以通过不同的例子来解释它。

    CREATE OR REPLACE TYPE pivotimpl AS OBJECT
    (
        ret_type anytype, -- The return type of the table function
        stmt     VARCHAR2(32767),
        fmt      VARCHAR2(32767),
        cur      INTEGER,
        STATIC FUNCTION odcitabledescribe
        (
            rtype  OUT anytype,
            p_stmt IN VARCHAR2,
            p_fmt  IN VARCHAR2 := 'upper(@p@)',
            dummy  IN NUMBER := 0
        ) RETURN NUMBER,
        STATIC FUNCTION odcitableprepare
        (
            sctx   OUT pivotimpl,
            ti     IN sys.odcitabfuncinfo,
            p_stmt IN VARCHAR2,
            p_fmt  IN VARCHAR2 := 'upper(@p@)',
            dummy  IN NUMBER := 0
        ) RETURN NUMBER,
        STATIC FUNCTION odcitablestart
        (
            sctx   IN OUT pivotimpl,
            p_stmt IN VARCHAR2,
            p_fmt  IN VARCHAR2 := 'upper(@p@)',
            dummy  IN NUMBER := 0
        ) RETURN NUMBER,
        MEMBER FUNCTION odcitablefetch
        (
            SELF   IN OUT pivotimpl,
            nrows  IN NUMBER,
            outset OUT anydataset
        ) RETURN NUMBER,
        MEMBER FUNCTION odcitableclose(SELF IN pivotimpl) RETURN NUMBER
    );

    CREATE OR REPLACE TYPE BODY pivotimpl AS
        STATIC FUNCTION odcitabledescribe
        (
            rtype  OUT anytype,
            p_stmt IN VARCHAR2,
            p_fmt  IN VARCHAR2 := 'upper(@p@)',
            dummy  IN NUMBER
        ) RETURN NUMBER IS
            atyp     anytype;
            cur      INTEGER;
            numcols  NUMBER;
            desc_tab dbms_sql.desc_tab2;
            rc       SYS_REFCURSOR;
            t_c2     VARCHAR2(32767);
            t_fmt    VARCHAR2(1000);
        BEGIN
            cur := dbms_sql.open_cursor;
            dbms_sql.parse(cur, p_stmt, dbms_sql.native);
            dbms_sql.describe_columns2(cur, numcols, desc_tab);
            dbms_sql.close_cursor(cur);
            --
            anytype.begincreate(dbms_types.typecode_object, atyp);
            FOR i IN 1 .. numcols - 2
            LOOP
                atyp.addattr(desc_tab(i).col_name,
                             CASE desc_tab(i).col_type
                                  WHEN 1 THEN
                                   dbms_types.typecode_varchar2
                                  WHEN 2 THEN
                                   dbms_types.typecode_number
                                  WHEN 9 THEN
                                   dbms_types.typecode_varchar2
                                  WHEN 11 THEN
                                   dbms_types.typecode_varchar2 -- show rowid as varchar2
                                  WHEN 12 THEN
                                   dbms_types.typecode_date
                                  WHEN 208 THEN
                                   dbms_types.typecode_varchar2 -- show urowid as varchar2
                                  WHEN 96 THEN
                                   dbms_types.typecode_char
                                  WHEN 180 THEN
                                   dbms_types.typecode_timestamp
                                  WHEN 181 THEN
                                   dbms_types.typecode_timestamp_tz
                                  WHEN 231 THEN
                                   dbms_types.typecode_timestamp_ltz
                                  WHEN 182 THEN
                                   dbms_types.typecode_interval_ym
                                  WHEN 183 THEN
                                   dbms_types.typecode_interval_ds
                              END, desc_tab(i).col_precision, desc_tab(i).col_scale,
                             CASE desc_tab(i).col_type
                                  WHEN 11 THEN
                                   18 -- for rowid col_max_len = 16, and 18 characters are shown
                                  ELSE
                                   desc_tab(i).col_max_len
                              END, desc_tab(i).col_charsetid, desc_tab(i).col_charsetform);
            END LOOP;
            IF instr(p_fmt, '@p@') > 0 THEN
                t_fmt := p_fmt;
            ELSE
                t_fmt := '@p@';
            END IF;
            OPEN rc FOR REPLACE('select distinct ' || t_fmt || '
                              from( ' || p_stmt || ' )
                              order by ' || t_fmt, '@p@', desc_tab(numcols - 1).col_name);
            LOOP
                FETCH rc
                    INTO t_c2;
                EXIT WHEN rc%NOTFOUND;
                atyp.addattr(t_c2,
                             CASE desc_tab(numcols).col_type WHEN 1 THEN dbms_types.typecode_varchar2 WHEN 2 THEN dbms_types.typecode_number WHEN 9 THEN
                              dbms_types.typecode_varchar2 WHEN 11 THEN dbms_types.typecode_varchar2 -- show rowid as varchar2
                              WHEN 12 THEN dbms_types.typecode_date WHEN 208 THEN dbms_types.typecode_urowid WHEN 96 THEN dbms_types.typecode_char WHEN 180 THEN
                              dbms_types.typecode_timestamp WHEN 181 THEN dbms_types.typecode_timestamp_tz WHEN 231 THEN dbms_types.typecode_timestamp_ltz WHEN 182 THEN
                              dbms_types.typecode_interval_ym WHEN 183 THEN dbms_types.typecode_interval_ds END, desc_tab(numcols).col_precision,
                             desc_tab(numcols).col_scale,
                             CASE desc_tab(numcols).col_type WHEN 11 THEN 18 -- for rowid col_max_len = 16, and 18 characters are shown
                              ELSE desc_tab(numcols).col_max_len END, desc_tab(numcols).col_charsetid, desc_tab(numcols).col_charsetform);
            END LOOP;
            CLOSE rc;
            atyp.endcreate;
            anytype.begincreate(dbms_types.typecode_table, rtype);
            rtype.setinfo(NULL, NULL, NULL, NULL, NULL, atyp, dbms_types.typecode_object, 0);
            rtype.endcreate();
            RETURN odciconst.success;
        EXCEPTION
            WHEN OTHERS THEN
                RETURN odciconst.error;
        END;
        --
        STATIC FUNCTION odcitableprepare
        (
            sctx   OUT pivotimpl,
            ti     IN sys.odcitabfuncinfo,
            p_stmt IN VARCHAR2,
            p_fmt  IN VARCHAR2 := 'upper(@p@)',
            dummy  IN NUMBER
        ) RETURN NUMBER IS
            prec     PLS_INTEGER;
            scale    PLS_INTEGER;
            len      PLS_INTEGER;
            csid     PLS_INTEGER;
            csfrm    PLS_INTEGER;
            elem_typ anytype;
            aname    VARCHAR2(30);
            tc       PLS_INTEGER;
        BEGIN
            tc := ti.rettype.getattreleminfo(1, prec, scale, len, csid, csfrm, elem_typ, aname);
            --
            IF instr(p_fmt, '@p@') > 0 THEN
                sctx := pivotimpl(elem_typ, p_stmt, p_fmt, NULL);
            ELSE
                sctx := pivotimpl(elem_typ, p_stmt, '@p@', NULL);
            END IF;
            RETURN odciconst.success;
        END;
        --
        STATIC FUNCTION odcitablestart
        (
            sctx   IN OUT pivotimpl,
            p_stmt IN VARCHAR2,
            p_fmt  IN VARCHAR2 := 'upper(@p@)',
            dummy  IN NUMBER
        ) RETURN NUMBER IS
            cur         INTEGER;
            numcols     NUMBER;
            desc_tab    dbms_sql.desc_tab2;
            t_stmt      VARCHAR2(32767);
            type_code   PLS_INTEGER;
            prec        PLS_INTEGER;
            scale       PLS_INTEGER;
            len         PLS_INTEGER;
            csid        PLS_INTEGER;
            csfrm       PLS_INTEGER;
            schema_name VARCHAR2(30);
            type_name   VARCHAR2(30);
            version     VARCHAR2(30);
            attr_count  PLS_INTEGER;
            attr_type   anytype;
            attr_name   VARCHAR2(100);
            dummy2      INTEGER;
        BEGIN
            cur := dbms_sql.open_cursor;
            dbms_sql.parse(cur, p_stmt, dbms_sql.native);
            dbms_sql.describe_columns2(cur, numcols, desc_tab);
            dbms_sql.close_cursor(cur);
            --
            FOR i IN 1 .. numcols - 2
            LOOP
                t_stmt := t_stmt || ', "' || desc_tab(i).col_name || '"';
            END LOOP;
            --
            type_code := sctx.ret_type.getinfo(prec, scale, len, csid, csfrm, schema_name, type_name, version, attr_count);
            FOR i IN numcols - 1 .. attr_count
            LOOP
                type_code := sctx.ret_type.getattreleminfo(i, prec, scale, len, csid, csfrm, attr_type, attr_name);
                t_stmt    := t_stmt || REPLACE(', max( decode( ' || sctx.fmt || ', ''' || attr_name || ''', ' || desc_tab(numcols).col_name || ' ) )', '@p@',
                                               desc_tab(numcols - 1).col_name);
            END LOOP;
            t_stmt := 'select ' || substr(t_stmt, 2) || ' from ( ' || sctx.stmt || ' )';
            FOR i IN 1 .. numcols - 2
            LOOP
                IF i = 1 THEN
                    t_stmt := t_stmt || ' group by "' || desc_tab(i).col_name || '"';
                ELSE
                    t_stmt := t_stmt || ', "' || desc_tab(i).col_name || '"';
                END IF;
            END LOOP;
            --
            dbms_output.put_line(t_stmt);
            sctx.cur := dbms_sql.open_cursor;
            dbms_sql.parse(sctx.cur, t_stmt, dbms_sql.native);
            FOR i IN 1 .. attr_count
            LOOP
                type_code := sctx.ret_type.getattreleminfo(i, prec, scale, len, csid, csfrm, attr_type, attr_name);
                CASE type_code
                    WHEN dbms_types.typecode_char THEN
                        dbms_sql.define_column(sctx.cur, i, 'x', 32767);
                    WHEN dbms_types.typecode_varchar2 THEN
                        dbms_sql.define_column(sctx.cur, i, 'x', 32767);
                    WHEN dbms_types.typecode_number THEN
                        dbms_sql.define_column(sctx.cur, i, CAST(NULL AS NUMBER));
                    WHEN dbms_types.typecode_date THEN
                        dbms_sql.define_column(sctx.cur, i, CAST(NULL AS DATE));
                    WHEN dbms_types.typecode_urowid THEN
                        dbms_sql.define_column(sctx.cur, i, CAST(NULL AS UROWID));
                    WHEN dbms_types.typecode_timestamp THEN
                        dbms_sql.define_column(sctx.cur, i, CAST(NULL AS TIMESTAMP));
                    WHEN dbms_types.typecode_timestamp_tz THEN
                        dbms_sql.define_column(sctx.cur, i, CAST(NULL AS TIMESTAMP WITH TIME ZONE));
                    WHEN dbms_types.typecode_timestamp_ltz THEN
                        dbms_sql.define_column(sctx.cur, i, CAST(NULL AS TIMESTAMP WITH LOCAL TIME ZONE));
                    WHEN dbms_types.typecode_interval_ym THEN
                        dbms_sql.define_column(sctx.cur, i, CAST(NULL AS INTERVAL YEAR TO MONTH));
                    WHEN dbms_types.typecode_interval_ds THEN
                        dbms_sql.define_column(sctx.cur, i, CAST(NULL AS INTERVAL DAY TO SECOND));
                END CASE;
            END LOOP;
            dummy2 := dbms_sql.execute(sctx.cur);
            RETURN odciconst.success;
        END;
        --
        MEMBER FUNCTION odcitablefetch
        (
            SELF   IN OUT pivotimpl,
            nrows  IN NUMBER,
            outset OUT anydataset
        ) RETURN NUMBER IS
            c1_col_type PLS_INTEGER;
            type_code   PLS_INTEGER;
            prec        PLS_INTEGER;
            scale       PLS_INTEGER;
            len         PLS_INTEGER;
            csid        PLS_INTEGER;
            csfrm       PLS_INTEGER;
            schema_name VARCHAR2(30);
            type_name   VARCHAR2(30);
            version     VARCHAR2(30);
            attr_count  PLS_INTEGER;
            attr_type   anytype;
            attr_name   VARCHAR2(100);
            v1          VARCHAR2(32767);
            n1          NUMBER;
            d1          DATE;
            ur1         UROWID;
            ids1        INTERVAL DAY TO SECOND;
            iym1        INTERVAL YEAR TO MONTH;
            ts1         TIMESTAMP;
            tstz1       TIMESTAMP WITH TIME ZONE;
            tsltz1      TIMESTAMP WITH LOCAL TIME ZONE;
        BEGIN
            outset := NULL;
            IF nrows < 1 THEN
                -- is this possible???
                RETURN odciconst.success;
            END IF;
            --
            dbms_output.put_line('fetch');
            IF dbms_sql.fetch_rows(self.cur) = 0 THEN
                RETURN odciconst.success;
            END IF;
            --
            dbms_output.put_line('done');
            type_code := self.ret_type.getinfo(prec, scale, len, csid, csfrm, schema_name, type_name, version, attr_count);
            anydataset.begincreate(dbms_types.typecode_object, self.ret_type, outset);
            outset.addinstance;
            outset.piecewise();
            FOR i IN 1 .. attr_count
            LOOP
                type_code := self.ret_type.getattreleminfo(i, prec, scale, len, csid, csfrm, attr_type, attr_name);
                dbms_output.put_line(attr_name);
                CASE type_code
                    WHEN dbms_types.typecode_char THEN
                        dbms_sql.column_value(self.cur, i, v1);
                        outset.setchar(v1);
                    WHEN dbms_types.typecode_varchar2 THEN
                        dbms_sql.column_value(self.cur, i, v1);
                        outset.setvarchar2(v1);
                    WHEN dbms_types.typecode_number THEN
                        dbms_sql.column_value(self.cur, i, n1);
                        outset.setnumber(n1);
                    WHEN dbms_types.typecode_date THEN
                        dbms_sql.column_value(self.cur, i, d1);
                        outset.setdate(d1);
                    WHEN dbms_types.typecode_urowid THEN
                        dbms_sql.column_value(self.cur, i, ur1);
                        outset.seturowid(ur1);
                    WHEN dbms_types.typecode_interval_ds THEN
                        dbms_sql.column_value(self.cur, i, ids1);
                    
                        outset.setintervalds(ids1);
                    WHEN dbms_types.typecode_interval_ym THEN
                        dbms_sql.column_value(self.cur, i, iym1);
                        outset.setintervalym(iym1);
                    WHEN dbms_types.typecode_timestamp THEN
                        dbms_sql.column_value(self.cur, i, ts1);
                        outset.settimestamp(ts1);
                    WHEN dbms_types.typecode_timestamp_tz THEN
                        dbms_sql.column_value(self.cur, i, tstz1);
                        outset.settimestamptz(tstz1);
                    WHEN dbms_types.typecode_timestamp_ltz THEN
                        dbms_sql.column_value(self.cur, i, tsltz1);
                        outset.settimestampltz(tsltz1);
                END CASE;
            END LOOP;
            outset.endcreate;
            RETURN odciconst.success;
        END;
        --
        MEMBER FUNCTION odcitableclose(SELF IN pivotimpl) RETURN NUMBER IS
            c INTEGER;
        BEGIN
            c := self.cur;
            dbms_sql.close_cursor(c);
            RETURN odciconst.success;
        END;
    END;

    CREATE OR REPLACE FUNCTION pivot
    (
        p_stmt IN VARCHAR2,
        p_fmt  IN VARCHAR2 := 'upper(@p@)',
        dummy  IN NUMBER := 0
    ) RETURN anydataset
        PIPELINED USING pivotimpl;
    --grant execute on pivot to '&USER';

    SELECT results.*
      FROM (TABLE(pivot('SELECT sc.st_id,
           sc.full_name,
           sc.lesson_name,
           MAX(sc.score) price
      FROM (SELECT ss.st_id,
                   student_api.get_full_name(ss.st_id),
                   replace(trim(substr(upper(lesson_api.get_name(ss.lesson_name)), 0, 15)), '' '', ''_'') lesson_name,
                   ss.score
              FROM student_score ss
             WHERE ss.st_id like ''&st_id_'' AND ss.score IS NOT NULL) sc
     GROUP BY sc.st_id,
              sc.full_name,
              sc.lesson_name
     ORDER BY sc.st_id'))) results;

结果:

【讨论】:

以上是关于Oracle 查询中的动态枢轴使用情况的主要内容,如果未能解决你的问题,请参考以下文章

oracle sql中的动态数据透视

是否可以编写具有多个动态枢轴的查询?

T-SQL 动态枢轴无法正常工作

如何使用动态枢轴c#winform将总计列和行插入datagridview

Oracle Pivot 和 Pivot XML - ORA-00918:列定义不明确

MySQL 动态枢轴