Merging data with or / nvl in where clause, what benefit? - oracle

In what cases using or / nvl combo in merge update is useful and what purpose does it serve ? (If it is good at all). Typical structure bellow.
MERGE INTO data_table p
USING (SELECT pk,
col1,
col2,
col3
FROM incoming_data_table) pp
ON (p.pk = pp.pk)
WHEN MATCHED THEN
UPDATE
SET p.col1 = pp.col1,
p.col2 = pp.col2,
p.col3 = pp.col3
WHERE nvl(p.col1, '0') != nvl(pp.col1, '0') -- Line in question
OR nvl(p.col2, '0') != nvl(pp.col2, '0') -- Line in question
OR nvl(p.col3, '0') != nvl(pp.col3, '0') -- Line in question
WHEN NOT MATCHED THEN
INSERT
(p.pk,
p.col1,
p.col2,
p.col3)
VALUES
(pp.pk,
pp.col1,
pp.col2,
pp.col3);

the OR's make sure that the update is done as soon as one of the three conditions is met. With AND's all three would have to be met before the update taking place. So as soon as at least one of the col's differ the update shall take place.
then NVL make sure that null is interpreted as any other value. Because without the NVL, neither 1=null is true nor is 1!=null true. They both return false. But as the writer of the query wants an update to take place e.g. when p.col1 contains 123 and p.col2 contains null, the NVL is needed. Here are some cases for you:
select 'true' from dual where 1!=null; -- false
select 'true' from dual where 1=null; -- false
select 'true' from dual where 1=nvl(null,0); -- false
select 'true' from dual where 1!=nvl(null,0); -- true
NULL is very special when it comes to comparisons. Not even this is true:
select 'true' from dual where null=null; -- false
Have a look at table 'Nulls in Conditions' on this page for more examples: https://docs.oracle.com/cd/B19306_01/server.102/b14200/sql_elements005.htm

Related

ORACLE apex - looping through checkbox items using PL/SQL

I have a checkbox on my page P3_Checkbox1 that is populated from the database. How can I loop through all the checked values of my checkbox in PL/SQL code?
I assume that APEX_APPLICATION.G_F01 is used only by sql generated checkboxes and I cannot use it for regular checbox as it would not populate G_Fxx array.
I think I understand now what you're saying.
For example, suppose that the P3_CHECKBOX1 checkbox item allows 3 values (display/return):
Absent: -1
Unknown: 0
Here: 1
I created a button (which will just SUBMIT the page) and two text items which will display checked values: P3_CHECKED_VALUES and P3_CHECKED_VALUES_2.
Then I created a process which fires when the button is pressed. The process looks like this (read the comments as well, please):
begin
-- This is trivial; checked (selected) values are separated by a colon sign,
-- just like in a Shuttle item
:P3_CHECKED_VALUES := :P3_CHECKBOX1;
-- This is what you might be looking for; it uses the APEX_STRING.SPLIT function
-- which splits selected values (the result is ROWS, not a column), and these
-- values can then be used in a join or anywhere else, as if it was result of a
-- subquery. The TABLE function is used as well.
-- LISTAGG is used just to return a single value so that I wouldn't have to worry
-- about TOO-MANY-ROWS error.
with
description (code, descr) as
(select -1, 'Absent' from dual union all
select 0 , 'Unknown' from dual union all
select 1 , 'Here' from dual),
desc_join as
(select d.descr
from description d join (select * from table(apex_string.split(:P3_CHECKED_VALUES, ':'))) s
on d.code = s.column_value
)
select listagg(j.descr, ' / ') within group (order by null)
into :P3_CHECKED_VALUES_2
from desc_join j;
end;
Suppose that the Absent and Unknown values have been checked. The result of that PL/SQL process is:
P3_CHECKED_VALUES = -1:0
P3_CHECKED_VALUES_2 = Absent / Unknown
You can rewrite it as you want; this is just one example.
Keywords:
APEX_STRING.SPLIT
TABLE function
I hope it'll help.
[EDIT: loop through selected values]
Looping through those values isn't difficult; you'd do it as follows (see the cursor FOR loop):
declare
l_dummy number;
begin
for cur_r in (select * From table(apex_string.split(:P3_CHECKED_VALUES, ':')))
loop
select max(1)
into l_dummy
from some_table where some_column = cur_r.column_value;
if l_dummy is null then
-- checked value does not exist
insert into some_table (some_column, ...) valued (cur_r.column_value, ...);
else
-- checked value exists
delete from some_table where ...
end if;
end loop;
end;
However, I'm not sure what you meant by saying that a "particular combination is in the database". Does it mean that you are storing colon-separated values into a column in that table? If so, the above code won't work either because you'd compare, for example
-1 to 0:-1 and then
0 to 0:-1
which won't be true (except in the simplest cases, when checked and stored values have only one value).
Although Apex' "multiple choices" look nice, they can become a nightmare when you have to actually do something with them (like in your case).
Maybe you should first sort checkbox values, sort database values, and then compare those two strings.
It means that LISTAGG might once again become handy, such as
listagg(j.descr, ' / ') within group (order by j.descr)
Database values could be sorted this way:
SQL> with test (col) as
2 (select '1:0' from dual union all
3 select '1:-1:0' from dual
4 ),
5 inter as
6 (select col,
7 regexp_substr(col, '[^:]+', 1, column_value) token
8 from test,
9 table(cast(multiset(select level from dual
10 connect by level <= regexp_count(col, ':') + 1
11 ) as sys.odcinumberlist))
12 )
13 select
14 col source_value,
15 listagg(token, ':') within group (order by token) sorted_value
16 from inter
17 group by col;
SOURCE SORTED_VALUE
------ --------------------
1:-1:0 -1:0:1
1:0 0:1
SQL>
Once you have them both sorted, you can compare them and either INSERT or DELETE a row.

Using Cases with update in oracle query

How to use CASE of WHEN and THEN in oracle UPDATE query, I have tried as following but no success,
when user status_id in 1,3,5 update ID to 3 (yes even if already 3 is there), and when status is 2 update to 4.
UPDATE USER
CASE WHEN USER_STATUS_ID IN ('1','3','5') THEN SET USER_STATUS_ID='3' WHEN USER_STATUS_ID IN ('2') THEN SET USER_STATUS_ID='4',
BLOCKING_REASON_CODE=(SELECT REASON_CODE_ID FROM REASON_CODES WHERE REASON_CODE='AGE_LIMIT')
WHERE CUSTOMER_ID IN
(SELECT CUSTOMER_ID FROM CUSTOMER_AGE_TABLE)
WHERE BATCH_ID=101
Assuming that your status is a varchar, even if it always seems to contain numbers, you may try:
update yourTable
set status_id = case
when status_id in ('1','3','5') then '3'
when status_id = '2' then '4'
else status_id -- no changes if status is not in 1, 2, 3, 4
end
where ...
If it is a number, you can remove the ''.
The else part is needed if you want to avoid null; for example:
select case when 1=2 then 'a' end from dual
gives null.
You may avoid it, by adding some more conditions in the WHERE clause, but this mainly depends on whether you always need to update that column, even if with no changes, or not.

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.

Passing a parameter to a WITH clause query in Oracle

I'm wondering if it's possible to pass one or more parameters to a WITH clause query; in a very simple way, doing something like this (taht, obviously, is not working!):
with qq(a) as (
select a+1 as increment
from dual
)
select qq.increment
from qq(10); -- should get 11
Of course, the use I'm going to do is much more complicated, since the with clause should be in a subquery, and the parameter I'd pass are values taken from the main query....details upon request... ;-)
Thanks for any hint
OK.....here's the whole deal:
select appu.* from
(<quite a complex query here>) appu
where not exists
(select 1
from dual
where appu.ORA_APP IN
(select slot from
(select distinct slots.inizio,slots.fine from
(
with
params as (select 1900 fine from dual)
--params as (select app.ora_fine_attivita fine
-- where app.cod_agenda = appu.AGE
-- and app.ora_fine_attivita = appu.fine_fascia
--and app.data_appuntamento = appu.dataapp
--)
,
Intervals (inizio, EDM) as
( select 1700, 20 from dual
union all
select inizio+EDM, EDM from Intervals join params on
(inizio <= fine)
)
select * from Intervals join params on (inizio <= fine)
) slots
) slots
where slots.slot <= slots.fine
)
order by 1,2,3;
Without going in too deep details, the where condition should remove those records where 'appu.ORA_APP' match one of the records that are supposed to be created in the (outer) 'slots' table.
The constants used in the example are good for a subset of records (a single 'appu.AGE' value), that's why I should parametrize it, in order to use the commented 'params' table (to be replicated, then, in the 'Intervals' table.
I know thats not simple to analyze from scratch, but I tried to make it as clear as possible; feel free to ask for a numeric example if needed....
Thanks

Sequence and case in a select in oracle

I'm trying to do this query (in oracle) but I have some problems:
SELECT CASE
WHEN deptno = '10' THEN scott.seq.nextval
|| 'next10'
WHEN deptno = '20' THEN scott.seqnextval
|| 'next20'
WHEN deptno = '30' THEN scott.seq.currval
|| 'curr'
END col_1
FROM scott.emp;
I'm getting this results:
COL_1
----------------------------------------------
191next20
192curr
193curr
194next20
195curr
196curr
197next10
198next20
199next10
200curr
201next20
202curr
203next20
204next10
205next20
206next10
207next10
And this is what I think they should be:
COL_1
----------------------------------------------
191next20
192curr
193curr
194next20
194curr
194curr
197next10
198next20
199next10
199curr
201next20
201curr
203next20
204next10
205next20
206next10
207next10
So, why i get the next value of the sequence also when I should have the current value and not only when the case selects the next value?
Yeah, this could be done with a plsql script but I can't.
Thanks you!
Nextval and currval are not functions, but are "Sequence Pseudocolumns".
"Within a single SQL statement containing a reference to NEXTVAL, Oracle increments the sequence once: For each row returned by the outer query block of a SELECT statement. Such a query block can appear in the following places. ..." (emphasis added) [Oracle Database SQL Language Reference, "How to Use Sequence Values"]
In other words, seq.nextval is not a function with a side affect, but a pseudocolumn that has a particular value per row. Once there is a single reference to seq.nextval, the value increments for every row, whether or not the value is used. The outcome OP is seeing is a peculiar to sequences, not case expressions. For example, same thing with decode:
SQL> select decode(deptno
2 , 10, seq.nextval || 'next10'
3 , 20, seq.nextval || 'next20'
4 , 30, seq.currval || 'curr30')
5 from emp;
DECODE(DEPTNO,10,SEQ.NEXTVAL||'NEXT10',20,SEQ.
----------------------------------------------
35next20
36curr30
37curr30
38next20
39curr30
40curr30
41next10
42next20
43next10
44curr30
45next20
46curr30
47next20
48next10
Interesting. Per the Oracle docs:
The statements in a WHEN clause can modify the database and call
non-deterministic functions. There is no fall-through mechanism as in
the C switch statement
Notice it doesn't say the statements in the "true" WHEN clause. So even if the when statement is false, the nextval will fire:
select
case when 1=0 then 'next ' || seq_id.nextval
when 1=1 then 'curr ' || seq_id.currval
end col1
from dual;
I must admit this is different than I expected.
EDIT:
See answer from ShannonSeverance

Resources