How to compare two table values using PLSQL - sql

I have to compare two tables values;
TABLE_A TABLE_B
ID TYPE ID TYPE
12345 12345 3
67891 12345 7
36524 67891 3
67891 2
67891 5
36524 3
Logic: I have to compare table_A id with Table_B id
if found 3&7
good
else found 3 only
avg
else if found 7 only
bad
These good, bad and avg should go back to table A type values.
could any one help me how to write this code in PLSQL.

Assuming that you are considering type 3 and 7 only for your calculations, you can use following merge statement, no need of PL-SQL
merge into table_a a
using (select id, case (listagg(type, ',') within group (order by type))
when '3,7' then 'Good'
when '3' then 'Avg'
when '7' then 'Bad'
else null
end new_type
from table_b
where type in (3,7)
group by id) b
on (a.id = b.id)
when matched then
update set type = new_type;
For Oracle versions prior to 11 g release 2, use following:
merge into table_a a
using (select id, case (trim(both ',' from min(decode(type, 3, 3, null))||','||min(decode(type, 7, 7, null))))
when '3,7' then 'Good'
when '3' then 'Avg'
when '7' then 'Bad'
else null
end new_type
from table_b
where type in (3,7)
group by id) b
on (a.id = b.id)
when matched then
update set type = new_type;
It has been assumed that there are unique combination of id an type in table_b.

I am interpreting what you mean as saying that you want to output 'good' when TableB contains both 3 and 7, 'avg' when it contains only 3, and so on. Here is a way to get this result:
select a.id,
(case when sum(case when b.type = 3 then 1 else 0 end) > 1 and
sum(case when b.type = 7 then 1 else 0 end) > 0
then 'good'
when sum(case when b.type = 3 then 1 else 0 end) > 1
then 'avg'
when sum(case when b.type = 7 then 1 else 0 end)
then 'bad'
end) as logic
from tableA a left outer join
tableB b
on a.id = b.id
group by a.id;

Related

invalid identifier : sum of multiple column in sql

I'm trying to calculate multible columns in this query
SELECT
SUM (CASE WHEN B.ID = 1 THEN 1 END) AS OPD,
SUM (CASE WHEN B.ID = 2 THEN 1 END) AS IPD,
SUM (CASE WHEN B.ID = 3 THEN 1 END) AS DC,
SUM (CASE WHEN B.ID = 4 THEN 1 END) AS PROC,
SUM (CASE WHEN B.ID = 5 THEN 1 END) AS SUR,
(OPD + IPD + PROC) as Total
FROM REF_TB_APP_TRANSACTIONS A,
REF_VW_VISIT_TYPE B
WHERE A.REQ_VISIT_TYPE = B.ID
AND A.TO_EST_CODE = 20068;
but I got this error PROC invalid identifier
You can't add the three SUMS in the Total column in the SELECT directly, since you're using the aliases of those columns. You could just do your Total column with another SUM CASE.
SELECT
SUM (CASE WHEN B.ID = 1 THEN 1 END) AS OPD,
SUM (CASE WHEN B.ID = 2 THEN 1 END) AS IPD,
SUM (CASE WHEN B.ID = 3 THEN 1 END) AS DC,
SUM (CASE WHEN B.ID = 4 THEN 1 END) AS [PROC],
SUM (CASE WHEN B.ID = 5 THEN 1 END) AS SUR,
SUM (CASE WHEN B.ID IN (1,2,4)THEN 1 END) AS Total
FROM REF_TB_APP_TRANSACTIONS A,
REF_VW_VISIT_TYPE B
WHERE A.REQ_VISIT_TYPE = B.ID
AND A.TO_EST_CODE = 20068;
Depending on the DBMS you are using. You cant sum columns that are aliased like that, you would have to use a sub select and do the sum from there. If you verify your DBMS we can create query.
If MS SQL the below will work. A couple things:
PROC is reserved word, so either change that or put brackets around it (I went for brackets). Also it is preferred if you use JOINS vs. the way you had the queries.
SELECT OPD, IPD, DC, [PROC], SUR, (OPD + IPD + [PROC]) as Total
FROM (
SELECT
SUM (CASE WHEN B.ID = 1 THEN 1 END) AS OPD,
SUM (CASE WHEN B.ID = 2 THEN 1 END) AS IPD,
SUM (CASE WHEN B.ID = 3 THEN 1 END) AS DC,
SUM (CASE WHEN B.ID = 4 THEN 1 END) AS [PROC],
SUM (CASE WHEN B.ID = 5 THEN 1 END) AS SUR
FROM REF_TB_APP_TRANSACTIONS A
INNER JOIN REF_VW_VISIT_TYPE B ON A.REQ_VISIT_TYPE = B.ID
WHERE A.TO_EST_CODE = 20068
) SUB
You can't reference the aliased columns as part of the select because in the order of query execution, they don't exist yet.
You simply wrap your query so it becomes a derived table and then you can refer to them in an outer select, see:
select OPD, IPD, DC, [PROC], SUR, OPD + IPD + [PROC] as Total from (
SELECT
SUM (CASE WHEN B.ID = 1 THEN 1 END) AS OPD,
SUM (CASE WHEN B.ID = 2 THEN 1 END) AS IPD,
SUM (CASE WHEN B.ID = 3 THEN 1 END) AS DC,
SUM (CASE WHEN B.ID = 4 THEN 1 END) AS [PROC],
SUM (CASE WHEN B.ID = 5 THEN 1 END) AS SUR
FROM REF_TB_APP_TRANSACTIONS A
join REF_VW_VISIT_TYPE B on B.ID=A.REQ_VISIT_TYPE
where A.TO_EST_CODE = 20068
)x
Guessing because you have a semi-colon this is SQLServer, in which case you will need to use [] around the reserved word PROC
I've also properly joined your tables as it's not 1989 any more :-0

Compare rows with condition

Edit: when 5 or 9 does not exist, i need a null value (or another flag)
I have 3 columns. SECTION, STATUS and NAME. Within a SECTION there are a maximum of 10 rows (STATUS 1 to 10). I have to compare the value of NAME for STATUS 5 and 9 within a SECTION. AND then indicate if those 2 NAMES (for STATUS 5 and 9) are the same for each SECTION.
section status name
1 5 a
1 6 a
1 9 b
2 4 c
2 5 d
2 9 d
2 10 d
3 5 e
3 10 e
Desired output
Section equalnames
1 no
2 yes
3 null/flag
select
a.section,
case
when a.name = b.name then 'YES'
when a.name <> b.name then 'NO'
when (a.name is null or b.name is null) then 'NULL' end
from
(select * from <table> where status = 5) a
full join (select * from <table> where status = 9) b
on a.section = b.section
With conditional aggregation:
SELECT section,
MAX(CASE WHEN status = 5 THEN name END) =
MAX(CASE WHEN status = 9 THEN name END) equalnames
FROM tablename
WHERE status IN (5, 9)
GROUP BY section
ORDER BY section
See the demo.
Results:
section | equalnames
------- | ----------
1 | f
2 | t
3 | null
You could try using a left join on same table for 5 and 9
select a.section, a.status s5, a.name n5, b.status b9, b.name n9
, case when a.name = b.name the yes
when a.name is null or b.name is nul the NULL
when a.name <> b.name then no end equalname
from my_table a
left join my_table b a.section = b.section and a.status =5 and b.status=9
A more optimised solution could be with cte and window function:
with cte as (
select section, count(status)over(partition by section,name order by section) count_with_same_name,
count(status)over(partition by section order by section) count_with_different_name
from tname where status in (5,9))
select section,(case when (max(count_with_same_name)=2) then 'Yes' when (max(count_with_different_name)=2) then 'No' else 'null/flag' end)
from cte
group by section
Output:
I think you just want conditional aggregation:
select section,
(case when min(case when status = 5 then name end) =
min(case when status = 9 then name end)
then 'yes'
when count(case when status in (5, 9) then status end) < 2
then 'null/flag'
else 'no'
end)
from t
group by section;
No join is needed and I would not advise one for this problem.

SQL query to find only those customer ids which have 2 source values

I have 2 tables, one which stores the customer id and the other table which stores customer id along with the information about different sources which use that customer information. Example:
TABLE A
Customer Id
1
2
3
..
TABLE B
Customer Id Source
1 'AA'
2 'AA'
1 'AB'
2 'AB'
2 'AC'
3 'AA'
3 'AB'
3 'AE'
4 'AA'
4 'AB'
I want to write a SQL query which returns records which have only AA and AB as sources (no other sources)
I have written the below query, but it is not working correctly:
select a.customer_id
from A a, B b
where a.customer_id = b.customer_id
and b.source IN ('AA','AB')
group by a.customer_id
having count(*) = 2;
A rather efficient solution is a couple of exists subqueries:
select a.*
from a
where
exists(select 1 from b where b.customer_id = a.customer_id and b.source = 'AA')
and exists(select 1 from b where b.customer_id = a.customer_id and b.source = 'AB')
and not exists(select 1 from b where b.customer_id = a.customer_id and b.source not in ('AA', 'AB'))
With an index on b(customer_id, source), this should run quickly.
Another option is aggreation:
select customer_id
from b
group by customer_id
having
max(case when source = 'AA' then 1 else 0 end) = 1
and max(case when source = 'AB' then 1 else 0 end) = 1
and max(case when source not in ('AA', 'AB') then 1 else 0 end) = 0
This assumes that the customer_id/source combination has no duplicates
select a.customer_id
from A a join B b
on a.customer_id = b.customer_id
group by a.customer_id
-- both 'AA' and 'AB', but no other
having sum(case when b.source IN ('AA','AB') then 1 else -1 end) = 2
It might be more efficient to aggregate before the join:
select a.customer_id
from A a join
( select customer_id
from B b
group by customer_id
-- both 'AA' and 'AB', but no other
having sum(case when source IN ('AA','AB') then 1 else -1 end) = 2
) b
on a.customer_id = b.customer_id
You can use aggregation:
select b.customer_id
from b
where b.source in ('AA', 'AB')
group by b.customer_id
having count(distinct b.source) = 2;
That said, your version should work. However, you should learn to use proper, explicit, standard, readable JOIN syntax. The join, however, is not needed in this case.
If you want only those two sources, you need to tweak the logic:
select b.customer_id
from b
group by b.customer_id
having sum(case when b.source = 'AA' then 1 else 0 end) > 0 and -- has AA
sum(case when b.source = 'AB' then 1 else 0 end) > 0 and -- has AB
count(distinct b.source) = 2;

Oracle query with group

I have a scenario where I need to fetch all the records within an ID for the same source. Given below is my input set of records
ID SOURCE CURR_FLAG TYPE
1 IBM Y P
1 IBM Y OF
1 IBM Y P
2 IBM Y P
2 TCS Y P
3 IBM NULL P
3 IBM NULL P
3 IBM NULL P
4 IBM NULL OF
4 IBM NULL OF
4 IBM Y ON
From the above settings, I need to select all the records with source as IBM within that same ID group.Within the ID group if there is at least one record with a source other than IBM, then I don't want any record from that ID group. Also, we need to fetch only those records where at least one record in that ID group with curr_fl='Y'
In the above scenario even though the ID=3 have a source as IBM, but there is no record with CURR_FL='Y', my query should not fetch the value.In the case of ID=4, it can fetch all the records with ID=4, as one of the records have value='Y'.
Also within the group which has satisfied the above condition, I need one more condition for source_type. if there are records with source_type='P', then I need to fetch only that record.If there are no records with P, then I will search for source_type='OF' else source_type='ON'
I have written a query as given below.But it's running for long and not fetching any results. Is there any better way to modify this query
select
ID,
SOURCE,
CURR_FL,
TYPE
from TABLE a
where
not exists(select 1 from TABLE B where a.ID = B.ID and source <> 'IBM')
and exists(select 1 from TABLE C where a.ID = C.ID and CURR_FL = 'Y') and
(TYPE, ID) IN (
select case type when 1 then 'P' when 2 then 'OF' else 'ON' END TYPE,ID from
(select ID,
max(priority) keep (dense_rank first order by priority asc) as type
from ( select ID,TYPE,
case TYPE
when 'P' then 1
when 'OF' then 2
when 'ON' then 3
end as priority
from TABLE where ID
in(select ID from TABLE where CURR_FL='Y') AND SOURCE='IBM')
group by ID))
I think you can just do a single aggregation over your table by ID and check for the yes flag as well as assert that no non IBM source appears. I do this in a CTE below, and then join back to your original table to return full matching records.
WITH cte AS (
SELECT
ID,
CASE WHEN SUM(CASE WHEN TYPE = 'P' THEN 1 ELSE 0 END) > 0
THEN 1
WHEN SUM(CASE WHEN TYPE = 'OF' THEN 1 ELSE 0 END) > 0
THEN 2
WHEN SUM(CASE WHEN TYPE = 'ON' THEN 1 ELSE 0 END) > 0
THEN 3 ELSE 4 END AS p_type
FROM yourTable
GROUP BY ID
HAVING
SUM(CASE WHEN CURR_FLAG = 'Y' THEN 1 ELSE 0 END) > 0 AND
SUM(CASE WHEN SOURCE <> 'IBM' THEN 1 ELSE 0 END) = 0
)
SELECT t1.*
FROM yourTable t1
INNER JOIN cte t2
ON t1.ID = t2.ID
WHERE
t2.p_type = 1 AND t1.TYPE = 'P' OR
t2.p_type = 2 AND t1.TYPE = 'OF' OR
t2.p_type = 3 AND t1.TYPE = 'ON';

Merge multiple columns into one column with multiple rows

In PostgreSQL, how can I merge multiple columns into one column with multiple rows?
The columns are all boolean, so I want to:
Filter for true values only
Replace the true value (1) with the name of the column (A, B or C)
I have this table:
ID | A | B | C
1 0 1 0
2 1 1 0
3 0 0 1
4 1 0 1
5 1 0 0
6 0 1 1
I want to get this table:
ID | Letter
1 B
2 A
2 B
3 C
4 A
4 C
5 A
6 B
6 C
I think you need something like this:
SELECT ID, 'A' as Letter FROM table WHERE A=1
UNION ALL
SELECT ID, 'B' as Letter FROM table WHERE B=1
UNION ALL
SELECT ID, 'C'as Letter FROM table WHERE C=1
ORDER BY ID, Letter
SELECT ID,
(CASE
WHEN TABLE.A = 1 then 'A'
WHEN TABLE.B = 1 then 'B'
WHEN TABLE.C = 1 then 'C'
ELSE NULL END) AS LETTER
from TABLE
You may try this.
insert into t2 select id, 'A' from t1 where A=1;
insert into t2 select id, 'B' from t2 where B=1;
insert into t2 select id, 'C' from t3 where C=1;
If you care about the order, then you can do this.
insert into t3 select id, letter from t2 order by id, letter;
W/o UNION
You can use a single query to get the desired output.Real time example
select id
,regexp_split_to_table((
concat_ws(',', case
when a = 0
then null
else 'a'
end, case
when b = 0
then null
else 'b'
end, case
when c = 0
then null
else 'c'
end)
), ',') l
from c1;
regexp_split_to_table() & concat_ws()