IF-ELSE issue in Select Statement in Oracle - oracle

I have following query in SQL Server which I am trying to convert to Oracle 11g.
IF '[Param.1]' = 'S' OR '[Param.1]' = 'T' THEN
select * from ULQUEUE
END IF
But when I write the same query in Oracle, it gives error stating Invalid SQL Statement. So how do I incorporate IF-ELSE in Select Statement in Oracle?

You are converting a T-SQL statement. The equivalent in Oracle is an anonymous PL/SQL block.
However, PL/SQL is a bit more demanding than T-SQL. It requires selecting rows into variables. If the query will return more than one row we need to define a collection variable or use a cursor.
Depending on your requirements you may enbd up with something like this:
begin
if ( &&param1 = 'S' or &&param1 = 'T' ) then
for lrec in ( select * from ulqueue ) loop
do_something;
end loop;
end if;
end;
/
I agree this looks like more work than T-SQL, but PL/SQL is a proper programming language with a lot more functionality. Find out more.

Related

How to Declare, Populate and Use a Variable using Oracle PL/SQL

I swear this has been asked so many times previously, yet I cannot seem to apply other examples to my use case:
First things first, this query will be executed as part of an Informatica SQL Source Qualifier, and in some circumstances, be passed-through from an SQL Server OpenQuery statement, so please be mindful of this, and
that SQL Plus will not be used be used; Oracle SQL Developer is used only for code development.
My history is primarily SQL Server & Teradata, but as the title suggests, I now have a requirement where I need to declare, populate and use a variable in Oracle, all within the same procedure. Not SP, so no In/Out declarations
In SQL Server, this code will work as expected (line numbers added for clarity):
1. Declare #MaxDate Int
2.
3. With f_data (cal_period) As (Select 201904 As cal_period)
4.
5. Select #MaxDate = Max(cal_period) From f_data
6.
7. Select
8. Case
9. When (#MaxDate%100) < 12 Then #MaxDate+1
10. Else (#MaxDate+100) - ((#MaxDate%100)-1)
11. End As dt
Line 1: These are YYYYMM date periods defined as int
Line 3: I am using an inline view (CTE) here for illustration, and to
make it easier for you to copy and paste, but in reality, this is
actually a physical control table, so would not normally be visible in the
script.
Line 5: Populates the parameter (SQL Server prefixes parameters with
the at symbol) with the single-value resultset
Line 7-11: Is simply the logic to progress the period by one, the
percentage-mark in SQL Server is the Modulus function, Oracle is
written as Mod(#MaxDate,100)
For those unfamiliar with SQL Server, it does not need a reference table such as Dual ("Sys.Dual") in order to execute the query, such that for Oracle a "From Dual" statement is necessary on the missing Line 12
My requirement is essentially a carbon-copy of the above T-SQL, so I need to declare a one-time use variable, to populate that variable with the results of an SQL query, and then to use this variable in a transformation - the result of which is captured to an Informatica and SSIS variable for later use.
So far, I have tried declaring a variable, this seemed to work (by which I mean it didn't return an error):
Declare MaxPeriod Int;
Begin
Select 201904 Into MaxPeriod From Dual;
End;
And populating from an SQL statement is also showing as successfully completed:
Declare MaxPeriod Int;
Begin
Select Max(MaxPeriodVal) Into MaxPeriod From CtrlTable;
End;
Although I can't seem to get beyond this to actually test the variable as put.line statements fail, as do simple Case checks:
Declare MaxPeriod Int;
Begin
Select 201904 Into MaxPeriod From Dual;
End;
Select
Case
When 201904 = MaxPeriod Then 'Match'
Else 'No Match'
End As dteChk
From Dual;
I have attempted to prefix the MaxPeriod in the check with a colon, and, to have prefixed,suffixed/both with an ampersand eg :MaxPeriod; &MaxPeriod; MaxPeriod&; &MaxPeriod&
All of which failed.
The basic issue is a variable scope problem. You're declaring MaxPeriod within the context of a PL/SQL anonymous block, so it will disappear (fall out of scope) when the block ends on line 4.
You could put your entire query inside the PL/SQL block, but there's not an easy way to return an entire result set from a PL/SQL block, so I don't think you want that.
I don't know how your Oracle driver handles native queries, but this might work:
var MaxPeriod number; -- bind variable declared as global scope for this script
Begin -- one of several ways to assign values to bind variables
:MaxPeriod := 201904;
End;
/
Select
Case
When 201904 = :MaxPeriod Then 'Match'
Else 'No Match'
End As dteChk
From Dual;
If the var syntax doesn't work for you to declare a SQL bind variable, then you may have to look into some other way of passing a bind variable for the query string. You could probably pass a null value (for a number datatype, anyway) and then overwrite it in the SQL script.
Alternately, in your original example code, I think I'd use a CTE or an inline view instead of a variable anyway.
With f_data As (Select 201904 As cal_period from dual)
Select
Case
When Mod(MaxDate,100) < 12 Then MaxDate+1
Else (MaxDate+100) - (Mod(MaxDate,100)-1)
End As dt
from (Select Max(cal_period) as MaxDate From f_data) mp
You can use substitution variable using define in sql*plus as following.
Define MaxPeriod := 201904
Select
Case
When &MaxPeriod = MaxPeriod Then 'Match'
Else 'No Match'
End As dteChk
From Dual;
Cheers!!

cannot mix between single row and multi-row (BULK) in INTO list.ERROR in proc

CREATE OR REPLACE PROCEDURE RDBSTAGE.ATCHMNT_ERR_FILEID AUTHID CURRENT_USER
IS
CURSOR cv_atchtab IS
SELECT * FROM ATTACHMENT_ERROR;
I_ATCHMNT_ERR cv_atchtab%ROWTYPE;
V_FILE_ID VARCHAR2(40);
BEGIN
OPEN cv_atchtab;
LOOP
FETCH cv_atchtab BULK COLLECT INTO I_ATCHMNT_ERR;
EXIT WHEN cv_atchtab%NOTFOUND;
FOR i IN 1..I_ATCHMNT_ERR.COUNT
LOOP
SELECT FILE_ID BULK COLLECT
INTO V_FILE_ID
FROM ATTACHMENT_CLAIM t1
WHERE t1.CLAIM_TCN_ID=I_ATCHMNT_ERR(i).CLAIM_TCN_ID;
UPDATE ATTACHMENT_ERROR
SET FILE_ID = V_FILE_ID
WHERE t1.CLAIM_TCN_ID=I_ATCHMNT_ERR.CLAIM_TCN_ID;
END LOOP;
END LOOP;
CLOSE cv_atchtab;
END;
END ATCHMNT_ERR_FILEID;
/
SHOW ERRORS
Procedure ATCHMNT_ERR_FILEID compiled
Errors: check compiler log Errors for PROCEDURE
RDBSTAGE.ATCHMNT_ERR_FILEID:
LINE/COL ERROR
11/40 PLS-00497: cannot mix between single row and multi-row (BULK) in INTO list
14/5 PL/SQL: Statement ignored
14/31 PLS-00302: component 'COUNT' must be declared
PLS-00497: cannot mix between single row and multi-row (BULK) in INTO list
BULK COLLECT INTO is the syntax for populating a PL/SQL collection from a query. But your code is populating a scalar single row variable.
14/31 PLS-00302: component 'COUNT' must be declared
I_ATCHMNT_ERR.COUNT is invalid because count() only applies to collections I_ATCHMNT_ERR is scalar.
To fix this you need to define and use collection types. Something like this:
CREATE OR REPLACE PROCEDURE ATCHMNT_ERR_FILEID
IS
CURSOR cv_atchtab IS
SELECT * FROM ATTACHMENT_ERROR;
type ATCHMNT_ERR_nt is table of cv_atchtab%ROWTYPE;
I_ATCHMNT_ERR ATCHMNT_ERR_nt;
V_FILE_ID VARCHAR2(40);
BEGIN
OPEN cv_atchtab;
LOOP
FETCH cv_atchtab BULK COLLECT INTO I_ATCHMNT_ERR; -- collection type
EXIT WHEN I_ATCHMNT_ERR.COUNT = 0; -- changed this
FOR i IN 1..I_ATCHMNT_ERR.COUNT
LOOP
SELECT FILE_ID
INTO V_FILE_ID -- scalar type
FROM ATTACHMENT_CLAIM t1
WHERE t1.CLAIM_TCN_ID = I_ATCHMNT_ERR(i).CLAIM_TCN_ID;
UPDATE ATTACHMENT_ERROR t2 -- changed this
SET FILE_ID = V_FILE_ID
WHERE t2.CLAIM_TCN_ID = I_ATCHMNT_ERR(i).CLAIM_TCN_ID; -- changed this
END LOOP;
END LOOP;
CLOSE cv_atchtab;
END ATCHMNT_ERR_FILEID;
Here is a db<>fiddle demo of the above working against my guess of the data model.
The Oracle documentation is comprehensive, online and free. The PL/SQL Guide has a whole chapter on Collections and Records which I suggest you read. Find it here.
As an aside, nested loops with single row statements like this are usually a red flag in PL/SQL. They are pretty inefficient and slow. SQL is a set-based language, and we should always try to solve problems using SQL whenever possible, and ideally in one set-based statement. If this code is intended for production (rather than being a homework assignment) you should definitely consider re-writing it in a more performative fashion.

Reading Oracle boolean filed dbms_utility.is_cluster_database in statement

I'd like to read boolean field from Oracle Database (11g), but I'm unable to do so, probably due to Oracle not fully supporting boolean data format.
Field I'm interessted in is dbms_utility.is_cluster_database.
Working statement:
set serveroutput on;
BEGIN
IF dbms_utility.is_cluster_database THEN
dbms_output.put_line('true');
ELSE
dbms_output.put_line('false');
END IF;
END;
This statement is working fine, however I need it as SQL query, like SELECT 'someValue' from dual;
I've tried:
SELECT dbms_utility.is_cluster_database FROM DUAL; fails with message "ORA-06553: PLS-382: expression is of wrong type"
SELECT CAST(dbms_utility.is_cluster_database AS INT) FROM DUAL; fails with same message
SELECT sys.diutil.bool_to_int(dbms_utility.is_cluster_database) from DUAL; fails with same message
SELECT CASE WHEN (dbms_utility.is_cluster_database) THEN 1 ELSE 0 END AS MY_BOOLEAN_COLUMN FROM DUAL; fails with message "SQL Error: ORA-00920: invalid relational operator"
I'm out of ideas how it can be fixed.
Problem is definitely not related to database access rights (since "dbms_output" solution works). Also, other fields from dbms_utility can be read using simple SELECT dbms_utility.<something> from dual;, eg. SELECT dbms_utility.get_endianness FROM DUAL;
i guess it's not possible to read boolean values in SQL, but you can write a wrapper function which would analyze that boolean and return varchar2/int/number.
alternatively, in your particular case you can do:
select value from v$parameter where name='cluster_database';
As suggest #MaxU you can create wrapper function.
create or replace function is_cluster_database return number is
begin
return sys.diutil.bool_to_int(dbms_utility.is_cluster_database);
end is_cluster_database;
And then used it like this
select is_cluster_database from dual;
Of course, result be number type.
is_cluster_database
0

PL/SQL - How to use an array in an IN Clause

I'm trying to use an array of input values to my procedure in an IN Clause as part of the where clause of a cursor. I know that this has been asked before, but I haven't seen how to make my syntax compile correctly.
In the package specification, the type is
TYPE t_brth_dt IS TABLE OF sourceTable.stdt_brth_dt%TYPE INDEX BY PLS_INTEGER;
sourceTable.std_brth_dt is a date column in the table.
Simplified version of my cursor is in the package body is -
cursor DataCursor_Sort( p_brth_dt in t_brth_dt) is
SELECT *
FROM sourceTable
WHERE a.brth_dt IN (select column_value
from table(p_brth_dt))
When I try to compile this, I'm getting the following errors.
[1]:(Error): PLS-00382: expression is of wrong type
[2]:(Error): PL/SQL: ORA-22905: cannot access rows from a non-nested table item
I know this looks similar to other questions, but I don't understand what the syntax error is.
In order to use collection defined as a nested table or an associative array in the from clause of a query you either should, as #Alex Poole correctly pointed out, create a schema level (SQL) type or use one, that is available to you trough ODCIConst package - odcidatelist as you intend to use a list of dates. For example, your cursor definition might look like this:
cursor DataCursor_Sort(p_brth_dt in sys.odcidatelist) is
select *
from sourceTable
where a.brth_dt IN (select column_value
from table(p_brth_dt))
OR
cursor DataCursor_Sort(p_brth_dt in sys.odcidatelist) is
select s.*
from sourceTable s
join table(p_brth_dt) t
on (s.brth_dt = t.column_value)
Note: You should take into consideration the time part of a date when performing a date comparison. If you want to compare date part only it probably would be useful to get rid of time part by using trunc() function.
It is possible to use a PL/SQL-defined nested table type (as opposed to a SQL-defined nested table type) indirectly in an IN clause of a SELECT statement in a PL/SQL package. You must use a PIPELINED function as an intermediary. It felt kind of clever to write, but I don't believe in its fundamental usefulness.
CREATE OR REPLACE PACKAGE so18989249 IS
TYPE date_plsql_nested_table_type IS TABLE OF DATE;
dates date_plsql_nested_table_type;
FUNCTION dates_pipelined RETURN date_plsql_nested_table_type PIPELINED;
PROCEDURE use_plsql_nested_table_type;
END so18989249;
/
CREATE OR REPLACE PACKAGE BODY so18989249 IS
FUNCTION dates_pipelined RETURN date_plsql_nested_table_type
PIPELINED IS
BEGIN
IF (dates.count > 0)
THEN
FOR i IN dates.first .. dates.last
LOOP
IF (dates.exists(i))
THEN
PIPE ROW(dates(i));
END IF;
END LOOP;
END IF;
END;
PROCEDURE use_plsql_nested_table_type IS
BEGIN
dates := NEW date_plsql_nested_table_type();
-- tweak these values as you see fit to produce the dbms_output results you want
dates.extend(5);
dates(1) := DATE '2013-12-25';
dates(2) := DATE '2013-01-01';
dates(3) := DATE '2013-07-01';
dates(4) := DATE '2013-09-03';
dates(5) := DATE '2008-11-18';
FOR i IN (SELECT o.owner,
o.object_name,
o.object_type,
to_char(o.last_ddl_time, 'YYYY-MM-DD') AS last_ddl
FROM all_objects o
WHERE trunc(o.last_ddl_time) IN
(SELECT column_value FROM TABLE(dates_pipelined))
--uses pipeline function which uses pl/sql-defined nested table
)
LOOP
dbms_output.put_line('"' || i.owner || '"."' || i.object_name || '" ("' || i.object_type || ') on ' || i.last_ddl);
END LOOP;
END;
END so18989249;
/
begin so18989249.use_plsql_nested_table_type; end;
/
The type has to be created at SQL level, not in a package. An SQL query doesn't know how to use any types defined in PL/SQL. So you'd have to do:
CREATE OR REPLACE TYPE t_brth_dt IS TABLE OF date;
/
... and remove the type from your package specification. (Or give them different names, at least, and they won't be interchangeable in use). Because it's at SQL level, you also can't use sourceTable.stdt_brth_dt%TYPE in the declaration, unfortunately.

Oracle 8i dynamic SQL error on subselects in pl/sql blocks

I wrote an Oracle function (for 8i) to fetch rows affected by a DML statement, emulating the behavior of RETURNING * from PostgreSQL. A typical function call looks like:
SELECT tablename_dml('UPDATE tablename SET foo = ''bar''') FROM dual;
The function is created automatically for each table and uses Dynamic SQL to execute a query passed as an argument. Moreover, a statement that executes the query dynamically is also wrapped in a BEGIN .. END block:
EXECUTE IMMEDIATE 'BEGIN'||query||' RETURNING col1, col2 BULK COLLECT INTO :1, :2;END;' USING OUT col1_t, col2_t;
The reason behind this perculiar construction is that it seems to be the only way to get values from the DML statement that affects multiple rows. Both col1_t and col2_t are declared as collections of the types corresponding to the table columns.
Finally, to the problem. When the query passed contains a subselect, execution of the function produces a syntax error. Below is a simpe example to illustrate this:
CREATE TABLE xy(id number, name varchar2(80));
CREATE OR REPLACE FUNCTION xy_fn(query VARCHAR2) RETURN NUMBER IS
PRAGMA AUTONOMOUS_TRANSACTION;
BEGIN
EXECUTE IMMEDIATE 'BEGIN '||query||'; END;';
ROLLBACK;
RETURN 5;
END;
SELECT xy_fn('update xy set id = id + (SELECT min(id) FROM xy)') FROM DUAL;
The last statement produces the following error: (the SELECT that is mentioned there is the SELECT min(id))
ORA-06550: line 1, column 32: PLS-00103: Encountered the symbol
"SELECT" when expecting one of the following: ( - + mod not null
others avg count current exists max min prior sql stddev sum
variance execute forall time timestamp interval date
This problem occurs on 8i (8.1.6), but not 10g.
If BEGIN .. END blocks are removed - the problem disappears.
If a subselect in a query is replaced with something else, i.e. a constant, the problem disappears.
Unfortunately, I'm stuck with 8i and removing BEGIN .. END is not an option (see the explanation above).
Is there a specific Oracle 8i limitation in play here? Is it possible to overcome it with dynamic SQL?
Not sure why you need to do all this work. Oracle 8i supported RETURNING INTO with bulk collection. Find out more
So you should just be able to execute this statement in non-dynamic SQL. Something like this:
UPDATE tablename
SET foo = 'bar'
returning col1, col2 bulk collect into col1_t, col2_t;
Stripped of all the irrelevancies, I think your question is simple.
This update statement runs in SQL:
update xy set id = id + (SELECT min(id) FROM xy);
And this anonymous block also runs:
begin
update xy set id = id + 100;
end;
But combining the two doesn't work:
begin
update xy set id = id + (SELECT min(id) FROM xy);
end;
Probably you have run into a limitation of older Oracle. Prior to 9i, the SQL engine and the PL/SQL SQL engine were always out of sync. So latest features supported in SQL often weren't supported in PL/SQL. It seems like you have one of those.
Since 9i Oracle have striven to keep the two engines in sync, so it is much rarer to find things which work in SQL but not in PL/SQL.
Given the nature of your task, upgrading your version of Oracle is out. So all I can suggest is that you have two procedures, one which supports the sub query syntax (by avoiding the need for such subqueries. Something like this:
CREATE OR REPLACE FUNCTION xy_sqfn
(main_query VARCHAR2
, sub_query VARCHAR2 )
RETURN NUMBER
IS
n pls_integer;
BEGIN
execute immediate sub_query into n;
EXECUTE IMMEDIATE 'BEGIN '||main_query||'; END;'
using n;
RETURN 5;
END;
call it like this
result := xy_sqfn ('update xy set id = id + :1'
, 'SELECT min(id) FROM xy');
Now this approach won't work for correlated sub-queries. So it you have any of them, you'll need to do something different again.
Incidentally, using the AUTONOMOUS TRANSACTION pragma to fudge executing DML in a SELECT statement is quite horrible. Why not just run the functions in PL/SQL? Or use procedures? I suppose you'll say it doesn't matter because you're just writing some shonky code to support a data migration. Which is fair enough, but for the benefit of future seekers: don't do this! It's very bad practice!

Resources