Oracle CASE failing with multiple columns in Else clause - sql

In Oracle below query is working fine with single column but when I tried to put multiple columns it fails, Actually my requirement is to Exit SQL query if there is no records in particular table and its complex inner query (below is simplified problem description) and I cannot use PL/SQL block, please suggest.
SQL> select
case when (select count(*) from bl1_charge_adj where rownum=1) = '1' then ( select customer_id from customer)
else 44 END as ee
from dual;
EE
----------
44
SQL> select
case when (select count(*) from bl1_charge_adj where rownum=1) = '1' then ( select customer_id,DL_UPDATE_STAMP from customer)
else (1,2) END
from dual
ERROR at line 1:
ORA-00907: missing right parenthesis

I think you want something like this:
select customer_id, DL_UPDATE_STAMP
from customer
where exists (select 1 from bl1_charge_adj)
union all
select 1, 2
from dual
where not exists (select 1 from bl1_charge_adj);

This should work:
select nvl(c.customer_id, 1), nvl(c.DL_UPDATE_STAMP, 2)
from ( select count(*) as c from bl1_charge_adj where rownum=1) d
left outer join customer c on d.c = 1;
I suspect it would be better done by changing the count(*) to something else if the bl1_charge_adj table is big.

Related

oracle count query with union

I have a query with union all functionality each giving me count(*) return from respective queries and another count query like below. I want an outer query that gives the total.
1st query
select count(*) from a
union all
select count(*) from b;
Sample result for 1st query:
COUNT
10
40
2nd query
select count(*) from xy;
Sample result for 2nd query:
COUNT
20
I want output like this in 2 rows:
TABLES
COUNT
xy
20
ab
50
something like above. How can I achieve this in oracle? please suggest the best way to do this.
I wrote a select and union all but not sure how to proceed further.
One option is to sum counts returned by the 1st query and then union it with the 2nd; also, add constants which show the source:
select 'ab' what, (select count(*) from a) + (select count(*) from b) cnt from dual
union all
select 'xy', count(*) from xy;
You can use:
SELECT 'ab' AS type,
COUNT(*) AS total
FROM ( SELECT 1 FROM a UNION ALL
SELECT 1 from b );
UNION ALL
SELECT 'xy', COUNT(*)
FROM xy;
You can sum counts from your three unioned Select statements and group the result by combination of sources:
WITH
a AS
( Select LEVEL "A_ID", 'some column a' "COL_A" From Dual Connect By LEVEL <= 30 ),
b AS
( Select LEVEL "B_ID", 'some column b' "COL_B" From Dual Connect By LEVEL <= 20 ),
xy AS
( Select LEVEL "XY_ID", 'some column xy' "COL_XY" From Dual Connect By LEVEL <= 20 )
with above sample data it is like here:
SELECT
CASE WHEN SOURCE IN('a', 'b') THEN 'ab' ELSE SOURCE END "SOURCE",
Sum(CNT) "CNT"
FROM
( Select 'a' "SOURCE", Count(*) "CNT" From a Union All
Select 'b', Count(*) From b Union All
Select 'xy', Count(*) From xy
)
GROUP BY
CASE WHEN SOURCE IN('a', 'b') THEN 'ab' ELSE SOURCE END
--
-- R e s u l t :
-- SOURCE CNT
-- ------ ----------
-- ab 50
-- xy 20
Assuming that your real queries can be a lot more complex, I take it as a given that we shall not try to change them and somehow merge or split them.
Your first query returns two rows. You want to get their sum, so you must aggregate the result and use SUM.
Below query uses CTEs (subqueries in the WITH clause) for your two queries, and then a query that gets this sum. It then uses these CTEs for the final UNION ALL query.
with query1 (cnt) as (select count(*) from a union all select count(*) from b)
, query2 (cnt) as (select count(*) from xy)
, sumquery1 (total) as (select sum(cnt) from query1)
select 'ab' as tables, total from sumquery1
union all
select 'xy' as tables, cnt from query2
order by tables desc;

Diff between two tables (using sql) -> incremental changes

I have a need to identify differences between two tables. I have looked at sql query to return differences between two tables but it was a bit too different for me to extrapolate with my current SQL skills.
Table A is a snapshot of a certain group of people where the snapshot was taken yesterday, where each row is a unique person and certain characteristics about the person. Table B is the same snapshot taken 24 hours later. Within the 24 hour period:
New people may have been added.
People from yesterday may have been removed.
People from yesterday may have changed (i.e., original row is there, but one or more column values have changed).
My output should have the following:
a row for each new person added
a row for each person removed
a row for each person who has changed
I would grateful for any ideas. Thanks!
This type of problem has a very simple and efficient solution that does not use joins (it doesn't even use a union of the results of two MINUS operations) - it just uses one union and a GROUP BY operation. The solution was developed in a thread on AskTom many years ago, it is surprising that it is not more widely known and used. For example (but not only): https://asktom.oracle.com/pls/apex/f?p=100:11:0::::P11_QUESTION_ID:24371552251735
In your case, assuming there is a primary key constraint on PERSON_ID (which makes the solution simpler):
select max(flag) as flag, PERSON_ID, first_name, last_name, (etc. - all the columns)
from ( select 'old' as flag, t1.*
from old_table t1
union all
select 'new' as flag, t2.*
from new_table t2
)
group by PERSON_ID, first_name, last_name, (etc.)
having count(*) = 1
order by PERSON_ID -- optional
;
If for a PERSON_ID all the data is the same in both tables, that will result in a count of 2 for that group. So it won't pass the HAVING condition. The only groups that will have a count of 1 (and therefore will be just one row each!) are either rows that are in one table but not the other. If a person was added, that will show only one row, with the flag = 'new'. If a person was deleted, you will get only one row, with the flag 'old'. If there were updates, the same PERSON_ID will appear twice, but since at least one field is different, the two rows (one with flag 'new' and the other with 'old') will be in different groups, they will pass the HAVING filter, and they will BOTH be in the output.
Which is slightly different from what you requested; you will get both the old AND the new information for updates, labeled as 'old' and 'new'. You said you wanted only one of those but didn't state which one. This will give you both (which makes more sense anyway), but if you really only want one, it can be done easily in the query above.
Note - the outer select must have max(flag) rather than flag because flag is not a GROUP BY column; but it's the max() over exactly one row, so it WILL be the flag for that row anyway.
Added - OP indicated he would like to get only the "new" row for a person with updated (changed, modified) data. The approach shown below will change the flag to "changed" in this case.
with old_table ( person_id, first_name, last_name ) as (
select 101, 'John', 'Smith' from dual union all
select 102, 'Mary', 'Green' from dual union all
select 103, 'July', 'Dobbs' from dual union all
select 104, 'Will', 'Scott' from dual
),
new_table ( person_id, first_name, last_name ) as (
select 101, 'Joe' , 'Smith' from dual union all
select 102, 'Mary', 'Green' from dual union all
select 104, 'Will', 'Scott' from dual union all
select 105, 'Andy', 'Brown' from dual
)
-- end of test data; solution (SQL query) begins below this line
select case ct when 1 then flag else 'changed' end as flag,
person_id, first_name, last_name
from (
select max(flag) as flag, person_id, first_name, last_name,
count(*) over (partition by person_id) as ct,
row_number() over (partition by person_id order by max(flag)) as rn
from ( select 'old' as flag, t1.*
from old_table t1
union all
select 'new' as flag, t2.*
from new_table t2
)
group by person_id, first_name, last_name
having count(*) = 1
)
where rn = 1
order by person_id -- ORDER BY clause is optional
;
Output:
FLAG PERSON_ID FIRS_NAME LAST_NAME
------- ---------- --------- ---------
changed 101 Joe Smith
old 103 July Dobbs
new 105 Andy Brown
The first 2 parts are easy:
select 'New', name from B where not exists (select name from A where A.name=B.name)
union select 'Removed', name from A where not exists (select name from B where B.name = A.name)
The last one is where you need to compare characteristics. How many of them are there? Do you want to list what has changed or only that they have changed?
For argument's sake, let us only say that the characteristics are address and telephone #:
union select 'Phone', name from A,B where A.name = B.name and A.telephone != B.telephone
union select 'Address', name from A,B where A.name = B.name and A.address != B.address
Note: The question isn't currently tagged with the dbms. I use sql-server, so that's what I used to write the below. There may be slight differences in another dbms.
You can do something along these lines:
select *
from TableA a
left join TableB b on b.ID = a.ID
where a.ID is null -- added since yesterday
union
select *
from TableA a
left join TableB b on b.ID = a.ID
where b.ID is null -- removed since yesterday
union
select *
from TableA a
inner join TableB b on b.ID = a.ID -- restrict to records in both tables
where a.SomeValue <> b.SomeValue
or a.SomeOtherValue <> b.SomeOtherValue
--etc
Each select handles one portion of your expected output. In this manner, they'd all be joined into 1 result set. If you drop the union, you'll end up with a separate set for each.
I suggest to use Except to get the changed records. The below query should work if the db is sql server.
-- added since yesterday
SELECT B.*
FROM TableA A
LEFT Outer Join TableB B on B.ID = A.ID
WHERE A.ID IS NULL
UNION
-- removed since yesterday
SELECT A.*
FROM TableA A
LEFT OUTER JOIN TableB B on B.ID = A.ID
WHERE B.ID IS NULL
UNION
-- Those changed with values from yesterdady
SELECT B.* FROM TableB B WHERE EXISTS(SELECT A.ID FROM TableA A WHERE A.ID = B.ID)
EXCEPT
SELECT A.* FROM TableA A WHERE EXISTS(SELECT B.ID FROM TableB B WHERE B.ID = A.ID)
Assuming you have a unique id for each person in the able, you can use full outer join:
select coalesce(ty.customerid, tt.customerid) as customerid,
(case when ty.customerid is null then 'New'
when tt.customerid is null then 'Removed'
else 'Modified'
end) as status
from tyesterday ty full outer join
ttoday tt
on ty.customerid= tt.customerid
where ty.customerid is null or
tt.customerid is null or
(tt.col1 <> ty.col1 or tt.col2 <> ty.col2 or . . . ); -- may need to take `NULL`s into account
mathguy provided a successful answer to my initial problem. I asked him for a revision (to make it even better). He provided a revision, but I am getting a "missing keyword" error when executing against my code. Here is my code:
select case when ct = 1 then flag else 'changed' as flag, PERSON_ID, FIRSTNAME, LASTNAME
from (
select max(flag), PERSON_ID, FIRSTNAME, LASTNAME
count() over (partition by PERSON_ID) as ct,
row_number() over (partition by PERSON_ID
order by case when flag = 'new' then 0 end) as rn
from ( select 'old' as flag, t1.*
from YESTERDAY_TABLE t1
union all
select 'new' as flag, t2.*
from TODAY_TABLE t2
)
group by PERSON_ID, FIRSTNAME, LASTNAME
having count(*) = 1
)
where rn = 1
order by PERSON_ID;

How to select case when more column for inserting?

i need your help to insert into from select but my query is too complex for me. My logic is below
INSERT INTO TheTable(A,CustomerNo,item,B,C,D)
SELECT DISTINCT
case when ((select count(*) from hesap where CustomerNo=e.CustomerNo)0) then
select top 1, A,CustomerNo,item+1,B,C,D
from dbo.Table1 order by ekno desc
) else select 100,e.CustomerNo,e.item,0,e.defterid,'C'
from Table2 e end
But i can not do that...
You can do case on column basis only.
What you can do is UNIONing two selects, and insert the result. Something like
INSERT INTO TheTable(A,CustomerNo,item,B,C,D)
SELECT ...
WHERE (select count(*) from hesap where CustomerNo=e.CustomerNo) = 0
UNION
SELECT select 100,e.CustomerNo,e.item,0,e.defterid,'C'
WHERE (select count(*) from hesap where CustomerNo=e.CustomerNo) > 0
I'm sorry, I cannot always follow your Select, but I hope you got the idea. You have to separate the 2 possibilities before you UNION them, so only 1 row can exist for each original row.
Can you try removing the comma after top 1 Like this:
INSERT INTO TheTable(A,CustomerNo,item,B,C,D)
SELECT DISTINCT
case when ((select count(*) from hesap where CustomerNo=e.CustomerNo)0) then
select top(1) A,CustomerNo,item+1,B,C,D
from dbo.Table1 order by ekno desc
)
else select 100,e.CustomerNo,e.item,0,e.defterid,'C'
from Table2 e end

Count rows in more than one table with tSQL

I need to count rows in more than one table in SQL Server 2008. I do this:
select count(*) from (select * from tbl1 union all select * from tbl2)
But it gives me an error of incorrect syntax near ). Why?
PS. The actual number of tables can be more than 2.
In case you have different number of columns in your tables try this way
SELECT count(*)
FROM (
SELECT NULL as columnName
FROM tbl1
UNION ALL
SELECT NULL
FROM tbl2
) T
try this:
You have to give a name to your derived table
select count(*) from
(select * from tbl1 union all select * from tbl2)a
I think you have to alias the SELECT in the FROM clause:
select count(*)
from
(
select * from tbl1
union all
select * from tbl2
) AS SUB
You also need to ensure that the * in both tables tbl1 and tbl2 return exactly the same number of columns and they have to be matched in their type.
I don't like doing the union before doing the count. It gives the SQL optimizer an opportunithy to choose to do more work.
AlexK's (deleted) solution is fine. You could also do:
select (select count(*) from tbl1) + (select count(*) from tbl2) as cnt

Join two sql queries side by side with no column common

I need to join the results of two queries horizontally.
Consider a query below which will return two rows:
Select *
from Salary
where sal > 10000
The result of the query above should be joined side by side with the result of the query below which will again return two rows.Or I need to just concatenate the two result sets:
Select 'xyz' from dual
union
Select 'abc' from dual
Please suggest how this can be done as I tried to do this with the query below but it returns a cartesian product:
Select *
from (Select *
from salary
where sal > 10000) TEMP1,
(Select 'xyz' from dual
union
Select 'abc' from dual) TEMP2
You can do that by joining on rownum like this:
SELECT *
FROM
(SELECT view_name, rownum AS r FROM all_views WHERE rownum <=10)
FULL OUTER JOIN (SELECT table_name, rownum AS r FROM all_tables WHERE rownum <=10) USING (r)
In your case this would look like (untested):
Select * from
(Select salary.*, rownum AS r from salary where sal>10000) TEMP1
FULL OUTER JOIN
(SELECT temp2.*, rownum r FROM
(Select 'xyz' from dual
union
Select 'abc' from dual) TEMP2)
USING (r)
you can introduce an artificial join column:
SELECT *
FROM (SELECT s.*, ROWNUM ID FROM Salary s WHERE sal > 10000) q1
JOIN (SELECT 'xyz' col1, 1 ID
FROM dual
UNION
SELECT 'abc' col1, 2 ID FROM dual) q2 ON q1.id = q2.id
Thank you very much for your help.
But what I need is a bit complicated. I have updated the previous query to be somewhat like below instead of simple query that I posted before (SELECT * FROM Salary WHERE sal > 10000):
SELECT name, sal, address
FROM (SELECT e1.name, s1.sal, s1.grade, s2.address FROM Emp e1, salary s1,
(Select empcode, address FROM Address WHERE empcode LIKE 'NY%') s2
WHERE e1.hiredate =201001
AND s1.sal>10000)
I know the above query does not make much of a relevance. However, this is similar to what I need actually. I am not posting the original one as that is very complicated but if this can be done for this query, then I will be able to replicate the same on the original one as well.
Thank you,
Sharon