I am having a problem with the procedure below as whenever I pass a date of month and year, the IF condition loops on the number of records that exists in my table- only two rows existed- and also it shows the two possibilities. I also included the result to show what I mean!
PRODUCT_NUM ORDER_NUM QUANTITY PRICE MONTHLY_DATE
-------------- ----------- ------------- ------------ -------------
12345 106 3 19.99 21-DEC-15
67894 107 1 19.99 21-DEC-15
CREATE OR REPLACE PROCEDURE Proce_Name (Pram_Date DATE )
AS
BGIN
FOR Y IN
(SELECT SUM(PRICE) AS TOTAL_AMOUNT, SUM(NVL(QUANTITY,0) * NVL (
(PRICE,0)) AS TOTAL, MONTHLY_DATE
FROM PRODUCTS
GROUP BY MONTHLY_DATE
ORDER BY MONTHLY_DATE DESC) LOOP
IF TO_CHAR( Y.MONTHLY_DATE, 'mm-yyyy')= TO_CHAR(Pram_Date,'mm-yyyy') THEN
DBMS_OUTPUT.PUT_LINE('TOTAL AMOUNT: '|| ' '|| Y.TOTAL_AMOUNT);
DBMS_OUTPUT.PUT_LINE('');
DBMS_OUTPUT.PUT_LINE('TOTAL: '|| ' '|| Y.TOTAL);
ELSE
DBMS_OUTPUT.PUT_LINE('No Data for this month');
END IF;
END LOOP;
END Proce_name;
calling block:
DECLARE
MONTHLY_D DATE := TO_DATE('01-2012','mm-yyyy');
BEGIN
Proce_Name(MONTHLY_D);
END;
/
This is when the date I pass matches the records in the table:
No Data for this month
TOTAL AMOUNT: 4
TOTAL: 99.96
The result here when I pass a date that is not existed in the table:
No Data for this month
No Data for this month
How can I alter my procedure to examine the above date format accurately ?
Here is the code, you can consider exception handling as well:
Few observations, you an consider prem_date as char it self instead of passing date. Also if data set is huge and monthly_date is indexed, you should avoid applying to_char condition on monthly_date.
CREATE OR REPLACE PROCEDURE Proce_Name (Pram_Date DATE )
AS
l_total_amount float;
l_total float;
BEGIN
SELECT SUM(PRICE) AS TOTAL_AMOUNT,
SUM(NVL(QUANTITY,0) * NVL(PRICE,0)) AS TOTAL
INTO l_total_amount, l_total
FROM PRODUCTS
WHERE to_char(monthly_date, 'mm-yyyy') = to_char(pram_date, 'mm-yyyy');
if(l_total_amount is not null) then
DBMS_OUTPUT.PUT_LINE('TOTAL AMOUNT: '|| ' '|| L_TOTAL_AMOUNT);
DBMS_OUTPUT.PUT_LINE('');
DBMS_OUTPUT.PUT_LINE('TOTAL: '|| ' '|| L_TOTAL);
else
DBMS_OUTPUT.PUT_LINE('No Data for this month');
end if;
END Proce_name;
/
I have update procedure as following
CREATE OR REPLACE PROCEDURE get_sales_report(c_date date) AS
l_amount number(18, 2);
l_total number(18, 2);
BEGIN
begin
SELECT SUM(PRICE), SUM(NVL(QUANTITY, 0) * NVL(PRICE, 0))
into l_amount, l_total
FROM product
where TO_CHAR(MONTHLY_DATE, 'mm-yyyy') = to_char(c_date, 'mm-yyyy')
GROUP BY TO_CHAR(MONTHLY_DATE, 'mm-yyyy')
ORDER BY TO_CHAR(MONTHLY_DATE, 'mm-yyyy') DESC;
DBMS_OUTPUT.PUT_LINE('TOTAL AMOUNT: ' || ' ' || l_amount);
DBMS_OUTPUT.PUT_LINE('');
DBMS_OUTPUT.PUT_LINE('TOTAL: ' || ' ' || l_total);
exception
when no_data_found then
DBMS_OUTPUT.PUT_LINE('No Data for this month');
END; -- end of exception
-- add your code here
-- add your code here
-- add your code here
end;
Also call following command for dbms_output echoing text.
set serveroutput on size 30000;
Call your procedure as
declare
c_date2 DATE := TO_DATE('12-2015','mm-yyyy');
begin
get_sales_report(c_date2);
end;
Dates are fixed sized types. So, if you want to group by a whole month, you must query your date column as char. Meaning, you should use TO_CHAR instead of TO_DATE.
Here's (the simpler) way to do it (My changes are lowercase) -
CREATE OR REPLACE PROCEDURE Proce_Name (Pram_Date date) AS
l_month char(7);
l_total_amount number;
l_total number;
BEGIN
SELECT to_char(monthly_date,'mm-yyyy') as month,
SUM(PRICE) AS TOTAL_AMOUNT,
SUM(NVL(QUANTITY,0)*NVL(PRICE,0)) AS TOTAL
INTO l_month, l_total_amount, l_total
FROM PRODUCTS
where to_char(monthly_date,'mm-yyyy')=to_char(Pram_Date, 'mm-yyyy')
GROUP BY to_char(monthly_date,'mm-yyyy');
if l_month is not null then
DBMS_OUTPUT.PUT_LINE('TOTAL AMOUNT: '|| ' '|| Y.TOTAL_AMOUNT);
DBMS_OUTPUT.PUT_LINE('');
DBMS_OUTPUT.PUT_LINE('TOTAL: '|| ' '|| Y.TOTAL);
else
DBMS_OUTPUT.PUT_LINE('No Data for this month');
end if;
END Proce_name;
/
Here's some testing code - http://sqlfiddle.com/#!4/b1068/2
Related
I have a query that takes start and end year for data also it takes a name of a table as procedure parameters..
the query then will create a table with that name if it doesn't exist or if exist it will drop and recreate it
CREATE OR REPLACE Procedure USE_RAWDATA
( START_RP IN NUMBER, END_RP IN NUMBER,TABLE_NAME varchar)
IS
v_listStr CLOB;
DAT VARCHAR(500):=to_char(to_date(SYSDATE), 'yyyymmdd');
cnt NUMBER;
BEGIN
BEGIN
SELECT COUNT(*) INTO cnt FROM user_tables WHERE table_name = TABLE_NAME||'_'||DAT;
dbms_output.put_line(TABLE_NAME||'_'||DAT);
dbms_output.put_line(cnt);
IF (cnt) = 1 THEN
EXECUTE IMMEDIATE 'DROP TABLE '||TABLE_NAME||'_'||DAT;
dbms_output.put_line('DROP TABLE '||TABLE_NAME||'_'||DAT);
END IF;
END;
select rtrim(xmlagg(xmlelement(e,column_name,', ').extract('//text()') order by column_id).getclobval(),', ') x INTO v_listStr from RAW_DATA_METADATA WHERE (START_ROUND<= END_RP and END_ROUND is NULL) or (START_ROUND >= START_RP and END_ROUND <= END_RP );
EXECUTE IMMEDIATE 'CREATE TABLE '||TABLE_NAME||'_'||DAT||' AS SELECT '||v_listStr ||' FROM RAW_DATA WHERE ROUND_ID BETWEEN '||START_RP ||' AND '||END_RP;
END;
/
my problem is that cnt is always giving me 0 even the table exists then the procedure will end with an error table already exist...
I don't know why .. since when I try it as PL\SQL query it gives me a correct result cnt =1 if exist
declare
v_listStr CLOB;
DAT VARCHAR(500):=to_char(to_date(SYSDATE), 'yyyymmdd');
cnt NUMBER;
BEGIN
BEGIN
SELECT COUNT(*) INTO cnt FROM user_tables WHERE :TABLE_NAME = :TABLE_NAME||'_'||DAT;
dbms_output.put_line(:TABLE_NAME||'_'||DAT);
dbms_output.put_line(cnt);
IF (cnt) = 1 THEN
EXECUTE IMMEDIATE 'DROP TABLE '||:TABLE_NAME||'_'||DAT;
dbms_output.put_line('DROP TABLE '||:TABLE_NAME||'_'||DAT);
END IF;
END;
select rtrim(xmlagg(xmlelement(e,column_name,', ').extract('//text()') order by column_id).getclobval(),', ') x INTO v_listStr from RAW_DATA_METADATA WHERE (START_ROUND<= :END_RP and END_ROUND is NULL) or (START_ROUND >= :START_RP and END_ROUND <= :END_RP );
EXECUTE IMMEDIATE 'CREATE TABLE '||:TABLE_NAME||'_'||DAT||' AS SELECT '||v_listStr ||' FROM RAW_DATA WHERE ROUND_ID BETWEEN '||:START_RP ||' AND '||:END_RP;
END;
/
can you help me figure out why it always cnt gives 0 even the table is exist
This query can only ever return 0:
select count(*) into cnt
from user_tables
where table_name = table_name || '_' || dat;
You need to either prefix table_name with the procedure name
select count(*) into cnt
from user_tables
where table_name = use_rawdata.table_name || '_' || dat;
or else name your parameters differently (tableName, p_table_name etc).
I'd also suggest removing the first begin and end as they aren't doing anything, and adjusting your indentation to reflect the code structure more accurately.
I make it something like this:
create or replace procedure use_rawdata
( start_rp in number
, end_rp in number
, table_name varchar2 )
is
v_liststr clob;
dat varchar2(8) := to_char(sysdate, 'yyyymmdd');
cnt number;
begin
select count(*) into cnt
from user_tables
where table_name = upper(use_rawdata.table_name) || '_' || dat;
dbms_output.put_line(table_name || '_' || dat);
dbms_output.put_line(cnt);
if cnt = 1 then
execute immediate 'DROP TABLE ' || table_name || '_' || dat;
dbms_output.put_line('DROP TABLE ' || table_name || '_' || dat);
end if;
select rtrim(xmlagg(xmlelement(e, column_name, ', ').extract('//text()') order by column_id).getclobval(), ', ') x
into v_liststr
from raw_data_metadata
where (start_round <= end_rp and end_round is null)
or (start_round >= start_rp and end_round <= end_rp);
execute immediate 'create table ' || table_name || '_' || dat || ' as select ' || v_liststr || ' from raw_data where round_id between ' || start_rp || ' and ' || end_rp;
end;
I am newbie to IBM db2.Need to convert the below mentioned SP to db2 syntax. But i am stuck with many equivalents used or available in Db2. Even google research doesn't show how exactly we can compare object id of tables in db2 as I am doing in SQL Server stored procedure. Could anyone suggest me with right way to proceed?
EDIT: I have updated with equivalent DB2 syntax, but facing below error while deploying at the particular line, Can anyone identify and help me understand what is wrong with this syntax or the problem lies anywhere else in the procedure.
line no 25 : DECLARE v_sqlstate CHAR(5);
BACKUPTABLE: 25: An unexpected token "<variable declaration> was found following "". Expected tokens may include: "".. SQLCODE=-104, SQLSTATE=42601, DRIVER=4.18.60
An unexpected token variable declaration was found following "". Expected tokens may include: "".. SQLCODE=-104, SQLSTATE=42601, DRIVER=4.18.60
SQL Server Stored procedure syntax:
CREATE PROCEDURE [dbo].[BackUpTable]
#TableName sysname
AS
BEGIN
SET nocount ON
DECLARE #sql VARCHAR(500)
IF EXISTS (SELECT *
FROM sys.objects
WHERE object_id = Object_id(N'[dbo].[' + #TableName+'_EST' + ']')
AND TYPE IN ( N'U' ))
BEGIN
SET #sql = 'declare #Done bit
set #Done = 0
while #Done = 0
begin
delete top (100000)
from ' + #TableName + '_Bak' +
' if ##rowcount = 0
set #Done = 1
end;'
SET #sql = #sql + 'insert into ' + #TableName + '_Bak select * from ' +
#TableName +'_EST'
EXEC(#sql)
END
ELSE
BEGIN
DECLARE #err_message VARCHAR(300)
SELECT #err_message = 'The table "' + Isnull(#TableName, 'null') +
'" does not exist'
RAISERROR (#err_message, 16, 1)
END
END
DB2 SYNTAX CREATED SO FAR:
CREATE OR REPLACE PROCEDURE BackUpTable (IN TableName VARCHAR(128))
DYNAMIC RESULT SETS 1
BEGIN
DECLARE dynamicSql VARCHAR(500);
IF(EXISTS(
SELECT * FROM SYSIBM.SYSTABLES
WHERE NAME = TableName||'_EST'
)
)
THEN
SET dynamicSql = 'DELETE FROM '||TableName ||'_BAK';
SET dynamicSql = dynamicSql ||'insert into ' || TableName || '_BAK select * from ' ||
TableName || '_EST';
EXECUTE IMMEDIATE dynamicSql;
ELSE
DECLARE v_sqlstate CHAR(5);
DECLARE v_sqlcode INT;
DECLARE SQLSTATE CHAR(5) DEFAULT '00000';
DECLARE SQLCODE INT DEFAULT 0;
DECLARE EXIT HANDLER FOR SQLEXCEPTION
BEGIN
SELECT SQLSTATE, SQLCODE
INTO v_sqlstate, v_sqlcode
FROM sysibm.sysdummy1;
SET O_Error_Msg = 'TABLE IS NOT AVAILABLE:: SQLState : '||v_sqlstate||' SQLCode : '||v_sqlcode ;
END;
END IF;
END
on z/os you can do it:
IF( EXISTS( SELECT 1 FROM QSYS2.SYSTABLES WHERE TABLE_SCHEMA = 'YOURLIB' AND TABLE_NAME = 'YOURTABLENAME')) THEN
DROP TABLE YOURLIB.YOURTABLENAME;
END IF;
System info
Oracle database 11g/11.2 Enterprise edition
Summary
When the procedure is called (via Hibernate's CallableStatements), it gets all the partitions to be exported based on the days to keep (an input parameter). After those partitions are evaluated, the function which accepts partitions lists as input parameters (the first one in the wall of code) uses the DBMS_DATAPUMP API to export the content into a flat file.
Sometimes (I don't have a 100% reproducible scenario) it fails with the error below. It has to be noted that 99.9% of the time these procedures run smootly. The database user has write access to the exports directory.
The procedure:
TYPE PARTITION_NAME_TYPE IS RECORD (
PARTITION_NAME VARCHAR2(32)
);
TYPE PARTITION_LIST IS REF CURSOR RETURN PARTITION_NAME_TYPE;
PROCEDURE EXPORT_PARTITIONS (
P_TABLE_NAME IN VARCHAR2,
P_PARTITION_LIST IN PARTITION_LIST,
P_EXPORT_DIR IN VARCHAR2,
P_PARALLELISM_DEGREE IN NUMBER DEFAULT 1,
P_FILE_PATH OUT VARCHAR2,
P_RESULT OUT VARCHAR2,
P_RESULT_MSG OUT VARCHAR2)
IS
V_PUMP_HANDLE NUMBER; -- Data Pump job handle
V_PUMP_STATE VARCHAR2(255);
V_PUMP_FILE VARCHAR2(255);
V_PUMP_DIR VARCHAR2(255);
V_JOB_STATE VARCHAR2(4000);
V_STATUS KU$_STATUS1010;
V_LOGS KU$_LOGENTRY1010;
V_ROW PLS_INTEGER;
V_ERRORDETAILS VARCHAR2(4000);
V_PARTITION_LIST VARCHAR2(32767);
V_PARTITION_LIST_TEMP PARTITION_NAME_TYPE;
BEGIN
FETCH P_PARTITION_LIST INTO V_PARTITION_LIST_TEMP;
IF P_PARTITION_LIST%NOTFOUND THEN
P_RESULT := 'FAILED';
P_RESULT_MSG := 'No partitions to export';
RETURN;
END IF;
V_PARTITION_LIST := V_PARTITION_LIST_TEMP.PARTITION_NAME;
V_PUMP_FILE := V_PARTITION_LIST_TEMP.PARTITION_NAME;
LOOP
FETCH P_PARTITION_LIST INTO V_PARTITION_LIST_TEMP;
IF P_PARTITION_LIST%NOTFOUND THEN
V_PUMP_FILE := V_PUMP_FILE || '_TO_' || V_PARTITION_LIST_TEMP.PARTITION_NAME;
EXIT;
END IF;
V_PARTITION_LIST := V_PARTITION_LIST || ',' || V_PARTITION_LIST_TEMP.PARTITION_NAME;
END LOOP;
V_PUMP_HANDLE := DBMS_DATAPUMP.OPEN('EXPORT', 'TABLE');
DBMS_DATAPUMP.ADD_FILE(HANDLE => V_PUMP_HANDLE, FILENAME => V_PUMP_FILE || '.dbf' , DIRECTORY => P_EXPORT_DIR, FILETYPE => DBMS_DATAPUMP.KU$_FILE_TYPE_DUMP_FILE);
DBMS_DATAPUMP.ADD_FILE(V_PUMP_HANDLE, V_PUMP_FILE || '.log',P_EXPORT_DIR, NULL, DBMS_DATAPUMP.KU$_FILE_TYPE_LOG_FILE);
DBMS_DATAPUMP.DATA_FILTER (V_PUMP_HANDLE, 'PARTITION_LIST', V_PARTITION_LIST);
DBMS_DATAPUMP.SET_PARALLEL(V_PUMP_HANDLE, P_PARALLELISM_DEGREE);
DBMS_DATAPUMP.START_JOB (V_PUMP_HANDLE);
DBMS_DATAPUMP.WAIT_FOR_JOB(V_PUMP_HANDLE, V_PUMP_STATE);
DBMS_DATAPUMP.DETACH(V_PUMP_HANDLE);
P_RESULT := 'SUCCESS';
-- get actual path to Export Directory
SELECT directory_path INTO V_PUMP_DIR FROM ALL_DIRECTORIES WHERE directory_name = P_EXPORT_DIR;
P_FILE_PATH := V_PUMP_DIR || '/' || V_PUMP_FILE || '.dbf';
P_RESULT_MSG := 'Successfully exported ' || P_TABLE_NAME || '(' || V_PARTITION_LIST || ') as ' || V_PUMP_FILE || '.dbf';
EXCEPTION
WHEN OTHERS THEN
DBMS_DATAPUMP.GET_STATUS(V_PUMP_HANDLE, 8, 0, V_JOB_STATE, V_STATUS);
V_LOGS := V_STATUS.ERROR;
V_ROW := V_LOGS.FIRST;
LOOP
EXIT WHEN V_ROW IS NULL;
V_ERRORDETAILS := 'LOGLINENUMBER='||V_LOGS(V_ROW).LOGLINENUMBER || ' ERRORNUMBER='||V_LOGS(V_ROW).ERRORNUMBER || ' LOGTEXT='||V_LOGS(V_ROW).LOGTEXT;
V_ROW := V_LOGS.NEXT (V_ROW);
END LOOP;
P_RESULT_MSG := 'Failed to export partitions on ' || P_TABLE_NAME || '(' || V_PARTITION_LIST || ') : ' || SQLERRM || ' Details: (' || V_ERRORDETAILS || ')';
P_RESULT := 'FAILED';
DBMS_DATAPUMP.STOP_JOB(V_PUMP_HANDLE);
END EXPORT_PARTITIONS;
PROCEDURE EXPORT_PARTITIONS (
P_TABLE_NAME IN VARCHAR2,
P_NUM_DAYS_TO_KEEP IN NUMBER,
P_EXPORT_DIR IN VARCHAR2,
P_PARALLELISM_DEGREE IN NUMBER DEFAULT 1,
P_FILE_PATH OUT VARCHAR2,
P_RESULT OUT VARCHAR2,
P_RESULT_MSG OUT VARCHAR2)
IS
V_MIN_DAYS_TO_KEEP NUMBER := 1;
V_CNT NUMBER;
V_PARTITIONS PARTITION_LIST;
V_EXPORT_PARTITIONS_RESULT VARCHAR2(2000);
V_EXPORT_PARTITIONS_RESULT_MSG VARCHAR2(2000);
BEGIN
IF P_NUM_DAYS_TO_KEEP < V_MIN_DAYS_TO_KEEP THEN
RAISE_APPLICATION_ERROR(-20000, 'Cannot drop partitions less than ' || V_MIN_DAYS_TO_KEEP || ' days old.');
END IF;
SELECT COUNT(*)
INTO V_CNT
FROM USER_TABLES
WHERE TABLE_NAME = P_TABLE_NAME;
IF V_CNT = 0 THEN
RAISE_APPLICATION_ERROR(-20000, 'Table Does Not Exist');
END IF;
OPEN V_PARTITIONS FOR SELECT partition_name
FROM USER_TAB_PARTITIONS
WHERE table_name = UPPER(P_TABLE_NAME)
AND
CASE
WHEN INSTR(PARTITION_NAME, 'MAXVALUE') = 0
THEN TO_DATE(SUBSTR(PARTITION_NAME, -8), 'YYYYMMDD')
END < TRUNC(SYSDATE) - P_NUM_DAYS_TO_KEEP
ORDER BY partition_name ASC;
EXPORT_PARTITIONS(P_TABLE_NAME, V_PARTITIONS, P_EXPORT_DIR, P_PARALLELISM_DEGREE, P_FILE_PATH, V_EXPORT_PARTITIONS_RESULT, V_EXPORT_PARTITIONS_RESULT_MSG);
CLOSE V_PARTITIONS;
IF INSTR(V_EXPORT_PARTITIONS_RESULT, 'SUCCESS') = 0 THEN
RAISE_APPLICATION_ERROR(-20000, 'Cannot export partitions. Logs purging will not proceede. ' || V_EXPORT_PARTITIONS_RESULT_MSG);
END IF;
P_RESULT := 'SUCCESS';
P_RESULT_MSG := V_EXPORT_PARTITIONS_RESULT_MSG;
EXCEPTION
WHEN OTHERS THEN
P_RESULT_MSG := 'Failed To Export Partition on ' || P_TABLE_NAME || ': ' || SQLERRM;
P_RESULT := 'FAILED';
END EXPORT_PARTITIONS;
The error:
ORA-06502: PL/SQL: numeric or value error: character string buffer too small
ORA-06512: at * . *, line 208
ORA-39001: invalid argument value
What's on line 208 can be found below:
SQL> select text from all_source where type = 'PACKAGE BODY' and owner = '***' and name = '***' and line = '208';
TEXT
--------------------------------------------------------------------------------
P_RESULT_MSG := 'Failed to export partitions on ' || P_TABLE_NAME || '(' || V_
PARTITION_LIST || ') : ' || SQLERRM || ' Details: (' || V_ERRORDETAILS || ')';
After busting my head around for several days, I still haven't managed to find the problematic piece of code.
Edit #1
The caller of the procedure is a Hibernate CallableStatement. The declaration of the resultMsg on the java side is as follows:
call.registerOutParameter("resultMsg", Types.VARCHAR);
This calls the EXPORT_PARTITIONS(tableName, daysToKeep, exportDir, paralelismDegree) procedure, which then calls the second EXPORT_PARTITIONS procedure.
CREATE OR REPLACE PROCEDURE STATUS_MAIL(FROM_MAIL IN VARCHAR2, TO_MAIL IN VARCHAR2)
is
v_From VARCHAR2(80) := FROM_MAIL;
v_Recipient VARCHAR2(80) := TO_MAIL;
v_Subject VARCHAR2(80) := 'EMPLOYEE STATUS';
v_Mail_Host VARCHAR2(30) := 'xx.xx.xxx.xxx';
v_Mail_Conn utl_smtp.Connection;
v_msg_body VARCHAR2(5000);
v_output VARCHAR2(5000);
BEGIN
/*Result always returns 42 rows*/
v_output := 'select empid,ename,mobile,dept from employee';
EXECUTE IMMEDIATE v_output into v_msg_body;
v_Mail_Conn := utl_smtp.Open_Connection(v_Mail_Host, xx);
utl_smtp.Helo(v_Mail_Conn, v_Mail_Host);
utl_smtp.Mail(v_Mail_Conn, v_From);
utl_smtp.Rcpt(v_Mail_Conn, v_Recipient);
utl_smtp.Data(v_Mail_Conn,
'Date: ' || to_char(sysdate, 'Dy, DD Mon YYYY hh24:mi:ss') || UTL_TCP.crlf ||
'From: ' || v_From || UTL_TCP.crlf ||
'Subject: '|| v_Subject || UTL_TCP.crlf ||
'To: ' || v_Recipient || UTL_TCP.crlf ||
UTL_TCP.crlf || v_msg_body );
utl_smtp.Quit(v_mail_conn);
EXCEPTION
WHEN utl_smtp.Transient_Error OR utl_smtp.Permanent_Error then
raise_application_error(-20000, 'Unable to send mail: '||sqlerrm);
END;
Getting an error inconsistent datatypes
# EXECUTE IMMEDIATE v_output into v_msg_body
while executing the above procedure, please help me....
So in fact your real question is: "how do I aggregate multiple rows into a single string ?"
The answer is to use aggregate functions. Oracle has introduced listagg-function in 11gR2 that solves this problem nicely but in earlier releases one has to do a bit more work.
When you know the right keywords Google finds a plenty of great resources, e.g.
String Aggregation Techniques
listagg function in 11g release 2
the collect function in 10g
I have collected the following examples from the above mentioned resources. Hope this gives you a good starting point:
The table that will be queried:
create table foo (d1 number, d2 varchar2(10));
insert all
into foo values(1, 'a')
into foo values(2, 'b')
into foo values(3, 'c')
select 1 from dual;
commit;
Oracle 11gR2:
declare
v_str varchar2(32767);
begin
select listagg('key = ' || d1 || ' value = ' || d2, chr(10))
within group (order by d1)
into v_str
from foo;
dbms_output.put_line(v_str);
exception
when value_error then
dbms_output.put_line('Exception: trying to insert too many characters to a varchar2 variable.');
end;
/
Oracle 10g:
create or replace type str_list_t as table of varchar2(32676);
/
create function to_string (
nt_in in str_list_t,
delimiter_in in varchar2 default ','
) return varchar2 is
v_idx pls_integer;
v_str varchar2(32767);
v_dlm varchar2(10);
begin
v_idx := nt_in.first;
while v_idx is not null loop
v_str := v_str || v_dlm || nt_in(v_idx);
v_dlm := delimiter_in;
v_idx := nt_in.next(v_idx);
end loop;
return v_str;
end;
/
declare
v_str varchar2(32676);
begin
select to_string(cast(collect('key = ' || d1 || ' value = ' || d2) as str_list_t), chr(10))
into v_str
from foo;
dbms_output.put_line(v_str);
exception
when value_error then
dbms_output.put_line('Exception: trying to insert too many characters to a varchar2 variable.');
end;
/
Note how I'll catch value_error exception that will be raised if the aggregated string won't fit into the reserved varchar2 capacity.
Output of both examples:
key = 1 value = a
key = 2 value = b
key = 3 value = c
I am new to DB2 queries.
Here, I am passing a comma separated value as an IN parameter in a Stored Procedure. I want to search on the basis of those values.
Select * from USER where user_id in (IN_User);
Here, IN_User will have values of the kind ('val1','val2','val3')
It should return all the rows which has val1 or val2 or val3 as the User_id. As much as I know this can be done using UDF but I want to know is there any other way to do it without UDF.
please create a function to split the comma separated string
Please see the below function
CREATE FUNCTION StringToRows(
cString1 CLOB (10 M) ,
cStringSplitting1 VARCHAR(10) )
RETURNS TABLE (Lines VARCHAR(500))
SPECIFIC StringToRows_Big
DETERMINISTIC
NO EXTERNAL ACTION
CONTAINS SQL
BEGIN ATOMIC
DECLARE cStringSplitting VARCHAR(10);
DECLARE LenSplit SMALLINT;
SET cStringSplitting = cStringSplitting1;
SET LenSplit = LENGTH(cStringSplitting);
IF LENGTH(TRIM(cStringSplitting)) = 0 THEN
SET cStringSplitting = ' ', LenSplit = 1 ;
END IF ;
RETURN WITH
TEMP1 ( STRING) as (values (cString1) ),
TEMP2 ( Lines, STRING_left) as
(SELECT
SUBSTR(STRING,1, CASE WHEN LOCATE(cStringSplitting, STRING) = 0 THEN LENGTH(STRING) ELSE LOCATE(cStringSplitting,STRING) - 1 END),
(CASE WHEN (LOCATE(cStringSplitting, STRING) = 0) THEN '' ELSE SUBSTR(STRING, LOCATE(cStringSplitting,STRING) + LenSplit) END)
FROM TEMP1 WHERE LENGTH(STRING) > 0
UNION ALL
SELECT
SUBSTR(STRING_left,1, CASE LOCATE(cStringSplitting,STRING_left) WHEN 0 THEN LENGTH(STRING_left) ELSE LOCATE(cStringSplitting,STRING_left) - 1 END),
(CASE WHEN LOCATE(cStringSplitting,STRING_left) = 0 THEN '' ELSE SUBSTR(STRING_left, LOCATE(cStringSplitting,STRING_left) + LenSplit) END)
FROM TEMP2 WHERE LENGTH(STRING_left) > 0 )
SELECT Lines FROM TEMP2;
END
please see the sample stored procedure to call the function
CREATE PROCEDURE TEST_USR(IN #inputParam CLOB (10 M))
SPECIFIC TEST_USR
DYNAMIC RESULT SETS 1
P1: BEGIN
DECLARE CURSOR1 CURSOR WITH RETURN FOR
Select * from USER where user_id IN (SELECT * FROM TABLE(StringToRows(#inputParam, ',')) AS test);
OPEN CURSOR1;
END P1