Left join update statement - sql

I couldn't find any easy of doing table update with value from left join.
I want to do something like this:
UPDATE tbl1 p
LEFT JOIN (
select column1 , column2 from tbl2
union
select column1,column2 from tbl3
) c on c.column2=p.column2
SET p.column1 = nvl(c.column1, 'dummy');

UPDATE p SET p.column1 = nvl(c.column1, 'dummy')
from tbl1 p
LEFT JOIN (
select column1 , column2 from tbl2
union
select column1,column2 from tbl3
) c on c.column2=p.column2

I would recommend expressing this as:
UPDATE tbl1 p
SET p.column1 = COALESCE((SELECT column1 from tbl2 t2 WHERE t2.column2 = p.column2),
(SELECT column1 from tbl3 t3 WHERE t3.column2 = p.column2),
'dummy'
);
Each subquery can make use of indexes, so this should have better performance as well.

Try and see whether below code gives performance with CTE
Update tbl1 p
set column1 = nvl((with CTE as
(select column1 , column2 from tbl2
union
select column1,column2 from tbl3) select column1 from CTE where CTE.column2= p.column2 ),'dummy');

Use merge:
merge into tbl1 tgt
using (select min(column1) column1, column2
from (select column1, column2 from tbl2 union all
select column1, column2 from tbl3)
group by column2) src
on (tgt.column2 = src.column2)
when matched then update set tgt.column1 = src.column1
Let's say you have these tables:
create table tbl1(column1, column2) as (
select 0, 'A' from dual union all
select 0, 'B' from dual union all
select 0, 'C' from dual union all
select 0, 'D' from dual );
create table tbl2(column1, column2) as (
select 1, 'A' from dual union all
select 41, 'D' from dual );
create table tbl3(column1, column2) as (
select 2, 'B' from dual union all
select 42, 'D' from dual );
We have value for key A in tbl2, value for B in tbl3, value for C is absent in both source tables, and we have two problematic values for key D. You have to decide what to do in last case, use any aggregation function like min(), avg() or listagg() for strings. If such situation is not possible then you can simplify my statement, replace source subquery with simple union.
You could also use update, but in this case you have to add where clause and check existence of keys to avoid nullifying values and this makes code longer.
Result of merge:
COLUMN1 COLUMN2
---------- -------
1 A
2 B
0 C
41 D

Merge statement could have been the perfect solution if it was possible to update the same field in WHEN MATCHED and in WHEN NOT MATCHED. Anyway i fixed it like that:
The table is getting filled from external table using procedure.
Setting the field to 'dummy' for all records in the procedure inserting statement and use only MERGE WHEN MATCHED after works as i expected
PS: i couldnt make it work as #Ondřej Crha said.

Related

How to get also the not existing values

I've got a query like this
select column, count(*)
from mytable
where column in ('XXX','YYY','ZZZ',....)
group by column;
But I want also to get a row for values the aren't in the table.
Let's suppose that 'ZZZ' doesn't exist in mytable, I'd like to get:
COLUMN COUNT(*)
XXX 3
YYY 2
ZZZ 0 (or NULL)
Oracle version 10g
Thanks in advance
Mark
In general, you would need to have a second table which contains all the possible column values whose counts you want to appear in the output. For demo purposes only, we can use a CTE for that:
WITH vals AS (
SELECT 'XXX' AS val UNION ALL
SELECT 'YYY' UNION ALL
SELECT 'ZZZ'
)
SELECT t1.val, COUNT(t2.col) AS cnt
FROM vals t1
LEFT JOIN mytable t2
ON t2.col = t1.val
GROUP BY
t1.val;

select different values for column based on what table the row exists in

I have this query
define LAST_DATE_BEFORE = to_date('03112016','ddmmyyyy')
with
table1 as (some result),
table2 as (some result),
select
MS.PAID_TRANS_IND,
MS.CURR_PRICE_PLAN_KEY,
case
when MS.SEGMENT_KEY in t1.SEGMENT_KEY
then MS.PLAN_SEGMENT_KEY
when MS.SEGMENT_KEY in t2.SEGMENT_KEY
and MS.START_ALLOC_DATE = &LAST_DATE_BEFORE + 1
then MS.SEGMENT_KEY
else null
end as SEGMENT_KEY
from MO_SU MS, table1 t1, table2 t2
table 1 and 2 have different values from table MO_SU. Now I just check column values, but I want to check if the whole row can be found in t1/t2.
I thought this could work
when MS.* in t1.*
it doesn't.
What can I do?
Trying to reduce the question to checking if a given row of a table exists in another table, with equal values in all the columns, not using a JOIN but only the IN, you may need something like the following ( assuming not null values):
with
tab1(colA, colB, colC) as ( select 'a', 'b', 'c' from dual union all
select 'A', 'B', 'C' from dual
),
tab2(columnA, columnB, columnC) as ( select 'a', 'b', 'c' from dual)
select *
from tab1
where (colA, colB, colC) in ( select columnA, columnB, columnC from tab2)
If the second table had exactly the columns you need to check, in the right order and with no other column, in theory you could even edit it into :
...
where (colA, colB, colC) in ( select * from tab2)
but I absolutely recommend NOT to use such an approach: it's always better to avoid things like select *.
OK so if table1 and table2 contains records from MO_SU you can do such thing:
with
table1 as (select rowid r, s.* from MO_SU),
table2 as (select rowid r, s.* from MO_SU)
select what_you_need
from
MO_SU MS inner join (select * from table1 t1 union all select * from table2 t2) t on (MO_SU.rowid = t.r);
However I don't see deeper sense here. What code do because if you're using only data from MO_SU you could probably select it in with. rowid is unique identifier of row so if you attach rowid in CTEs you can the join on rowid to filter only data that is present in CTEs.

How to delete all records returned by a subquery?

I want to delete all records that are returned by a certain query, but I can't figure out a proper way to do this. I tried to DELETE FROM mytable WHERE EXISTS (subquery), however, that deleted all records from the table and not just the ones selected by the subquery.
My subquery looks like this:
SELECT
MAX(columnA) as columnA,
-- 50 other columns
FROM myTable
GROUP BY
-- the 50 other columns above
having count(*) > 1;
This should be easy enough, but my mind is just stuck right now. I'm thankful for any suggestions.
Edit: columnA is not unique (also no other column in that table is globally unique)
Presumably, you want to use in:
DELETE FROM myTable
WHERE columnA IN (SELECT MAX(columnA) as columnA
FROM myTable
GROUP BY -- the 50 other columns above
HAVING count(*) > 1
);
This assumes that columnA is globally unique in the table. Otherwise, you will have to work a bit harder.
DELETE FROM myTable t
WHERE EXISTS (SELECT 1
FROM (SELECT MAX(columnA) as columnA,
col1, col2, . . .
FROM myTable
GROUP BY -- the 50 other columns above
HAVING count(*) > 1
) t2
WHERE t.columnA = t2.columnA AND
t.col1 = t2.col1 AND
t.col2 = t2.col2 AND . . .
);
And, even this isn't guaranteed to work if any of the columns have NULL values (although the conditions can be easily modified to handle this).
Another solution if the uniqueness is only guaranteed by a set of columns:
delete table1 where (col1, col2, ...) in (
select min(col1), col2, ...
from table1
where...
group by col2, ...
)
Null values will be ignored and not deleted.
To achieve this, try something like
with data (id, val1, val2) as
(
select 1, '10', 10 from dual union all
select 2, '20', 21 from dual union all
select 2, null, 21 from dual union all
select 2, '20', null from dual
)
-- map null values in column to a nonexistent value in this column
select * from data d where (d.id, nvl(d.val1, '#<null>')) in
(select dd.id, nvl(dd.val1, '#<null>') from data dd)
If you need to delete all the rows of a table such that the value of a given field is in the result of a query, you can use something like
delete table
my column in ( select column from ...)

Select statement for Oracle SQL

I have a table say,
column1 column2
a apple
a ball
a boy
b apple
b eagle
b orange
c bat
c ball
c cork
Now I would like to fetch column1 based on the rows that doesn't contain 'apple' and also ignore values in column1 if any of the rows have 'apple' in it. So in the table above only 'C' must be retured.
I am kind of new to Oracle SQL and I know Select column1 from table where column2 != 'apple' will not work. I need some help with this please.
You could use DISTINCT with NOT IN in following:
QUERY 1 using NOT IN
select distinct col1
from t
where col1 not in (select col1 from t where col2 = 'Apple')
QUERY 2 using NOT EXISTS
As per #jarlh comment you could use NOT EXISTS in following:
select distinct col1
from #t t1
where not exists (select 1 from #t t2 where col2 = 'Apple' and t1.col1 = t2.col1)
SAMPLE DATA
create table t
(
col1 nvarchar(60),
col2 nvarchar(60)
)
insert into t values
('a','apple')
,('a','ball')
,('a','boy')
,('b','apple')
,('b','eagle')
,('b','orange')
,('c','bat')
,('c','ball')
,('c','cork')
Assuming that column1 is NOT NULL you could use:
SELECT DISTINCT t.column1
FROM table_name t
WHERE t.column1 NOT IN (SELECT column1
FROM table_name
WHERE column2 = 'apple');
LiveDemo
To get all columns and rows change DISTINCT t.column1 to *.
Select * from tbl
Left join (
Select column1 from tbl
Where column2 like '%apple%'
Group by column1
) g on tbl.colum1 = g.column1
Where g.column1 is null
Seems to me that you need to find a summary of all colum1 values that have any reference to apple. Then list the rows that have no match to the summary list (g)
If I understand well, you need the values af column1 such that in your table does not exist a row with the same value of column1 and 'apple' in column2; you can translate this in SQL with:
Select column1
from your_table t
where not exists (
select 1
from your_table t2
where t2.column1 = t1.column1
and t2.column2= 'apple'
)
This is only one of the possible ways to get your result, soyou can rewrite it in many ways; I believe this way of writing is similar enough to the logics to clearly explain how a logic could be written in plain SQL.

How to use union if i need to "order by" all selects

i have 3 separate select statements that i need to union. but all of them need to be ordered by a different column.
i tried doing this
select * from(
select * from (select columns from table1 order by column1 ) A
UNION
select * from (select columns from table2 order by column2 ) B
UNION
select * from (select columns from table3 order by column3 ) C
) Table
but this doesn't work
does anyone have any experience with this?
You can do something like this:
select *
from((select columns, 'table1' as which from table1 )
UNION ALL
(select columns, 'table2' from table2 )
UNION ALL
(select columns, 'table3' from table3 )
) t
order by which,
(case when which = 'table1' then column1
when which = 'table2' then column2
when which = 'table3' then column3
end);
This assumes that the columns used for ordering are all of the same type.
Note that this query uses union all instead of union. I see no reason why you would want to eliminate duplicates if you want the results from the three subqueries ordered independently.
EDIT:
You can also express the order by separately for each table:
order by which,
(case when which = 'table1' then column1 end) ASC,
(case when which = 'table2' then column2 end) DESC
(case when which = 'table3' then column3 end)
You should separate these columns in the one common column and then order
SELECT * FROM
(
SELECT A.*,columnA as ORDER_COL FROM A
UNION ALL
SELECT B.*,columnB as ORDER_COL FROM B
UNION ALL
SELECT C.*,columnC as ORDER_COL FROM C
) as T1
ORDER BY ORDER_COL
You have to order it AFTER the UNION's.
You can "trick it" like this:
select Artificial, a,b,c from(
select 1 as Artificial, a,b,c from (select columns from table1 ) A
UNION
select 2 as Artificial,a,b,c from (select columns from table2 ) B
UNION
select 3 as Artificial,a,b,c from (select columns from table3 ) C
) derivedTable
order by Artificial, c,b,a