COUNT function for files? - sql

Is it possible to use COUNT in some way that will give me the number of tuples that are in a .sql file? I tried using it in a query with the file name like this:
SELECT COUNT(*) FROM #q65b;
It tells me that the table is invalid, which I understand because it isn't a table, q65b is a file with a query saved in it. I'm trying to compare the number of rows in q65b to a view that I have created. Is this possible or do I just have to run the query and check the number of rows at the bottom?
Thanks

You can do this in SQL*Plus. For example:
Create the text file, containing the query (note: no semicolon!):
select * from dual
Save it in a file, e.g. myqueryfile.txt, to the folder accessible from your SQL*Plus session.
You can now call this from within another SQL query - but make sure the # as at the start of a line, e.g.:
SQL> select * from (
2 #myqueryfile.txt
3 );
D
-
X
I don't personally use this feature much, however.

Here is one approach. It's a function which reads a file in a directory, wraps the contents in a select count(*) from ( .... ) construct and executes the resultant statement.
1 create or replace function get_cnt
2 ( p_file in varchar2 )
3 return number
4 as
5 n pls_integer;
6 stmt varchar2(32767);
7 f_line varchar2(255);
8 fh utl_file.file_type;
9 begin
10 stmt := 'select count(*) from (';
11 fh := utl_file.fopen('SQL_SCRIPTS', p_file, 'R');
12 loop
13 utl_file.get_line(fh, f_line );
14 if f_line is null then exit;
15 elsif f_line = '/' then exit;
16 else stmt := stmt ||chr(10)||f_line;
17 end if;
18 end loop;
19 stmt := stmt || ')';
20 execute immediate stmt into n;
21 return n;
22* end get_cnt;
SQL>
Here is the contents of a sql file:
select * from emp
/
~
~
~
"scripts/q_emp.sql" 3L, 21C
And here is how the script runs:
SQL> select get_cnt ('q_emp.sql') from dual
2 /
GET_CNT('Q_EMP.SQL')
--------------------
14
SQL>
So it works. Obviously what I have posted is just a proof of concept. You will need to include lots of error handling for the UTL_FILE aspects - it's a package which can throw lots of exceptions - and probably some safety checking of the script that gets passed.

Related

IF condition containing IN operator inside ORACLE Trigger is not working

I have a trigger that uses IF condition with IN operator and two variables v_audit_user and v_evdnse_user inside IN. Both variables are containing comma separated ID values. The trigger gets compiled successfully with no errors. I am not understanding why the IF condition with IN is not working. When I select the function that assigns value to the variables independently, I do see the comma separated values, so nothing is wrong with function (see screenshot).
create or replace TRIGGER TRG_CHK_HRCQA_CASE_ACTIONS
AFTER INSERT ON KDD_CASE_ACTIONS
FOR EACH ROW
DECLARE
user_audit kdd_review_owner.OWNER_ID%TYPE; /* The user that is displayed in audit */
user_evdnse kdd_review_owner.OWNER_ID%TYPE; /* The user that took action in evidence tab */
v_audit_user NUMBER; /* The HRCO/QA user from audit tab */
v_evdnse_user NUMBER; /* The HRCO/QA user from evidence tab */
LV_ERRORCODE VARCHAR2(1000);
BEGIN
/* pass the username into the variables */
SELECT kro.OWNER_ID into user_audit from kdd_review_owner kro where kro.OWNER_SEQ_ID = :NEW.ACTION_BY_ID;
SELECT kro.OWNER_ID into user_evdnse from kdd_review_owner kro where kro.OWNER_SEQ_ID = :NEW.ACTION_BY_ID;
/* fetch the comma separated IDs */
v_audit_user := F_GET_HRCQA_ACTIONS(:NEW.CASE_INTRL_ID,user_audit,'AUDIT');
v_evdnse_user := F_GET_HRCQA_ACTIONS(:NEW.CASE_INTRL_ID,user_evdnse,'EVDNSE');
-- select ENTITY_ID into v_evdnse_user from table(f_get_arg_table(F_GET_HRCQA_ACTIONS(:NEW.CASE_INTRL_ID,user_evdnse,'EVDNSE')));
/* If the action taken is by QA or HRCO role */
IF (:NEW.ACTION_SEQ_ID in (v_audit_user,v_evdnse_user))
THEN
/* then insert record in the SC_HRCQA_CASE_ACTIONS table with IS_HRCO_QA flag as Y */
Insert into SC_HRCQA_CASE_ACTIONS (ACTION_SEQ_ID,ACTION_BY_ID,ACTION_TS,STATUS_CD,CASE_INTRL_ID,ACTION_ID,NEW_CASE_OWNR_ASSGN_ID,CASE_DUE_TS,PREV_CASE_OWNR_ASSGN_ID,IS_HRCO_QA)
values (:NEW.ACTION_SEQ_ID, :NEW.ACTION_BY_ID, :NEW.ACTION_TS, :NEW.STATUS_CD, :NEW.CASE_INTRL_ID, :NEW.ACTION_ID, :NEW.NEW_CASE_OWNR_ASSGN_ID, :NEW.CASE_DUE_TS, :NEW.PREV_CASE_OWNR_ASSGN_ID,'Y');
-- ELSE
--
-- /* else the logged in user is NOT HRCO/QA hence insert record in the SC_HRCQA_CASE_ACTIONS table with IS_HRCO_QA flag as N */
--
-- Insert into SC_HRCQA_CASE_ACTIONS (ACTION_SEQ_ID,ACTION_BY_ID,ACTION_TS,STATUS_CD,CASE_INTRL_ID,ACTION_ID,NEW_CASE_OWNR_ASSGN_ID,CASE_DUE_TS,PREV_CASE_OWNR_ASSGN_ID,IS_HRCO_QA)
-- values (:NEW.ACTION_SEQ_ID, :NEW.ACTION_BY_ID, :NEW.ACTION_TS, :NEW.STATUS_CD, :NEW.CASE_INTRL_ID, :NEW.ACTION_ID, :NEW.NEW_CASE_OWNR_ASSGN_ID, :NEW.CASE_DUE_TS, :NEW.PREV_CASE_OWNR_ASSGN_ID,'N');
END IF;
EXCEPTION
WHEN OTHERS THEN LV_ERRORCODE := SQLCODE;
INSERT INTO KDD_LOGS_MSGS (LOG_DT, LOG_INFO_TX, REMARK_TX)
VALUES (SYSDATE,'ErrorCode - ' || LV_ERRORCODE,'TRG_CHK_HRCQA_CASE_ACTIONS');
END;
You cannot pass a comma-delimited string stored in a single variable to an IN condition and expect it to be parsed as multiple values as it is not.
If you want to use a single variable containing a delimited list then you will need to use string functions to find a sub-string match:
create or replace TRIGGER TRG_CHK_HRCQA_CASE_ACTIONS
AFTER INSERT ON KDD_CASE_ACTIONS
FOR EACH ROW
DECLARE
v_owner_id kdd_review_owner.OWNER_ID%TYPE;
v_audit_user VARCHAR2(1000);
v_evdnse_user VARCHAR2(1000);
LV_ERRORCODE VARCHAR2(1000);
BEGIN
SELECT OWNER_ID
into v_owner_id -- You only need one variable here
from kdd_review_owner
where OWNER_SEQ_ID = :NEW.ACTION_BY_ID;
v_audit_user := F_GET_HRCQA_ACTIONS(:NEW.CASE_INTRL_ID, v_owner_id, 'AUDIT');
v_evdnse_user := F_GET_HRCQA_ACTIONS(:NEW.CASE_INTRL_ID, v_owner_id, 'EVDNSE');
IF ','||v_audit_user||','||v_evdnse_user||',' LIKE '%,'||:NEW.ACTION_SEQ_ID||',%'
THEN
Insert into SC_HRCQA_CASE_ACTIONS (
ACTION_SEQ_ID, ACTION_BY_ID, ACTION_TS, STATUS_CD, CASE_INTRL_ID,
ACTION_ID, NEW_CASE_OWNR_ASSGN_ID, CASE_DUE_TS, PREV_CASE_OWNR_ASSGN_ID, IS_HRCO_QA
) values (
:NEW.ACTION_SEQ_ID, :NEW.ACTION_BY_ID, :NEW.ACTION_TS, :NEW.STATUS_CD, :NEW.CASE_INTRL_ID,
:NEW.ACTION_ID, :NEW.NEW_CASE_OWNR_ASSGN_ID, :NEW.CASE_DUE_TS, :NEW.PREV_CASE_OWNR_ASSGN_ID,'Y'
);
END IF;
EXCEPTION
WHEN OTHERS THEN LV_ERRORCODE := SQLCODE;
INSERT INTO KDD_LOGS_MSGS (LOG_DT, LOG_INFO_TX, REMARK_TX)
VALUES (SYSDATE,'ErrorCode - ' || LV_ERRORCODE,'TRG_CHK_HRCQA_CASE_ACTIONS');
END;
/
I don't have your tables so I'll try to illustrate it using my own code.
This is what you're doing now: to us (humans), it is obvious that L_NEW_ID (its value is 10) is contained in L_VAR1 (its value is '10,20'). However, Oracle doesn't recognize that and returns "Not OK":
SQL> declare
2 -- does L_NEW_ID exist in L_VAR1 and L_VAR2 (using your code)?
3 l_new_id number := 10;
4 --
5 l_var1 varchar2(20) := '10,20';
6 l_var2 varchar2(20) := '30,40';
7 begin
8 if l_new_id in (l_var1, l_var2) then
9 dbms_output.put_line('OK');
10 else
11 dbms_output.put_line('Not OK');
12 end if;
13 end;
14 /
Not OK
PL/SQL procedure successfully completed.
SQL>
So, what to do? One option is to split variables to rows and then check whether search value (10, right?) exists in such a list of values (rows). The result is - as you can see - "OK":
SQL> declare
2 l_new_id number := 10;
3 --
4 l_var1 varchar2(20) := '10,20';
5 l_var2 varchar2(20) := '30,40';
6 l_cnt number;
7 begin
8 -- count how many times L_NEW_ID exists in L_VAR1 nad L_VAR2
9 select count(*)
10 into l_cnt
11 from (-- split L_VAR1 into rows
12 select regexp_substr(l_var1, '[^,]+', 1, level) val
13 from dual
14 connect by level <= regexp_count(l_var1, ',') + 1
15 union all
16 -- split L_VAR2 into rows
17 select regexp_substr(l_var2, '[^,]+', 1, level)
18 from dual
19 connect by level <= regexp_count(l_var2, ',') + 1
20 ) x
21 where x.val = l_new_id;
22
23 if l_cnt > 0 then
24 dbms_output.put_line('OK');
25 else
26 dbms_output.put_line('Not OK');
27 end if;
28 end;
29 /
OK
PL/SQL procedure successfully completed.
SQL>
Just to verify it, let's change L_NEW_ID value to e.g. 99 and see what happens; as expected, "Not OK" as 99 isn't contained in 10, 20 nor 30, 40.
SQL> l2
2* l_new_id number := 10;
SQL> c/10/99
2* l_new_id number := 99;
SQL> /
Not OK
PL/SQL procedure successfully completed.
SQL>

check if two values are present in a table with plsql in oracle sql

I'm trying to create a procedure, that checks if two values are present in a table.
The logic is as follows: Create a function called get_authority. This function takes two parameters (found in the account_owner table): cust_id and acc_id, and returns 1 (one), if the customer has the right to make withdrawals from the account, or 0 (zero), if the customer doesn't have any authority to the account. I'm writing plsql and using oracle live sql. I can't figure out how to handle the scenario where a customer has two accounts!
account_owner is seen here:
create or replace function get_authority(
p_cust_id in account_owner.cust_id%type,
p_acc_id in account_owner.acc_id%type
)
return varchar2
as
v_return number(1);
v_acc_id account_owner.acc_id%type;
v_cust_id account_owner.cust_id%type;
begin
for v_ in (select account_owner.cust_id,
account_owner.acc_id
from account_owner
where p_cust_id = cust_id)
LOOP
if p_cust_id = v_cust_id and p_acc_id = v_acc_id then
v_return := v_return + 1;
else
v_return := v_return + 0;
end if;
return v_return;
END LOOP;
end;
/
When I check for the cust_id I get the return 0 - but it should be 1??
select get_authority('650707-1111',123) from dual;
return:
GET_AUTHORITY('650707-1111',123)
0
What do I do wrong?
You got 0? How come; should be NULL.
v_return number(1);
so it is initially NULL. Later on, you're adding "something" to it, but - adding anything to NULL will be NULL:
SQL> select 25 + null as result from dual;
RESULT
----------
SQL>
Therefore, set its default value to 0 (zero):
v_return number(1) := 0;
Also, you declared two additional variables:
v_acc_id account_owner.acc_id%type;
v_cust_id account_owner.cust_id%type;
Then you compare them to values passed as parameters; as they are NULL, ELSE is executed.
Furthermore, there's a loop, but you don't do anything with it. If you meant that this:
for v_ in (select account_owner.cust_id,
(rewritten as for v_ in (select cust_id) evaluates to v_cust_id - it does not. Cursor variables are referred to as v_.cust_id (note the dot in between).
Also, if there's only one row per p_cust_id and p_acc_id, why do you use cursor FOR loop at all? To avoid no_data_found or too_many_rows? I wouldn't do that; yes, it fixes such "errors", but is confusing. You'd rather properly handle exceptions.
Here's what you might have done:
Sample data:
SQL> select * From account_owner;
ACCOW_ID CUST_ID ACC_ID
---------- ----------- ----------
1 650707-1111 123
2 560126-1148 123
3 650707-1111 5899
Function; if there are more rows per parameters' combination, max function will make sure that too_many_rows is avoided (as it bothers you). You don't really care what it returns - important is that select returns anything to prove that authority exists for that account.
SQL> create or replace function get_authority
2 (p_cust_id in account_owner.cust_id%type,
3 p_acc_id in account_owner.acc_id%type
4 )
5 return number
6 is
7 l_accow_id account_owner.accow_id%type;
8 begin
9 select max(o.accow_id)
10 into l_accow_id
11 from account_owner o
12 where o.cust_id = p_cust_id
13 and o.acc_id = p_acc_id;
14
15 return case when l_accow_id is not null then 1
16 else 0
17 end;
18 end;
19 /
Function created.
Testing:
SQL> select get_authority('650707-1111', 123) res_1,
2 get_authority('650707-1111', 5899) res_2
3 from dual;
RES_1 RES_2
---------- ----------
1 1
SQL>

Generate XML files from Oracle query

I have a query which return 50 millions rows. I want to generate XML files for each row (file max. size is 100k). Of course I know the tags but I don't know how to write this in the most efficient way. Any help ?
Thanks
I wouldn't recommend trying to write 50M files to disk, but here's some code you can play with to demonstrate why its not a good idea
1: a function that writes files to disk using a directory
create or replace function WRITETOFILE (dir in VARCHAR2,fn in VARCHAR2, dd IN clob) return clob AS
ff UTL_FILE.FILE_TYPE;
l_amt number := 30000;
l_offset number := 1;
l_length number := nvl(dbms_lob.getlength(dd),0);
buf varchar2(30000);
begin
ff := UTL_FILE.FOPEN(dir,fn,'W',32760);
while ( l_offset < l_length ) loop
buf := dbms_lob.substr(dd,l_amt,l_offset);
utl_file.put(ff, buf);
utl_file.fflush(ff);
utl_file.new_line(ff);
l_offset := l_offset+length(buf);
end loop;
UTL_FILE.FCLOSE(ff);
return dd;
END WRITETOFILE;
/
2: statement creating a table with all rows from a query that makes use of function above - suggesting that you keep the number of rows small to see how it plays
create table tmptbl as
select writetofile('DMP_DIR','xyz-'||level||'.xml', xmlelement("x", level).getClobVal()) tmpcol, systimestamp added_at
from dual CONNECT BY LEVEL <= 10;
3: drop table to repeat create table statement with more rows
drop table tmptbl purge;
I did 10k files in 10 seconds - which would give 1000 seconds for 1M files and 50000 seconds for 50M files (i.e. just under 14 hours).

calling column name from array oracle sql

my table looks like:
Name Salary Salary_1 Salary_2
AAA 100 70 80
BBB 120 100 110
CCC 20 25 30
This what I want to do is to save sum for each salaries (so for salary, salary_1 and salary_2). I've created the pl/sql block:
declare
type col is table of varchar2(8);
type suma is table of number;
v_col col:=col('SALARY','SALARY_1','SALARY_2');
v_suma suma:=suma();
begin
for i in 1..3 loop
v_col.extend();
v_suma.extend();
select sum(v_col(i))
into v_suma(i) from employees;
dbms_output.put_line('value : ' ||v_suma(i));
end loop;
end;
In dbms_output I get:
value :
value :
value :
I think that I cannot call column names like that from the varray. Am I correct?
Any ideas how to resolve this problem?
Many thanks for your help
Kamil
You are right; you cannot sum the columns the way you are trying it. sum(v_col(i)) simply multiplies the content of v_col(i) (which is null) with the number of records in the table.
However, you don't need a loop to get the three sums:
declare
type suma is table of number;
v_suma suma := suma();
begin
v_suma.extend(3);
select sum(salary), sum(salary_1), sum(salary_2)
into v_suma(1), v_suma(2), v_suma(3)
from employees;
for i in 1 .. 3 loop
dbms_output.put_line('value : ' || v_suma(i));
end loop;
end;

split CLOB field into table like structure

I have field in a Oracle database of type CLOB. I would like to split this field in several columns and rows. Here's an example of the content:
4:true5:false24:<p>option sample 1.</p>4:true22:<p>option sample 2.</p>5:false23:<p>option sample 3.</p>5:false22:<p>option sample 4.</p>5:false
The result should look like this:
ID LEVEL ANSWER_OPTION VALUE
1 3 option sample 3 false
1 4 option sample 4 false
2 3 option sample 3 false
4 3 option sample 3 true
3 2 option sample 2 false
1 2 option sample 2 true
2 1 option sample 1 true
2 4 option sample 4 false
4 1 option sample 1 false
2 2 option sample 2 false
4 2 option sample 2 false
1 1 option sample 1 false
3 4 option sample 4 false
4 4 option sample 4 false
3 3 option sample 3 false
3 1 option sample 1 true
We have made the following statement which created the result above.
with guest_string as
( select qsn.id id
, dbms_lob.substr( qsn.guest, 2000, 1 ) answer_options
from mneme_question qsn
where qsn.id < 10
)
select distinct id
, level
, substr(regexp_substr( answer_options'<p>[^<]+', 1, level, 'i'), 4) ANSWER_OPTION
, substr(regexp_substr( answer_options, '(true|false)', regexp_instr( answer_options, '</p>', 1, 1), level, 'i'), 1) VALUE
from guest_string
connect by regexp_substr( answer_options, '<p>[^<]+', 1, level, 'i') is not null
The problem with this code is it takes way to long to split all records we have. We had to cut it off at 10 rows (5 row take 0.25 sec, 10 takes 16 seconds, 15 rows takes about over 2,5 minutes). We currently have 30000 rows and they will grow. At the moment we cannot change the software to change the datamodel, so we will have to do this ad hoc.
Our current approach is to create a procedure that will be called for each record, but it would be better to have a faster parsing. Does anybody have a suggestion how to create a script that can do this in a reasonable time. We can do this at night time, but preferably it shouldn't take longer than 2 minutes.
BTW in the future we could build some sort of mechanism to determine which records have already been parsed, but that also requires some form of detecting already parsed fields that have changed in the mean time. We don't have time to do that yet, so for now we need crude parsing as fast as possible.
Thanks
Here is a different approach.
The code may not be as nice as it can, and you will probably have to fix some small things...
I checked it on 11g (couldn't find 10g) and used your input as the values in my clob column.
For 10 rows (all with the same input as you gave as example),
original query: 9.8 sec
new query: 0.08 sec
I used a pipelined function, here is the code:
create or replace type t_parse is object(idd number, levell number, answer_option varchar2(128), valuee varchar2(4000));
/
create or replace type tab_parse is table of t_parse;
/
create or replace function split_answers return tab_parse
pipelined is
cursor c is
select * from mneme_question;
str_t clob;
phraseP varchar2(128);
phraseV varchar2(8);
i1s number;
i1e number;
i2s number;
levell number;
begin
for r in c loop
str_t := r.guest;
levell := 1;
while str_t is not null loop
i1s := dbms_lob.instr(str_t, '<p>', 1, 1) + 3;
if i1s = 3 then
str_t := '';
else
i1e := dbms_lob.instr(str_t, '</p>', 1, 1);
phraseP := dbms_lob.substr(str_t, i1e - i1s, i1s);
str_t := dbms_lob.substr(str_t, offset => i1e + 4);
i2s := dbms_lob.instr(str_t, 'true', 1, 1) ;
if i2s = 0 then
i2s := dbms_lob.instr(str_t, 'false', 1, 1) ;
if i2s = 0 then
str_t := '';
else
phraseV := dbms_lob.substr(str_t, 5, i2s);
pipe row(t_parse(r.id, levell, phraseP, phraseV));
levell := levell + 1;
end if;
else
phraseV := dbms_lob.substr(str_t, 4, i2s);
pipe row(t_parse(r.id, levell, phraseP, phraseV));
levell := levell + 1;
end if;
end if;
end loop;
end loop;
return;
end split_answers;
/
new query should be like:
select * from table(split_answers);
There is some function whis returning pipelined table:
CREATE OR REPLACE FUNCTION split_clob(p_clob IN CLOB DEFAULT NULL,
p_varchar IN VARCHAR2 DEFAULT NULL,
p_separator IN VARCHAR2)
RETURN varchar_set
PIPELINED IS
l_clob CLOB;
BEGIN
l_clob := nvl(p_clob,
to_clob(p_varchar));
FOR rec IN (
WITH vals AS
(SELECT CAST(TRIM(regexp_substr(l_clob,
'[^'||p_separator||']+',
1,
levels.column_value)) AS
VARCHAR2(320)) AS val
FROM TABLE(CAST(MULTISET
(SELECT LEVEL
FROM dual
CONNECT BY LEVEL <=
length(regexp_replace(l_clob,
'[^'||p_separator||']+')) + 1) AS
sys.odcinumberlist)) levels)
SELECT val FROM vals)
LOOP
PIPE ROW(rec.val);
END LOOP;
RETURN;
END;
U can use it like this:
-with table with clob
SELECT t2.column_value FROM my_table t, TABLE(split_clob(p_clob => t.clob_column,p_separator => ',')) t2
-or with varchar too
SELECT * FROM TABLE(split_clob(p_varchar => '1,2,3,4,5,6, 7, 8, 9', p_separator => ','))