Oracle and optional stored procedure parameter in where clause - oracle

I have a stored procedure with an optional parameter like this
create or replace
PROCEDURE "my_stored_procedure" (param1 int, param2 int default null)
IS
BEGIN
[select some fields from some tables]
...
I need a where clause with a if on the default parameter (if it is set), but I don't even know if it's possible...
Something like
where param1=123 and (if param2 is not null then some_value != param2)
The select clause it's pretty long and complex, so I'll prefer to have a "flexible" WHERE rather than a structure like
if param2 is not null then
[long and complex select] where param1=123 and some_value != param2
else
[long and complex select] where param1=123
Is this possible?

In this case you can do:
where param1=123 and (param2 is null or param2 != some_value)
If param2 is not null - then it's true only if param2 != some_value - as expected
If param2 is null - then it returns true no matter what some_value is

Initially we used this syntax:
WHERE (pParameter = COLUMN OR pParameter IS NULL)
until a database tuning specialist found out that this causes Oracle to perform full table scans and ignore the indexes, because of using OR.
Now we use
WHERE decode(pParameter,
null, 1, --if parameter is null, then return 1
COLUMN, 1, -- if parameter matches the value in the column, then return 1
0) --else return 0
= 1
this construct allows an easy and readable way to add special handling for special values
WHERE decode(pParameter,
null, 1, --if parameter is null, then allow the row
'abc', 1, --if parameter is 'abc', then always allow the row
'xyz', 1, --if parameter is 'xyz', then always reject the row
COLUMN, 1, -- if parameter matches the value in the column, then allow
0) --else return 0 to reject the row
= 1
Alternatively, you can rewrite this with COALESCE or with CASE:
WHERE COALESCE(pParameter, COLUMN, 'ok') = COALESCE(COLUMN, 'ok')
or, sample using CASE:
THEN (
case
when pParameteris null then 1
when pParameter= COLUMN then 1
else 0
end
) = 1

Related

How to test if a binding variable is null in PL/SQL? [duplicate]

I want to check if a variable is null. If it is null, then I want to set a value to that variable:
//data type of var is number
if Var = null then
var :=5;
endif
But I am geting error in it. How can I check if a variable is null?
I am using oracle data type
if var is NULL then
var :=5;
end if;
Use:
IF Var IS NULL THEN
var := 5;
END IF;
Oracle 9i+:
var = COALESCE(Var, 5)
Other alternatives:
var = NVL(var, 5)
Reference:
COALESCE
NVL
NVL2
In PL/SQL you can't use operators such as '=' or '<>' to test for NULL because all comparisons to NULL return NULL. To compare something against NULL you need to use the special operators IS NULL or IS NOT NULL which are there for precisely this purpose. Thus, instead of writing
IF var = NULL THEN...
you should write
IF VAR IS NULL THEN...
In the case you've given you also have the option of using the NVL built-in function. NVL takes two arguments, the first being a variable and the second being a value (constant or computed). NVL looks at its first argument and, if it finds that the first argument is NULL, returns the second argument. If the first argument to NVL is not NULL, the first argument is returned. So you could rewrite
IF var IS NULL THEN
var := 5;
END IF;
as
var := NVL(var, 5);
I hope this helps.
EDIT
And because it's nearly ten years since I wrote this answer, let's celebrate by expanding it just a bit.
The COALESCE function is the ANSI equivalent of Oracle's NVL. It differs from NVL in a couple of IMO good ways:
It takes any number of arguments, and returns the first one which is not NULL. If all the arguments passed to COALESCE are NULL, it returns NULL.
In contrast to NVL, COALESCE only evaluates arguments if it must, while NVL evaluates both of its arguments and then determines if the first one is NULL, etc. So COALESCE can be more efficient, because it doesn't spend time evaluating things which won't be used (and which can potentially cause unwanted side effects), but it also means that COALESCE is not a 100% straightforward drop-in replacement for NVL.
Use IS NULL
reference page
There's also the NVL function
http://www.techonthenet.com/oracle/functions/nvl.php
use if var is null
Always remember to be careful with nulls in pl/sql conditional clauses as null is never greater, smaller, equal or unequal to anything. Best way to avoid them is to use nvl.
For example
declare
i integer;
begin
if i <> 1 then
i:=1;
foobar();
end if;
end;
/
Never goes inside the if clause.
These would work.
if 1<>nvl(i,1) then
if i<> 1 or i is null then
Another way:
var := coalesce (var, 5);
COALESCE is the ANSI equivalent (more or less) of Oracle's NVL function.

Oracle: is it possible to trim a string and count the number of occurances, and insert to a new table?

My source table looks like this:
id|value|count
Value is a String of values separated by semicolons(;). For example it may look like this
A;B;C;D;
Some may not have values at a certain position, like this
A;;;D;
First, I've selectively moved records to a new table(targettable) based on positions with values using regexp. I achieved this by using [^;]+; for having some value between the semicolons, and [^;]*; for those positions I don't care about. For example, if I wanted the 1st and 4th place to have values, I could incorporate regexp with insert into like this
insert into
targettable tt (id, value, count)
SELECT some_seq.nextval,value, count
FROM source table
WHERE
regexp_like(value, '^[^;]+;[^;]*;[^;]*;[^;]+;')
so now my new table has a list of records that have values at the 1st and 4th position. It may look like this
1|A;B;C;D;|2
2|B;;;E;|1
3|A;D;;D|3
Next there are 2 things I want to do. 1. get rid of values other than 1st and 4th. 2.combine identical values and add up their count. For example, record 1 and 3 are the same, so I want to trim so they become A;D;, and then add their count, so 2+3=5. Now my new table looks like this
1|A;D;|5
2|B;E;|1
As long as I can somehow get to the final table from source table, I don't care about the steps. The intermediate table is not required, but it may help me achieve the final result. I'm not sure if I can go any further with Orcale though. If not, I'll have to move and process the records with Java. Bear in mind I have millions of records, so I would consider the Oracle method if it is possible.
You should be able to skip the intermediate table; just extract the 1st and 4th elements, using the regexp_substr() function, while checking that those are not null:
select regexp_substr(value, '(.*?)(;|$)', 1, 1, null, 1) -- first position
|| ';' || regexp_substr(value, '(.*?)(;|$)', 1, 4, null, 1) -- fourth position
|| ';' as value, -- if you want trailing semicolon
count
from source
where regexp_substr(value, '(.*?)(;|$)', 1, 1, null, 1) is not null
and regexp_substr(value, '(.*?)(;|$)', 1, 4, null, 1) is not null;
VALUE COUNT
------------------ ----------
A;D; 2
B;E; 1
A;D; 3
and then aggregate those results:
select value, sum(count) as count
from (
select regexp_substr(value, '(.*?)(;|$)', 1, 1, null, 1) -- first position
|| ';' || regexp_substr(value, '(.*?)(;|$)', 1, 4, null, 1) -- fourth position
|| ';' as value, -- if you want trailing semicolon
count
from source
where regexp_substr(value, '(.*?)(;|$)', 1, 1, null, 1) is not null
and regexp_substr(value, '(.*?)(;|$)', 1, 4, null, 1) is not null
)
group by value;
VALUE COUNT
------------------ ----------
A;D; 5
B;E; 1
Then for your insert you can use that query, either with an auto-increment ID (12c+), or setting an ID from a sequence via a trigger, or possibly wrapped in another level of subquery to get the value explicitly:
insert into target (id, value, count)
select some_seq.nextval, value, count
from (
select value, sum(count) as count
from (
select regexp_substr(value, '(.*?)(;|$)', 1, 1, null, 1) -- first position
|| ';' || regexp_substr(value, '(.*?)(;|$)', 1, 4, null, 1) -- fourth position
|| ';' as value, -- if you want trailing semicolon
count
from source
where regexp_substr(value, '(.*?)(;|$)', 1, 1, null, 1) is not null
and regexp_substr(value, '(.*?)(;|$)', 1, 4, null, 1) is not null
)
group by value
);
If you're creating a new sequence to do that, so they start from 1, you can use rownum or row_number() instead.
Incidentally, using a keyword or a function name like count as a column name is confusing (sum(count) !?); those might not be your real names though.
I would use regexp_replace to remove the 2nd and 3rd parts of the string, combined with an aggregate query to get the total count, like :
SELECT
regexp_replace(value, '^[^;]+;([^;]*;[^;]*;)[^;]+;', ''),
SUM(count)
FROM source table
WHERE
regexp_like(value, '^[^;]+;[^;]*;[^;]*;[^;]+;')
GROUP BY
regexp_replace(value, '^[^;]+;([^;]*;[^;]*;)[^;]+;', '')

Concatenation respecting conventional null handling

The conventional handling of null in SQL, and the language specification, is that if any part of an expression is null, the whole expression is null.
However in Oracle, text concatenation converts null to a <blank>, eg:
select concat(concat('foo', null), 'bar') from dual; --> returns "foobar"
select 'foo' || null || 'bar' from dual; --> returns "foobar"
I want the conventional behaviour, where the result would be null if any term is null.
Is there a method or function provided by Oracle that concatenates text using a single expression, without recoding any term, such that if any term is null, the result is null?
Notes:
I don't want to repeat any terms, which would be required by a case etc, because the terms are very long and complex, and besides it's bad practice to repeat code
I can’t define any functions. I must use just a single SQL query using nothing but standard syntax and plain Oracle provided functions/operators
Side-stepping the no-repeat requirement by using a subquery or a CTE isn’t answering the question, it’s avoiding it: I want to know if Oracle can concatenate Strings using a single expression in the same way every other database I know does
Yes, concat and || do not work in a standard (as in SQL92 spec) way. I guess, this is because there is no distinction between an empty string and null value in Oracle DB.
You can create a user defined function and use it in SQL.
CREATE OR REPLACE FUNCTION standard_concat (
a VARCHAR2,
b VARCHAR2
) RETURN VARCHAR2
AS
BEGIN
IF
a IS NULL OR b IS NULL
THEN
RETURN NULL;
ELSE
RETURN a || b;
END IF;
END;
/
Using this function gives you these results:
select standard_concat(standard_concat('foo', ''), 'bar') from dual; returns null
select standard_concat(standard_concat('foo', null), 'bar') from dual; returns null
select standard_concat(standard_concat('foo', 'foo'), 'bar') from dual; returns "foofoobar"
As you can see, empty string will be treated as null since this is the way Oracle DB treats Strings. There is no way to make a distinction here.
If this function is only needed for one query you can inline your function definition into the SQL itself as in:
WITH
FUNCTION standard_concat (
a VARCHAR2,
b VARCHAR2
) RETURN VARCHAR2
AS
BEGIN
IF
a IS NULL OR b IS NULL
THEN
RETURN NULL;
ELSE
RETURN a || b;
END IF;
END;
SELECT
standard_concat(standard_concat('foo',''),'bar')
FROM
dual;
I hope it helps.
I'm not avere of a SQL function and this NULL behavior on VARCHAR2 is posible conventional, but for sure not usual expected. The reason is that Oracle doesn't distinct betwen NULL and a string with length zero (''). For string concatenation the NULLs are considered as empty strings.
Anyway you may use subqueries to avoid repeating the expressions:
with t1 as (
select 'foo' col1, null col2, 'bar' col3 from dual union all
select null col1, null col2, null col3 from dual union all
select 'foo' col1, 'baz' col2, 'bar' col3 from dual
)
select col1,col2,col3,
case when col1 is not NULL and col2 is not NULL and col3 is not NULL then
col1||col2||col3 end as concat
from t1;
returns
COL COL COL CONCAT
--- --- --- ---------
foo bar
foo baz bar foobazbar
Alternatively you may write the predicate in teh CASE statement a bit more compact using the Group Comparison Conditions
select
case when 0 < ALL(length(col1),length(col2),length(col3)) then
col1||col2||col3 end as concat
from t1;
Unfortunately the Group Comparison Conditions doesn't allow a dierct IS NULL test, so a workaround with length must be used.
The third option is a bit ugly (as requires some special string that doesn't exists in regular strings, but probably meets best your requriements.
Simple NVL all strings before concatenation and than exclude those mappend by NVL
with t2 as
(select nvl(col1,'#§$%')||nvl(col2,'#§$%')||nvl(col3,'#§$%') as concat
from t1)
select
case when concat not like '%#§$\%%' escape'\' then concat end as concat
from t2;
There ins't a single expression that will do what you want unfortunately - there isn't a stanrd-compliant equivalent of concat() or the concatenation operator.
I'm sure this doesn't meet the criteria either, but as requested (if slightly mis-advertised), an 'XNL' (well, XMLDB anyway) workaround/hack/abomination:
set null "(null)"
select xmlquery(
'if (min((string-length($x), string-length($y), string-length($z))) = 0)
then "" else concat($x, $y, $z)'
passing 'foo' as "x", null as "y", 'bar' as "z"
returning content)
from dual;
XMLQUERY('IF(MIN((STRING-LENGTH($X),STRING-LENGTH($Y),STRING-LENGTH($Z)))=0)THEN
--------------------------------------------------------------------------------
(null)
With no null expressions:
select xmlquery(
'if (min((string-length($x), string-length($y), string-length($z))) = 0)
then "" else concat($x, $y, $z)'
passing 'foo' as "x", 'bar' as "y", 'baz' as "z"
returning content)
from dual;
XMLQUERY('IF(MIN((STRING-LENGTH($X),STRING-LENGTH($Y),STRING-LENGTH($Z)))=0)THEN
--------------------------------------------------------------------------------
foobarbaz
As well as being a hack, of course, it suffers from the problem of not being general as the number of terms is fixed. I haven't come up with a way to pass any number of terms in.

Not regexp_like, find null values

Doing data checking of input data in staging tables prior to loading into the main system tables. Because I want to use the same field checking in Apex I have implemented the checks as regular expressions using 'not regexp_like' to return any field that does not match its pattern. However, for a mandatory 10 character field I expected a pattern such as:
'^.{1,10}$'
to return any field that was null with the fields that were too long, it does not, null fields are not returned.
Is there a regexp pattern that will do this?
Example:
select col
from (select null col from dual
union all
select 'A string' from dual)
where not regexp_like(col, '^.{1,10}$')
Removing the 'not' returns 1 row as expected...
The point here is to force the pattern to find the null, not to use regexp_like to find the good values and then exclude them, or to use an NVL on the field. This checking is already expensive enough!
I am assuming, that like most Oracle code involving null what is happening here is that regexp_like sees null and therefore returns null?
Does Java script and Java behave in the same way?
Thanks
Bob
Oracle has 3 states in the result of a boolean expression: TRUE, FALSE or NULL (unknown).
WHERE REGEXP_LIKE( NULL, '.' )
Will return NULL and not TRUE or FALSE.
Negating the expression to:
WHERE NOT REGEXP_LIKE( NULL, '.' )
Will also return NULL and not TRUE or FALSE and, since it is not TRUE then the row will never be returned.
Instead you need to explicitly check for a NULL value:
WHERE NOT REGEXP_LIKE( col, '^.{1,10}$' )
OR col IS NULL
or, more simply:
WHERE LENGTH( col ) > 10
OR col IS NULL
If you only need to get the strings with zero or more than 10 characters, you can simply use:
length(col) > 10 or col is null
About NULL, the only check you can rely on is is [not] null.
For example:
select length(null) from dual
gives NULL, not 0.

PL/SQL - Split string into an associative array

In plsql is there a way to split a string into an associative array?
Sample string: 'test1:First string, test2: Second string, test3: Third string'
INTO
TYPE as_array IS TABLE OF VARCHAR2(50) INDEX BY VARCHAR2(50);
a_array as_array;
dbms_output.put_line(a_array('test1')); // Output 'First string'
dbms_output.put_line(a_array('test2')); // Output 'Second string'
dbms_output.put_line(a_array('test3')); // Output 'Third string'
The format of the string does not matter for my purposes. It could be 'test1-First string; test2-Second string; test3-Third string'. I could do this with a very large function manually splitting by commas first and then splitting each of those but I'm wondering if there is something built in to the language.
Like I said, I am not looking to do it through a large function (especially using substr and making it look messy). I am looking for something that does my task simpler.
There is no built in function for such a requirement.
But you can easily build a query like below to parse these strings:
SELECT y.*
FROM (
select trim(regexp_substr(str,'[^,]+', 1, level)) as str1
from (
SELECT 'test1:First string, test2: Second string, test3: Third string' as Str
FROM dual
)
connect by regexp_substr(str, '[^,]+', 1, level) is not null
) x
CROSS APPLY(
select trim(regexp_substr(str1,'[^:]+', 1, 1)) as key,
trim(regexp_substr(str1,'[^:]+', 1, 2)) as value
from dual
) y
KEY VALUE
------ --------------
test1 First string
test2 Second string
test3 Third string
Then you may use this query in your function and pass it's result to the array.
I leave this exercise for you, I believe you can manage it (tip: use Oracle's bulk collect feature)
This method handles NULL list elements if you need to still show that element 2 is NULL for example. Note the second element is NULL:
-- Original data with multiple delimiters and a NULL element for testing.
with orig_data(str) as (
select 'test1:First string,, test3: Third string' from dual
),
--Split on first delimiter (comma)
Parsed_data(rec) as (
select regexp_substr(str, '(.*?)(,|$)', 1, LEVEL, NULL, 1)
from orig_data
where str is not null
CONNECT BY LEVEL <= REGEXP_COUNT(str, ',') + 1
)
-- For testing-shows records based on 1st level delimiter
--select rec from parsed_data;
-- Split the record into columns
select trim(regexp_replace(rec, '^(.*):.*', '\1')) key,
trim(regexp_replace(rec, '^.*:(.*)', '\1')) value
from Parsed_data;
Watch out for the regex form of [^,]+ for parsing delimited strings, it fails on NULL elements. More Information

Resources