Can I create a macro which is only testing a condition? - oracle

the following query doesn't work if doesn't comment WHERE fc().
WITH
FUNCTION ft
RETURN VARCHAR2 SQL_MACRO(table)
IS
BEGIN
RETURN q'{
ta
}';
END;
FUNCTION fc
RETURN VARCHAR2 SQL_MACRO(scalar)
IS
BEGIN
RETURN q'{
1=1
}';
END;
ta(v) as (select 1 from dual)
SELECT *
FROM ft()
WHERE fc()
ORA-00920: invalid relational operator
Have I done some syntax error or isn't it possible to make macro returning a condition?
code

SQL Macro is not a way to inject arbitrary part of the code into the SQL statement, but an expression or a table. It is described in the documentation:
SQL macros can either be scalar expressions, typically used in SELECT lists, WHERE, GROUPBY and HAVING clauses, to encapsulate calculations and business logic or can be table expressions, typically used in a FROM clause.
So macros may be used where expressions may be used, and expressions require some operator in the where clause.

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!!

select inside a case statement not works

I wanted to write a select statement inside CASE THEN statement in PLSQL but it throws error. Please advise if I could write select statement inside THEN Statement.
An example similar to my requirement looks like below
SET SERVEROUTPUT ON
DECLARE
LV_VAR VARCHAR2(4000):=NULL;
BEGIN
LV_VAR:= CASE
WHEN 1=1 THEN
(SELECT 1 FROM DUAL)
ELSE
0
END;
DBMS_OUTPUT.PUT_LINE(LV_VAR);
END;
While executing , it throws error as below
ORA-06550: line 6, column 26:
PLS-00103: Encountered the symbol "SELECT" when expecting one of the following:
( - + case mod new not null <an identifier>
<a double-quoted delimited-identifier> <a bind variable>
You can't use scalar subqueries directly in PL/SQL code, like you have shown. (Of course, you knew that already.) You must select the value INTO a variable, and then use it.
ALSO: You have no case statements in your code. You have a case expression. It just won't work quite the way you wrote it.
One alternative is to use a case expression within the SQL SELECT ... INTO statement, as David Goldman has shown in his Answer.
However, if the whole point of your exercise was to practice case expressions as used in PL/SQL, not inside a SQL statement, you would need to SELECT ... INTO a variable you declare in your code, and then use that variable in the case expression. Something like this:
DECLARE
LV_VAR VARCHAR2(4000);
BEGIN
SELECT 1 INTO LV_VAR FROM DUAL;
LV_VAR:= CASE
WHEN 1=1 THEN
LV_VAR
ELSE
0
END;
DBMS_OUTPUT.PUT_LINE(LV_VAR);
END;
As you can see, I did something that is done frequently in procedural language code: Instead of declaring and using TWO variables, I only declared one. I populated it with the result of the SELECT ... INTO query. Then I assigned to it again in the case expression: in one case I assign it to itself and in the other I assign to it the value 0.
In PL/SQL, you'll need to do a SELECT ... INTO. So, to re-write your code:
SET SERVEROUTPUT ON
DECLARE
LV_VAR VARCHAR2(4000):=NULL;
BEGIN
SELECT CASE
WHEN 1=1 then 1
else 0
end
INTO LV_VAR
FROM DUAL;
DBMS_OUTPUT.PUT_LINE(LV_VAR);
END;
You are trying to combine PL/SQL Control Statement CASE with SQL CASE Expressions.
While it is possible to use a sub-query in THEN return expression of SQL CASE,
select CASE WHEN 1=1 THEN ( select 1 FROM DUAL )
ELSE 0 END FROM DUAL;
The same is not true while you use it in PL/SQL although the syntax is same.
Read the Oracle documentation for SQL , CASE1 and PL/SQL : CASE2

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.

IF-ELSE issue in Select Statement in 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.

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