Essentially, my problem is that I need to run a query in Oracle that unions a static list of values ('Static' meaning it's obtained from somewhere else that I cannot get from the database, but is actually an arbitrary list of values I plug into the query) with a dynamic list of values returned from a query.
So, my initial query looks like:
select * from (select ('18776') as instanceid from dual) union (<more complex query>)
I think, hooray! And then try to do it with a longer list of static values. Turns out, I get 'Missing Right Parenthesis' if I try to run:
select ('18776','18775') as instanceid from dual
So, my basic issue is how can I integrate a list of static values into this union?
NOTE: This is a simplified example of the problem. The actual list is generated from an API before I generate a query, and so this list of "static" values is unpredictably and arbitrarily large. I'm not dealing with just 2 static values, it is an arbitrary list.
select '18776' as instanceid from dual union all
select '18775' as instanceid from dual
or
select column_value from table(sys.odcivarchar2list('18776', '18775'))
or some sort of hierarchical query that could take your comma separated-string and split it into a set of varchars.
Union these to your initial query.
update: "I'm not dealing with just 2 static values, it is an arbitrary list."
Still can pass to a query as a collection (below is just one of many possible approaches)
23:15:36 LKU#sandbox> ed
Wrote file S:\spool\sandbox\BUFFER_LKU_39.sql
1 declare
2 cnt int := 10;
3 coll sys.odcivarchar2list := sys.odcivarchar2list();
4 begin
5 coll.extend(cnt);
6 for i in 1 .. cnt loop
7 coll(i) := dbms_random.string('l', i);
8 end loop;
9 open :result for 'select * from table(:c)' using coll;
10* end;
23:37:03 11 /
PL/SQL procedure successfully completed.
Elapsed: 00:00:00.50
23:37:04 LKU#sandbox> print result
COLUMN_VALUE
-------------------------------------------------------------
g
kd
qdv
soth
rvwnq
uyfhbq
xxvxvtw
eprralmd
edbcajvfq
ewveyljsjn
10 rows selected.
Elapsed: 00:00:00.01
I think you want to break it into two subqueries:
select *
from ((select '18776' as instanceid from dual)
union
(select '18775' as instanceid from dual)
union
(<more complex query>)
) t;
Note that union all performs better than union. If you know there are no duplicates (or duplicates don't matter) then use union all instead.
If you have the ability/permission to create a table type, you can do this:
CREATE OR REPLACE
TYPE TYP_NUMBER_TABLE AS TABLE OF NUMBER(11);
And then you can use the TABLE function to select from a instance of that type that you initialize on the fly in your SQL:
SELECT COLUMN_VALUE FROM TABLE(TYP_NUMBER_TABLE(1, 2, 3));
Result:
COLUMN_VALUE
------------
1
2
3
Related
Let's say I have a table with numerical columns x,y & z. For data quality purposes I want to calculate the mean, median and mode for each column and present as rows, like so:
https://ibb.co/HFz1qTw
The end goal is to create an up-to-date table like the one in sys.all_columns (I'm not admin)
Is there an elegant way to do this (preferably dynamically so I don't have to enter every column name, while not using UNION) ?
Feasible but complex.
Here are the steps :
1 : Generates dynamically the SQL to compute mean, median... for the columns you want
2 : Run the query as dynamic cursor.
If you need it to like really easy to use at the end, you can use a pipelined function :
3 : So basically create a function that takes as an argument the table name and will be used like :
select * from table(MyPipelinedFunction(MyTableName));
4 : And you will easily get the result
Here are some tips on each step :
Step 1 : generate SQL dynamically
SQL := 'Select average(#FIELD1#), mean or median(#FIELD2#)... from #TABLE#';
SQL := replace(SQL,'#FIELD1#','Whatever');
SQL := replace(SQL,'#TABLE#','Whatever');
Step 2 : Run the query as dynamic cursor.
Declare
RefCursor SYS_REFCURSOR;
Mean number;
Median nuber;
...
BEGIN
OPEN RefCursor FOR SQL;
FETCH RefCursor INTO Mean, Median...;
CLOSE RefCursor;
Step 3 : pipelined function
TYPE Statistics IS RECORD(Mean NUMBER, MEdian Number, ...);
TYPE StatisticsTable IS TABLE OF Statistics;
FUNCTION ComputeStatistics(TableName in varchar2) RETURN Statistics PIPELINED IS
S Statistics;
BEGIN
-- Get the table field names in case they are different using a static cursor
SELECT * FROM USER_TAB_COLUMNS where table_name = upper(TableName);
-- Use the results to build the SQL query and run the dynamic cursor as in Step 1 & 2
-- After this step, results are stored in the variables Mean, Median...
-- Output result
S := Statistics(Mean, Median, ...);
PIPE ROW (S);
RETURN;
END ComputeStatistics;
Step 4 : Get the result
select * from table(ComputeStatistics('WhateverTable');
I would just unpivot and aggregate:
select c.col,
min(c.val), max(c.val), avg(c.val), median(c.val), stats_mode(c.val)
from t cross join lateral
(select 'x' as col, x as val from dual union all
select 'y', y from dual union all
select 'z', z from dual
) c
group by c.col;
You can turn this into dynamic SQL using PL/SQL. In practice, though, I would just query for the column names and generate the SQL directly:
select replace('
select [col] as col,
min(t.[col]), max(t.[col]), avg(t.[col]), median(t.[col]), stats_mode(t.[col])
from t t union all
', '[col]', column_name)
from sys.all_tab_columns c join
sys.all_tables t
on c.owner = t.owner and
c.table_name = t.table_name;
You can then just remove the last union all and run the code.
Note: This doesn't use listagg() because of limits on string length. But you can combine this logic into a single string if you don't have too many columns.
I'm writing a pl/sql function. I need to select multiple rows from select statement:
SELECT pel.ceid
FROM pa_exception_list pel
WHERE trunc(pel.creation_date) >= trunc(SYSDATE-7)
if i use:
SELECT pel.ceid
INTO v_ceid
it only stores one value, but i need to store all values that this select returns. Given that this is a function i can't just use simple select because i get error, "INTO - is expected."
You can use a record type to do that. The below example should work for you
DECLARE
TYPE v_array_type IS VARRAY (10) OF NUMBER;
var v_array_type;
BEGIN
SELECT x
BULK COLLECT INTO
var
FROM (
SELECT 1 x
FROM dual
UNION
SELECT 2 x
FROM dual
UNION
SELECT 3 x
FROM dual
);
FOR I IN 1..3 LOOP
dbms_output.put_line(var(I));
END LOOP;
END;
So in your case, it would be something like
select pel.ceid
BULK COLLECT INTO <variable which you create>
from pa_exception_list
where trunc(pel.creation_Date) >= trunc(sysdate-7);
If you really need to store multiple rows, check BULK COLLECT INTO statement and examples. But maybe FOR cursor LOOP and row-by-row processing would be better decision.
You may store all in a rowtype parameter and show whichever column you want to show( assuming ceid is your primary key column, col1 & 2 are some other columns of your table ) :
SQL> set serveroutput on;
SQL> declare
l_exp pa_exception_list%rowtype;
begin
for c in ( select *
from pa_exception_list pel
where trunc(pel.creation_date) >= trunc(SYSDATE-7)
) -- to select multiple rows
loop
select *
into l_exp
from pa_exception_list
where ceid = c.ceid; -- to render only one row( ceid is primary key )
dbms_output.put_line(l_exp.ceid||' - '||l_exp.col1||' - '||l_exp.col2); -- to show the results
end loop;
end;
/
SET SERVEROUTPUT ON
BEGIN
FOR rec IN (
--an implicit cursor is created here
SELECT pel.ceid AS ceid
FROM pa_exception_list pel
WHERE trunc(pel.creation_date) >= trunc(SYSDATE-7)
)
LOOP
dbms_output.put_line(rec.ceid);
END LOOP;
END;
/
Notes from here:
In this case, the cursor FOR LOOP declares, opens, fetches from, and
closes an implicit cursor. However, the implicit cursor is internal;
therefore, you cannot reference it.
Note that Oracle Database automatically optimizes a cursor FOR LOOP to
work similarly to a BULK COLLECT query. Although your code looks as if
it fetched one row at a time, Oracle Database fetches multiple rows at
a time and allows you to process each row individually.
I'm creating a query which uses 2 embedded server functions multiple times.
Problem: the functions search through a decently large table, and they take a long time to execute.
Goal: Use a subquery as if it were a table so that I can reference columns without running the function to generate the column more than once.
Example Pseudocode:
Select general.column1, general.column2, general.column1-general.column2
from (select package.function1('I take a long time') column1,
package.function2('I take even longer') column2,
normal_column
from bigtable) general;
When I run my code general.column1 will reference the function in the statement of column1, not the data returned by it (which is ultimately what I'm after).
I'm fairly new to SQL, so any help is appreciated and if you need more info, I'll do my best to provide it.
Thanks!
I suggest you tu use the subquery factoring. The first subquery will be executed only once and then used through the rest of he query.
WITH function_result AS
(SELECT package.function1('I take a long time') column1
, package.function2('I take even longer') column2
FROM dual)
SELECT function_result.column1
, function_result.column2
, function_result.column1 - function_result.column2
, bigtable.normal_column
FROM bigtable
In general what you want to do is in this case is take advatage of scalar subquery caching.
i.e. put:
Select general.column1, general.column2, general.column1-general.column2
from (select (select package.function1('I take a long time') from dual) column1,
(select package.function2('I take even longer') from dual) column2,
normal_column
from bigtable) general;
delcaring the function as deterministic too helps if it is deterministic.
a small example:
SQL> create or replace function testfunc(i varchar2)
2 return varchar2
3 is
4 begin
5 dbms_application_info.set_client_info(userenv('client_info')+1 );
6 return 'hi';
7 end;
8 /
Function created.
now lets test a call to the function like you have:
SQL> exec dbms_application_info.set_client_info(0);
PL/SQL procedure successfully completed.
SQL> set autotrace traceonly
SQL> select *
2 from (select testfunc(owner) a
3 from all_objects);
57954 rows selected.
SQL> select userenv('client_info') from dual;
USERENV('CLIENT_INFO')
----------------------------------------------------------------
57954
the function was called 57954 times (once per row). now lets use scalar caching:
SQL> exec dbms_application_info.set_client_info(0);
PL/SQL procedure successfully completed.
SQL> select *
2 from (select (select testfunc(owner) from dual) a
3 from all_objects);
57954 rows selected.
SQL> select userenv('client_info') from dual;
USERENV('CLIENT_INFO')
----------------------------------------------------------------
178
178 calls instead of 57k!
in your case you've only shown that you have a literal and no input that is varying per row (if this is the case, the number of calls after using scalar caching should be 1).
if we add deterministic:
SQL> create or replace function testfunc(i varchar2)
2 return varchar2 deterministic
3 is
4 begin
5 dbms_application_info.set_client_info(userenv('client_info')+1 );
6 return 'hi';
7 end;
8 /
Function created.
SQL> exec dbms_application_info.set_client_info(0);
PL/SQL procedure successfully completed.
SQL> select *
2 from (select (select testfunc(owner) from dual) a
3 from all_objects);
57954 rows selected.
SQL> select userenv('client_info') from dual;
USERENV('CLIENT_INFO')
----------------------------------------------------------------
55
now down to 55. in 11g we have result_cache which we can put in place of deterministic, which would reduce the calls on subsequant runs to 0 calls.
I have a table that contains a VARCHAR2 column called COMMANDS.
The data in this column is a bunch of difficult to read ZPL code that will be sent to a label printer, and amidst the ZPL there are several tokens in the form {TABLE.COLUMN}.
I would a like nice list of all the distinct {TABLE.COLUMN} tokens that are found in COMMANDS. I wrote the following regex to match the token format:
SELECT REGEXP_SUBSTR(COMMANDS,'\{\w+\.\w+\}') FROM MYTABLE;
The regex works, but it only returns the first matched token per row. Is there a way to return all regex matches for each row?
I'm using Oracle 11GR2.
Edit - Here is a small sample of data from a single row -- there are many such lines in each row:
^FO360,065^AEN,25,10^FD{CUSTOMERS.CUST_NAME}^FS
^FO360,095^AAN,15,12^FD{CUSTOMERS.CUST_ADDR1}^FS
So if that was the only row in table, I'd like to have returned:
{CUSTOMERS.CUST_NAME}
{CUSTOMERS.CUST_ADDR1}
You've provided sample of data saying that this is a single row but have presented it as two different rows. So this example based on your words.
-- Sample of data from your question + extra row for the sake of demonstration
-- id column is added to distinguish the rows(I assume you have one)
with t1(id, col) as(
select 1, '^FO360,065^AEN,25,10^FD{CUSTOMERS1.CUST_NAME}^FS^FO360,095^AAN,15,12^FD{CUSTOMERS1.CUST_ADDR1}^FS' from dual union all
select 2, '^FO360,065^AEN,25,10^FD{CUSTOMERS2.CUST_NAME}^FS^FO360,095^AAN,15,12^FD{CUSTOMERS2.CUST_ADDR2}^FS' from dual
),
cnt(c) as(
select level
from (select max(regexp_count(col, '{\w+.\w+}')) as o_c
from t1
) z
connect by level <= z.o_c
)
select t1.id, listagg(regexp_substr(t1.col, '{\w+.\w+}', 1, cnt.c)) within group(order by t1.id) res
from t1
cross join cnt
group by t1.id
Result:
ID RES
---------------------------------------------------------
1 {CUSTOMERS1.CUST_ADDR1}{CUSTOMERS1.CUST_NAME}
2 {CUSTOMERS2.CUST_ADDR2}{CUSTOMERS2.CUST_NAME}
As per #a_horse_with_no_name comment to the question, really, it's much simpler to just replace everything else that doesn't match the pattern. Here is an example:
with t1(col) as(
select '^FO360,065^AEN,25,10^FD{CUSTOMERS.CUST_NAME}^FS^FO360,095^AAN,15,12^FD{CUSTOMERS.CUST_ADDR1}^FS' from dual
)
select regexp_replace(t1.col, '({\w+.\w+})|.', '\1') res
from t1
Result:
RES
-------------------------------------------
{CUSTOMERS.CUST_NAME}{CUSTOMERS.CUST_ADDR1}
I think there isn't. You should write some PL/SQL to get the others matching tokens. My best advice to you is to use a pipelined function.
First, create a type:
create type strings as table of varchar2(200);
Then the function:
CREATE OR REPLACE function let_me_show
return strings PIPELINED as
l_n number;
l_r varchar2(200);
begin
for r_rec in
( SELECT commands
FROM MYTABLE )
loop
l_n := 1;
l_r := REGEXP_SUBSTR(r_rec.COMMANDS,'\{\w+\.\w+\}', 1, l_n);
while l_r is not null
loop
pipe row(l_r);
l_n := l_n + 1;
l_r := REGEXP_SUBSTR(r_rec.COMMANDS,'\{\w+\.\w+\}', 1, l_n);
end loop;
end loop;
end;
Now you can use the function to return the results:
select *
from table(let_me_show())
Let's say I have a function call on a select or where clause in Oracle like this:
select a, b, c, dbms_crypto.hash(utl_raw.cast_to_raw('HELLO'),3)
from my_table
A similar example can be constructed for MS SQLServer.
What's the expected behavior in each case?
Is the HASH function going to be called once for each row in the table, or DBMS will be smart enough to call the function just once, since it's a function with constant parameters and no side-effects?
Thanks a lot.
The answer for Oracle is it depends. The function will be called for every row selected UNLESS the Function is marked 'Deterministic' in which case it will only be called once.
CREATE OR REPLACE PACKAGE TestCallCount AS
FUNCTION StringLen(SrcStr VARCHAR) RETURN INTEGER;
FUNCTION StringLen2(SrcStr VARCHAR) RETURN INTEGER DETERMINISTIC;
FUNCTION GetCallCount RETURN INTEGER;
FUNCTION GetCallCount2 RETURN INTEGER;
END TestCallCount;
CREATE OR REPLACE PACKAGE BODY TestCallCount AS
TotalFunctionCalls INTEGER := 0;
TotalFunctionCalls2 INTEGER := 0;
FUNCTION StringLen(SrcStr VARCHAR) RETURN INTEGER AS
BEGIN
TotalFunctionCalls := TotalFunctionCalls + 1;
RETURN Length(SrcStr);
END;
FUNCTION GetCallCount RETURN INTEGER AS
BEGIN
RETURN TotalFunctionCalls;
END;
FUNCTION StringLen2(SrcStr VARCHAR) RETURN INTEGER DETERMINISTIC AS
BEGIN
TotalFunctionCalls2 := TotalFunctionCalls2 + 1;
RETURN Length(SrcStr);
END;
FUNCTION GetCallCount2 RETURN INTEGER AS
BEGIN
RETURN TotalFunctionCalls2;
END;
END TestCallCount;
SELECT a,TestCallCount.StringLen('foo') FROM(
SELECT 0 as a FROM dual
UNION
SELECT 1 as a FROM dual
UNION
SELECT 2 as a FROM dual
);
SELECT TestCallCount.GetCallCount() AS TotalFunctionCalls FROM dual;
Output:
A TESTCALLCOUNT.STRINGLEN('FOO')
---------------------- ------------------------------
0 3
1 3
2 3
3 rows selected
TOTALFUNCTIONCALLS
----------------------
3
1 rows selected
So the StringLen() function was called three times in the first case. Now when executing with StringLen2() which is denoted deterministic:
SELECT a,TestCallCount.StringLen2('foo') from(
select 0 as a from dual
union
select 1 as a from dual
union
select 2 as a from dual
);
SELECT TestCallCount.GetCallCount2() AS TotalFunctionCalls FROM dual;
Results:
A TESTCALLCOUNT.STRINGLEN2('FOO')
---------------------- -------------------------------
0 3
1 3
2 3
3 rows selected
TOTALFUNCTIONCALLS
----------------------
1
1 rows selected
So the StringLen2() function was only called once since it was marked deterministic.
For a function not marked deterministic, you can get around this by modifying your query as such:
select a, b, c, hashed
from my_table
cross join (
select dbms_crypto.hash(utl_raw.cast_to_raw('HELLO'),3) as hashed from dual
);
For SQL server, it will be evaluated for every single row.
You will be MUCH better off by running the function once and assigning to a variable and using the variable in the query.
short answer....it depends.
If the function is accessing data ORACLE does not know if it is going to be the same for each row, therefore, it needs to query for each. If, for example, your function is just a formatter that always returns the same value then you can turn on caching (marking it as Deterministic) which may allow for you to only do the function call once.
Something you may want to look into is ORACLE WITH subquery:
The WITH query_name clause lets you
assign a name to a subquery block. You
can then reference the subquery block
multiple places in the query by
specifying the query name. Oracle
optimizes the query by treating the
query name as either an inline view or
as a temporary table
I got the quoted text from here, which has plenty of examples.