问题
Is it possible to turn the below Oracle SQL query into a dynamic query? What I mean is, I have hard coded the values for the case statement as 'INTERNET', 'SALES' ETC... Is it possible to avoid hard coding? My source column is dynamic. I was thinking a for loop and array, but is that available in SQL? If someone could get me going on this, that would be great. Thank you.
SELECT
NVL(status, 'Grand Total') AS "ROW LABELS",
COUNT(case when source = 'INTERNET' THEN 1 end) AS "INTERNET",
COUNT(case when source = 'SALES' THEN 1 end) AS "SALES",
COUNT(case when source = 'DEMO' THEN 1 end) AS "DEMO",
COUNT(case when source = 'COM' THEN 1 end) AS "COM",
COUNT(CASE WHEN order_source IN ('INTERNET', 'SALES', 'DEMO', 'COM') THEN 1 END) AS "Grand Total"
FROM
SOMETABLE
GROUP BY ROLLUP(status);
回答1:
You need a PIVOT function with dynamic columns definition. The simplest way is pivot xml:
create table tst_data (id int primary key, source varchar2(255));
insert into tst_data values (1, 'INTERNET');
insert into tst_data values (2, 'DEMO');
insert into tst_data values (3, 'INTERNET');
insert into tst_data values (4, 'SALES');
insert into tst_data values (5, 'INTERNET');
insert into tst_data values (6, 'DEMO');
insert into tst_data values (7, 'INTERNET');
insert into tst_data values (8, 'COM');
commit;
select * from (
select source from tst_data
)
pivot xml
(
count(1)
for source in (select distinct t.source from tst_data t)
)
After you need to process XML data:
<PivotSet>
<item>
<column name = "SOURCE">COM</column>
<column name = "COUNT(1)">1</column>
</item>
<item>
<column name = "SOURCE">DEMO</column>
<column name = "COUNT(1)">2</column>
</item>
<item>
<column name = "SOURCE">INTERNET</column>
<column name = "COUNT(1)">4</column>
</item>
<item>
<column name = "SOURCE">SALES</column>
<column name = "COUNT(1)">1</column>
</item>
</PivotSet>
PIVOT XML supports dynamic columns definition (for source in (select distinct t.source from tst_data t)) however it returns XML data. Extractvalue and xmltable functions allow to query particular columns from the XML on server side but you have to specify field names in advance. So I assume to parse it on client side.
If you want to do everything on DB-layer there is another approach. PIVOT (not XML) requires columns names for source in ('INTERNET', 'DEMO', 'COM', ...). It's possible to generate such a query and return a cursor to client side:
CREATE OR REPLACE FUNCTION FUNCTION1 RETURN SYS_REFCURSOR AS
cur sys_refcursor;
BEGIN
open cur for 'select * from dual'; // generate PIVOT query here
RETURN cur;
END FUNCTION1;
I don't know any method to create a simple untyped query from the cursor (on server side), so if you desire to use a plain SQL query do it in two steps:
- Generate a PIVOT query with named columns in PL/SQL function;
- Run the query from your client.
回答2:
You can't really do it in plain SQL. You could make a function that generates the results dynamically; maybe easier to handle than with an anonymous block:
create or replace function get_counts return sys_refcursor as
query varchar2(32767);
rc sys_refcursor;
begin
query := 'select nvl(status, ''Grand Total'') as row_labels';
for tmp in (select distinct source from sometable order by 1)
loop
query := query || ', count(case when source = ''' || tmp.source
|| ''' then 1 end) as "' || substr(tmp.source, 1, 30) || '"';
end loop;
query := query || ', count(*) as total';
query := query || ' from sometable';
query := query || ' group by rollup(status)';
query := query || ' order by status';
open rc for query;
return rc;
end;
/
To run in SQL*Plus or SQL Developer (with 'run as a script'):
var rc refcursor;
exec :rc := get_counts;
print rc
You can also do this as a query:
select get_counts from dual;
In SQL Developer that shows an unhelpful-looking display in the query output window, but if you double-click the value (which looks something like {<ROW_LABELS=...>}) then an edit icon appears at the far right; click that and the actual values will be shown in a new window.
I'm assuming 'total' should include everything; if not I'm not sure how you'd determine what to include dynamically. If you can do that though you could keep a separate variable or collection that builds the case statement for the total as you loop through, then add it afterwards.
来源:https://stackoverflow.com/questions/24808808/oracle-sql-dynamic-case-statement