简体   繁体   中英

Metaprogramming oracle sql select statement

Let's imagine I have a query like the following:

SELECT
  CASE WHEN ONE = 1 THEN 1 ELSE 0 END,
  CASE WHEN JUST_ONE = 1 THEN 1 ELSE 0 END,
  CASE WHEN ANOTHER_ONE = 1 THEN 1 ELSE 0 END,

  CASE WHEN TWO = 1 THEN 1 ELSE 0 END,
  CASE WHEN JUST_TWO = 1 THEN 1 ELSE 0 END,
  CASE WHEN ANOTHER_TWO = 1 THEN 1 ELSE 0 END

  -- 20 more things like that where changes only columns name
FROM
  SOME_TABLE;

As you can see, the only difference between these two groups is that in the first one I use columns that have 'ONE' and in the second the ones that have 'TWO' and I have around 30 groups like this in my actual query so I wonder if there is a way to shorten it somehow?

Since they are different columns, you must explicitly mention them separately in the SELECT list. You cannot do it dynamically in pure SQL .

I would suggest, using a good text editor , it would hardly take a minute or two to write the entire SQL.

You could use DECODE which will have some less syntax instead of CASE expression which is verbose.

For example,

  DECODE(ONE, 1, 1, 0) AS col1, 
  DECODE(JUST_ONE, 1, 1, 0) AS col2,
  DECODE(ANOTHER_ONE, 1, 1, 0) AS col3,
  DECODE(TWO, 1, 1, 0) AS col4,
  DECODE(JUST_TWO, 1, 1, 0) AS col5,
  DECODE(ANOTHER_TWO, 1, 1, 0) as col6

I would suggest to stick to SQL , and not use PL/SQL . They are not the same, they are different engines. PL --> Procedural Language .

But if you insist, then you could use a cursor for loop to loop through all the columns in [ DBA|ALL|USER]_TAB_COLS . You could use a SYS_REFCURSOR to see the data. First you will have to build the dynamic SQL .

Below is an example of dynamically creating query. You can put this in eg cursor variable if you want more queries created.

select 'SELECT ' || listagg('CASE WHEN '||column_name||' = 1 THEN 1 ELSE 0 END ' || column_name,',') within group(order by column_name) || ' FROM YOUR_TABLE_NAME'
from cols 
where data_type in ('NUMBER')
and table_name = 'YOUR_TABLE_NAME';

You can use COLS view to get all column names and their datatypes for all your tables.

Oracle SQL metaprogramming is possible by combining Oracle Data Cartridge with the ANY types.

Even using an existing package for metaprogramming, building a query inside a query is complicated and should be avoided when possible. The other answers are generally better, even though they may require an extra step and are not "pure" SQL.

If you really need to do everything in a single SQL statement, try my open source project, Method4 . After installing it, create a sample schema:

create table some_table(
    one number, just_one number, another_one number,
    two number, just_two number, another_two number);

insert into some_table values(1,1,1,2,2,2);

Run this query that builds the real SELECT statement based on the data dictionary:

select * from table(method4.dynamic_query(
    q'[
        --Find columns that match pattern and aggregate into SELECT list.
        select
            'SELECT'||chr(10)||
            rtrim(listagg('  CASE WHEN '||column_name||' = 1 THEN 1 ELSE 0 END '||column_name||',', chr(10))
                within group (order by order_by1, order_by2), ',')||chr(10)||
            'FROM some_table' sql_statement
        from user_tab_columns
        join
        (
            --Column names that might match the pattern [null|JUST_|ANOTHER]SPELLED_NUMBER.
            select prefix||spelled_number possible_column_names
                ,order_by1, order_by2
            from
            (
                --Numbers 1-10.
                select upper(to_char(to_date(level, 'j'), 'jsp')) spelled_number
                    ,level order_by1
                from dual
                --Increase this number up to the maximum possible number.
                connect by level <= 10
            )
            cross join
            (
                --Possible prefixes.
                select null prefix, 1 order_by2 from dual union all
                select 'JUST_' prefix, 2 order_by2 from dual union all
                select 'ANOTHER_' prefix, 3 order_by2 from dual
            )
        ) column_names
            on user_tab_columns.column_name = column_names.possible_column_names
        where table_name = 'SOME_TABLE'
    ]'
));

The same query will return different columns based on the table:

       ONE   JUST_ONE ANOTHER_ONE        TWO   JUST_TWO ANOTHER_TWO
---------- ---------- ----------- ---------- ---------- -----------
         1          1           1          0          0           0

That's some seriously complicated coding to avoid typing a few lines. This is the kind of solution that a manager would dream up when he first hears that hard-coding is always bad.

This literally answers the question about metaprogramming an Oracle SQL SELECT statement. There are a few rare cases where this approach is a life-saver. But 99.9% of the time it's better to do things the simple way even if it is a bit less automated.

The generative approach (in SQL limited with manuall post processing) could be

with src as (
select 'ONE' col_name from dual union all
select 'TWO' col_name from dual
)
select 
'CASE WHEN '||col_name||' = 1 THEN 1 ELSE 0 END,'||chr(10)||
'CASE WHEN JUST_'||col_name||' = 1 THEN 1 ELSE 0 END,'||chr(10)||
'CASE WHEN ANOTHER_'||col_name||' = 1 THEN 1 ELSE 0 END,'||chr(10)
from src;

giving

CASE WHEN ONE = 1 THEN 1 ELSE 0 END,
CASE WHEN JUST_ONE = 1 THEN 1 ELSE 0 END,
CASE WHEN ANOTHER_ONE = 1 THEN 1 ELSE 0 END,

CASE WHEN TWO = 1 THEN 1 ELSE 0 END,
CASE WHEN JUST_TWO = 1 THEN 1 ELSE 0 END,
CASE WHEN ANOTHER_TWO = 1 THEN 1 ELSE 0 END,

Note that you provide the list of your column names and gets the expanded syntax. Typically you need to remove the last delimiter and add the wrapping SELECT frame.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM