Alternative for conditional subquery in Oracle 11g - oracle

I'm getting more and more experienced with oracle pl/sql but this problem seems to be persistent: I have a procedure that merges external data into a table in the database that looks something like this:
PROCEDURE updateTable (ts DATE, val NUMBER, id NUMBER)
BEGIN
IF id NOT IN (15, 16, 23)
THEN
MERGE INTO myTable dest
USING (SELECT ts, val, id FROM Dual) src
ON (src.id = dest.id AND src.ts = dest.ts)
WHEN MATCHED THEN UPDATE SET dest.val = src.val
WHEN NOT MATCHED THEN INSERT (ts, val, id) VALUES (src.ts, src.val, src.id);
END IF;
END;
This works just fine so far. Now the problem is that the list of id's that are excluded is hardcoded and it would be much more dynamic to have those in another table, i.e. in the code above replace the line
IF id NOT IN (15, 16, 23)
with something like
IF id NOT IN (SELECT id FROM excluTable)
which returns the notorious error: PLS_00405: subquery not allowed in this context
If it was only one id, I could simply create a variable and select the id into it. Unfortunately it's quite a long list. I've tried to bulk collect them into an array but then I don't find a way to put that into the conditional clause either. I'm sure there is an elegant solution for this.
Thanks for your help!

There may be many IDs in your exclusion table, but you are only passing one into the procedure. You can see if that single value exists in the table with a count into a local variable, and then check whether the count was zero or non-zero; something like:
PROCEDURE updateTable (ts DATE, val NUMBER, id NUMBER) IS
l_excl_id PLS_INTEGER;
BEGIN
SELECT count(*)
INTO l_excl_id
FROM excluTable
WHERE excluTable.id = updateTable.id;
IF l_excl_id = 0
THEN
MERGE INTO myTable dest
USING (SELECT ts, val, id FROM Dual) src
ON (src.id = dest.id AND src.ts = dest.ts)
WHEN MATCHED THEN UPDATE SET dest.val = src.val
WHEN NOT MATCHED THEN INSERT (ts, val, id) VALUES (src.ts, src.val, src.id);
END IF;
END;
Incidentally, it can get confusing if your procedure argument names are the same as table column names, or other identifiers. For instance, as id is the procedure argument name and the column name in the table I've had to prefix them both:
WHERE excluTable.id = updateTable.id;
one with the table name (or alias if you add one), the other with the procedure name. If you just did
WHERE excluTable.id = id
then the scoping rules would mean it matched every ID in the table with itself, not the argument, so you would be counting all rows - and it might not be immediately obvious why it wasn't behaving as you expected. If the arguments were named as, say, p_ts and p_id then you wouldn't have to account for that ambiguity. That's also why I've prefixed my local flag variable with l_.

Related

How to duplicate rows with a column change for a list of tables?

Given the following example:
BEGIN
FOR r IN (
SELECT * FROM table_one WHERE change_id = 0
) LOOP
r.change_id := -1;
INSERT INTO table_one VALUES r;
END LOOP;
END;
This inserts new rows to table_one with the exact same content, except the intended change on column change_id to the value -1. I don't have to specify the columns inside of the script as I have to in an INSERT INTO table_one (change_id, ...) SELECT -1, ... FROM table_one WHERE change_id=0;
It works perfectly fine. But how to modify this script to work with a list of tables? The internal structure of those tables are different, but all of them have the necessary column change_id.
Of course the easiest solution would be to copy and paste this snippet x-times and replace the fix table name inside. But is there an option to work with a list of tables in an array?
My approach was like this:
DECLARE
TYPE tablenamearray IS VARRAY(30) OF VARCHAR2(30);
tablenames tablenamearray;
BEGIN
tablenames := tablenamearray('TABLE_ONE', 'TABLE_TWO', 'TABLE_THREE'); -- up to table 30...
FOR i IN tablenames.first..tablenames.last LOOP
/* Found no option to use tablenames(i) here with dynamic SQL */
END LOOP;
END;
Note: There is no technical primary key like an id with a sequence behind. The primary key is build by three columns incl. the change_id column.
You cannot create a SQL statement where the statement is not known at parse time. So, you cannot have a variable as a table name. What you're looking for is Dynamic SQL, which is a fairly complicated topic, but basically you're going to wind up building a SQL statement with DBMS_SQL or running a statement as a string with EXECUTE IMMEDIATE.

Function results column names to be used in select statement

I have function which returns column names and i am trying to use the column name as part of my select statement, but my results are coming as column name instead of values
FUNCTION returning column name:
get_col_name(input1, input2)
Can И use this query to the results of the column from table -
SELECT GET_COL_NAME(input1,input2) FROM TABLE;
There are a few ways to run dynamic SQL directly inside a SQL statement. These techniques should be avoided since they are usually complicated, slow, and buggy. Before you do this try to find another way to solve the problem.
The below solution uses DBMS_XMLGEN.GETXML to produce XML from a dynamically created SQL statement, and then uses XML table processing to extract the value.
This is the simplest way to run dynamic SQL in SQL, and it only requires built-in packages. The main limitation is that the number and type of columns is still fixed. If you need a function that returns an unknown number of columns you'll need something more powerful, like the open source program Method4. But that level of dynamic code gets even more difficult and should only be used after careful consideration.
Sample schema
--drop table table1;
create table table1(a number, b number);
insert into table1 values(1, 2);
commit;
Function that returns column name
create or replace function get_col_name(input1 number, input2 number) return varchar2 is
begin
if input1 = 0 then
return 'a';
else
return 'b';
end if;
end;
/
Sample query and result
select dynamic_column
from
(
select xmltype(dbms_xmlgen.getxml('
select '||get_col_name(0,0)||' dynamic_column from table1'
)) xml_results
from dual
)
cross join
xmltable
(
'/ROWSET/ROW'
passing xml_results
columns dynamic_column varchar2(4000) path 'DYNAMIC_COLUMN'
);
DYNAMIC_COLUMN
--------------
1
If you change the inputs to the function the new value is 2 from column B. Use this SQL Fiddle to test the code.

How to implement multi value parameter in spagoBi server

I made a birt report in spagoBI studio with multi_value parameter it works fine in studio.
But when i upload it to server it's execution give a blank page.
pls someone help me.
Has been a couple of years since the queston was asked but I thought I would post my experience with this issue as it may help others out there.
The problem is due to the fact that each value in the parameter string is being wrapped with single quotes so no where condition is met in your report's sql where statement.
So if you are using Postgresql see: https://www.spagoworld.org/jforum/posts/list/382.page. However if like me you are using MySQL then that where all the fun and games begin because MySQL does not have a ready to use regxp_split_to_table function! What worked for me was to use temporary tables and a stored procedure to return the report dataset. I then called the procedure in the report's queryString.
So the following were the steps I took:
create a function to split out each parameter from the multivalue String and remove the single quotes:
CREATE DEFINER=root#localhost FUNCTION SPLIT_STR(
x VARCHAR(255),
delim VARCHAR(12),
pos INT
) RETURNS varchar(255) CHARSET utf8
RETURN REPLACE(SUBSTRING(SUBSTRING_INDEX(x, delim, pos),
LENGTH(SUBSTRING_INDEX(x, delim, pos -1)) + 1),
delim, '')
Create a stored procedure to return the parameters as a resultset that can be matched via your sql's where in ($P{parameter}) statement. Thet trick here is to take the split and cleaned up parameter and insert it into a temporary table that can then be queried in the subsequent select statement that returns the dataset. My stored procedure looks like:
CREATE DEFINER=root#localhost PROCEDURE create_temp_breweries(fullstr varchar(255), startDate date, endDate date, outlet_Type varchar(255))
BEGIN
DECLARE a INT Default 0 ;
DECLARE b INT Default 0 ;
DECLARE str VARCHAR(255);
DECLARE outletStr VARCHAR(255);
drop temporary table if exists temp_table1;
create temporary table temp_breweries(col1 varchar(255));
drop temporary table if exists temp_table2;
create temporary table temp_outletTypes(col2 varchar(255));
loop1: LOOP
SET a=a+1;
SET str= REPLACE(SPLIT_STR(fullstr,",",a),'\'', '');
IF str='' THEN
LEAVE loop1;
END IF;
#Do Inserts into temp table here with str going into the row
insert into temp_table1 values (str);
END LOOP loop1;
loop2: LOOP
SET b=b+1;
SET outletStr= REPLACE(SPLIT_STR(outlet_Type,",",b),'\'', '');
IF outletStr='' THEN
LEAVE loop2;
END IF;
#Do Inserts into temp table here with outletStr going into the row
insert into temp_table2 values (outletStr);
# For testing: insert into mytest (brewery) values (outletStr);
END LOOP loop2;
SELECT [fields]
FROM
[tables]
WHERE
BINARY field IN (SELECT * FROM temp_table1)
AND DATE BETWEEN startDate AND endDate AND
BINARY field2 IN (SELECT * FROM temp_table2);
END
Got this from: MySQL Split Comma Separated String Into Temp Table.
I am sure there must be a better or easier way but this worked like a charm!

Merge function error - On Clause cannot be updated - PK issue

Procedure is to check the the eid ,and do merge. While updating the existing row, it needs to update with the eid.seq.nextval.
I have created a the sequence and calling in Procedure.
CREATE OR REPLACE PROCEDURE Temp(
Eid Number,
dpt varchar2
) As
BEGIN
MERGE INTO Src1 e
USING (select v_eid as eid
, v_dept as dept
FROM dual) d
ON (e.eid = d.eid)
WHEN NOT MATCHED THEN
INSERT (e.eid,e.dept)
VALUES(d.eid, d.dept)
WHEN MATCHED THEN
UPDATE SET e.eid = eid_SEQ.nextval, e.dept = d.dept;
END;
/
Error:
1.ORA--38104:ORA-38104: Columns referenced in the ON Clause cannot be updated.
IF I remove the ON clause condition then PK cannot be null error .
Also, the best procedure to call the seq.nextval in the procedure.
Any help is greatly appreciated!
What you want to do is impossible with a merge statement. Think about it: you're trying to update the value of a field that is used to determine if the field should be updated. This is not safe, especially if the merge statement updates/inserts more than one row.
Apart from that it is close to impossible to suggest an alternative since eid seems to be a primary key and updating primary keys is normally a bad thing.

Update or insert based on if employee exist in table

Do want to create Stored procc which updates or inserts into table based on the condition if current line does not exist in table?
This is what I have come up with so far:
PROCEDURE SP_UPDATE_EMPLOYEE
(
SSN VARCHAR2,
NAME VARCHAR2
)
AS
BEGIN
IF EXISTS(SELECT * FROM tblEMPLOYEE a where a.ssn = SSN)
--what ? just carry on to else
ELSE
INSERT INTO pb_mifid (ssn, NAME)
VALUES (SSN, NAME);
END;
Is this the way to achieve this?
This is quite a common pattern. Depending on what version of Oracle you are running, you could use the merge statement (I am not sure what version it appeared in).
create table test_merge (id integer, c2 varchar2(255));
create unique index test_merge_idx1 on test_merge(id);
merge into test_merge t
using (select 1 id, 'foobar' c2 from dual) s
on (t.id = s.id)
when matched then update set c2 = s.c2
when not matched then insert (id, c2)
values (s.id, s.c2);
Merge is intended to merge data from a source table, but you can fake it for individual rows by selecting the data from dual.
If you cannot use merge, then optimize for the most common case. Will the proc usually not find a record and need to insert it, or will it usually need to update an existing record?
If inserting will be most common, code such as the following is probably best:
begin
insert into t (columns)
values ()
exception
when dup_val_on_index then
update t set cols = values
end;
If update is the most common, then turn the procedure around:
begin
update t set cols = values;
if sql%rowcount = 0 then
-- nothing was updated, so the record doesn't exist, insert it.
insert into t (columns)
values ();
end if;
end;
You should not issue a select to check for the row and make the decision based on the result - that means you will always need to run two SQL statements, when you can get away with one most of the time (or always if you use merge). The less SQL statements you use, the better your code will perform.
BEGIN
INSERT INTO pb_mifid (ssn, NAME)
select SSN, NAME from dual
where not exists(SELECT * FROM tblEMPLOYEE a where a.ssn = SSN);
END;
UPDATE:
Attention, you should name your parameter p_ssn(distinguish to the column SSN ), and the query become:
INSERT INTO pb_mifid (ssn, NAME)
select P_SSN, NAME from dual
where not exists(SELECT * FROM tblEMPLOYEE a where a.ssn = P_SSN);
because this allways exists:
SELECT * FROM tblEMPLOYEE a where a.ssn = SSN

Resources