How to Unpivot with dynamic columns Oracle

醉酒当歌 提交于 2019-12-11 04:27:30

问题


I need to unpiviot a table that I don't have control over the columns, so i need to dynamically get the column names: This is what I have

CREATE TABLE test
(
 PK VARCHAR2(255 CHAR),
COL1                VARCHAR2(255 CHAR),
COL2              VARCHAR2(255 CHAR),
COL3            VARCHAR2(255 CHAR),
COL4              VARCHAR2(255 CHAR),
COL5             VARCHAR2(255 CHAR),
COL6            NUMBER,

)

    declare
      sql_stmt     clob;
      pivot_clause clob;
    begin
      select listagg('''' || column_name || ''' as "' || column_name || '"', ',') within group (order by column_name) 
      into   pivot_clause
    FROM USER_TAB_COLUMNS
    WHERE table_name = 'test');



      sql_stmt := 'SELECT PK,

    VarName,
    Valuer,
    Max(timestamp) over (Partition by PK) as timestamp,

    FROM   test
    UNPIVOT(Valuer FOR VarName IN (' || pivot_clause || '))';

  execute immediate sql_stmt;
end;

Which returns:

Fehlerbericht -
ORA-00904: : invalid identifier
ORA-06512: at line 23
00904. 00000 -  "%s: invalid identifier"
*Cause:    
*Action:

Expected out put would be something like:

PK|VARNAME|Valuer
1 |Col1 | value1
1 |Col2 | value2
1 |Col3 | value3
1 |Col4 | value4
1 |Col5 | value5
1 |Col6 | 12345
2 |Col1 | value1
2 |Col2 | value2
2 |Col3 | value3
2 |Col4 | value4
2 |Col5 | value5
2 |Col6 | 12345

Which is the same error i get if i just toss the sub select right into the IN()

Thanks


回答1:


You can use dbms_output.put_line(sql_stmt) to see the actual dynamic SQL being generated, which in this case is:

SELECT PK,

    VarName,
    Valuer,
    Max(timestamp) over (Partition by PK) as timestamp,

    FROM   test
    UNPIVOT(Valuer FOR VarName IN ('COL1' as "COL1",'COL2' as "COL2",'COL3' as "COL3",'COL4' as "COL4",'COL5' as "COL5",'COL6' as "COL6",'PK' as "PK",'TIMESTAMP' as "TIMESTAMP"))

Which has a number of issues. Generally for this sort of thing it's sensible to start from a static statement you know works and then figure out how to make it dynamic, but you don't seem to have done that here.

This version gets ORA-00936: missing expression because of the trailing comma after the timestamp in the dynamic statement. Which I imagine is another error from modifying your code for posting. Without that it gets the ORA-00904: : invalid identifier you have in your question. The immediate cause of that error you're getting is the parts in the parentheses, such as:

'COL1' as "COL1"

The quotes are wrong; that should be:

COL1 as 'COL1'

Fixing that then gives ORA-00904: "PK": invalid identifier because your column look-up isn't excluding the columns you don't want to pivot on, so really you want:

  select listagg(column_name || ' as ''' || column_name || '''', ',')
    within group (order by column_name) 
  into   pivot_clause
  from user_tab_columns
  where table_name = 'TEST'
  and column_name not in ('PK', 'TIMESTAMP');

Your next problem is that the columns you're unpivoting are different data types, so you get ORA-01790: expression must have same datatype as corresponding expression. That's a bit trickier - essentially you'll have to convert everything to strings, using suitable formats, particularly if there are dates involved. You need to do that conversion in a subquery, and you unpivot the result of that. An example that just explicitly handles the number column, generating the subquery columns in the same way as the unpivot clause:

declare
  sql_stmt     clob;
  subquery     clob;
  pivot_clause clob;
begin
  select listagg(column_name || ' as ''' || column_name || '''', ',')
      within group (order by column_name),
    listagg(
    case when data_type = 'NUMBER' then 'to_char(' || column_name || ')'
      else column_name
      end || ' as ' || column_name, ',')
      within group (order by column_name)
  into   pivot_clause, subquery
  from user_tab_columns
  where table_name = 'TEST'
  and column_name not in ('PK', 'TIMESTAMP');

  sql_stmt := 'select pk,
    varname,
    valuer,
    max(timestamp) over (partition by pk) as timestamp
    from (select pk, timestamp, ' || subquery || ' from test)
    unpivot(valuer for varname in (' || pivot_clause || '))';

  dbms_output.put_line(sql_stmt);
  execute immediate sql_stmt;
end;
/

You could instead always cast every column to say varchar2(255) in the second listagg(), but then yuo have no direct control over formatting and are reliant on NLS settints.

When run the block above now generates:

select pk,
    varname,
    valuer,
    max(timestamp) over (partition by pk) as timestamp
    from (select pk, timestamp, COL1 as COL1,COL2 as COL2,COL3 as COL3,COL4 as COL4,COL5 as COL5,to_char(COL6) as COL6 from test)
    unpivot(valuer for varname in (COL1 as 'COL1',COL2 as 'COL2',COL3 as 'COL3',COL4 as 'COL4',COL5 as 'COL5',COL6 as 'COL6'))

and when run manually that gets output like:

PK  VARNAME  VALUER   TIMESTAMP                     
1   COL1     value1   11-AUG-17 15.49.42.239283000  
1   COL2     value2   11-AUG-17 15.49.42.239283000  
1   COL3     value3   11-AUG-17 15.49.42.239283000  
1   COL4     value4   11-AUG-17 15.49.42.239283000  
1   COL5     value5   11-AUG-17 15.49.42.239283000  
1   COL6     12345    11-AUG-17 15.49.42.239283000  
2   COL1     value6   11-AUG-17 15.49.42.340387000  
2   COL2     value7   11-AUG-17 15.49.42.340387000  
2   COL3     value8   11-AUG-17 15.49.42.340387000  
2   COL4     value9   11-AUG-17 15.49.42.340387000  
2   COL5     value10  11-AUG-17 15.49.42.340387000  
2   COL6     23456    11-AUG-17 15.49.42.340387000  

(I set up dummy data with different values in the unpivoted columns, and just used systimestamp to get the timestamp column value).

When you run it dynamically it doesn't really do anything - it's parsed but not fully executed because you aren't executing immediate into anything.

My plan was to use this in a view

You can make your anonymous block generate the view dynamically, as a one-off event:

  sql_stmt := 'create or replace view your_view_name as
    select pk,
      varname,
      valuer,
      max(timestamp) over (partition by pk) as timestamp
      from (select pk, timestamp, ' || subquery || ' from test)
      unpivot(valuer for varname in (' || pivot_clause || '))';

  execute immediate sql_stmt;

and you can then just select from your_view_name.

Whenever a new column is added to the table (which is hopefully rare and under change control - but then you wouldn't really need to do this dynamically) you can just re-run the block to recreate the view.



来源:https://stackoverflow.com/questions/45637308/how-to-unpivot-with-dynamic-columns-oracle

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!