SQL get record when finding in 2 records - sql

Hi I tried to build sql query to find id when is in 2 records (can be more). Let me explained by example
I have 2 tables
C
id
type_id
1
499
1
599
D
type_id
type_name
499
AN
599
DE
And I want to get id which has AN and DE
SELECT *
FROM C
INNER JOIN D
ON D.type_id = C.type_id
WHERE
EXISTS (SELECT 1 FROM D D1 WHERE D1.type_id = C.type_id AND D1.type_name = 'AN') AND
EXISTS (SELECT 1 FROM D D2 WHERE D2.type_id = C.type_id AND D2.type_name = 'DE');
But did not work .Than you for help

If you want all the data from the join then you can use analytic functions:
SELECT id,
type_id,
type_name
FROM (
SELECT c.id,
c.type_id,
d.type_name,
COUNT(CASE d.type_name WHEN 'AN' THEN 1 END) OVER (PARTITION BY c.id)
AS num_an,
COUNT(CASE d.type_name WHEN 'DE' THEN 1 END) OVER (PARTITION BY c.id)
AS num_de
FROM C
INNER JOIN D
ON D.type_id = C.type_id
WHERE d.type_name IN ('AN', 'DE')
)
WHERE num_an > 0
AND num_de > 0;
Which outputs:
ID
TYPE_ID
TYPE_NAME
1
599
DE
1
499
AN
If you just want the id then you can aggregate and use a HAVING clause:
SELECT c.id
FROM C
INNER JOIN D
ON D.type_id = C.type_id
WHERE d.type_name IN ('AN', 'DE')
GROUP BY c.id
HAVING COUNT(CASE d.type_name WHEN 'AN' THEN 1 END) > 0
AND COUNT(CASE d.type_name WHEN 'DE' THEN 1 END) > 0
Which outputs:
ID
1
fiddle

Get the distinct counts of type_name for each ID ensure count = two and limit type_name to 'AN' or 'DE'
SELECT C.ID
FROM C
INNER JOIN D
on C.type_id=D.type_id -- standard join on Type_ID
WHERE D.Type_name in ('AN','DE') -- limit records to only AN/DE since we need both.
GROUP BY C.ID -- group so we get just 1 ID
HAVING Count(Distinct Type_name) = 2 -- ensure distinct count is 2 for each C.ID.
We join the two tables
We limit to ID having either an 'AN' or DE type name
We group by ID's
We count the distinct types for each ID and if it's 2, we know we have an AN and DE type for that ID.
Count distinct is used since I'm unsure if a type_name could be duplicated for a C.ID. It looks possible given table structure. but unsure without known Pk/FK relations. distinct "Might" be able to be removed if we KNOW it's not possible.

Related

Compare the same id with 2 values in string in one table

I have a table like this:
id
status
grade
123
Overall
A
123
Current
B
234
Overall
B
234
Current
D
345
Overall
C
345
Current
A
May I know how can I display how many ids is fitting with the condition:
The grade is sorted like this A > B > C > D > F,
and the Overall grade must be greater than or equal to the Current grade
Is it need to use CASE() to switch the grade to a number first?
e.g. A = 4, B = 3, C = 2, D = 1, F = 0
In the table, there should be 345 is not match the condition. How can I display the tables below:
qty_pass_the_condition
qty_fail_the_condition
total_ids
2
1
3
and\
fail_id
345
Thanks.
As grade is sequential you can do order by desc to make the number. for the first result you can do something like below
select
sum(case when GradeRankO >= GradeRankC then 1 else 0 end) AS
qty_pass_the_condition,
sum(case when GradeRankO < GradeRankC then 1 else 0 end) AS
qty_fail_the_condition,
count(*) AS total_ids
from
(
select * from (
select Id,Status,
Rank() over (partition by Id order by grade desc) GradeRankO
from YourTbale
) as a where Status='Overall'
) as b
inner join
(
select * from (
select Id,Status,
Rank() over (partition by Id order by grade desc) GradeRankC
from YourTbale
) as a where Status='Current'
) as c on b.Id=c.Id
For second one you can do below
select
b.Id fail_id
from
(
select * from (
select Id,Status,
Rank() over (partition by Id order by grade desc) GradeRankO
from Grade
) as a where Status='Overall'
) as b
inner join
(
select * from (
select Id,Status,
Rank() over (partition by Id order by grade desc) GradeRankC
from Grade
) as a where Status='Current'
) as c on b.Id=c.Id
where GradeRankO < GradeRankC
You can use pretty simple conditional aggregation for this, there is no need for window functions.
A Pass is when the row of Overall has grade which is less than or equal to Current, with "less than" being in A-Z order.
Then aggregate again over the whole table, and qty_pass_the_condition is simply the number of non-nulls in Pass. And qty_fail_the_condition is the inverse of that.
SELECT
qty_pass_the_condition = COUNT(t.Pass),
qty_fail_the_condition = COUNT(*) - COUNT(t.Pass),
total_ids = COUNT(*)
FROM (
SELECT
t.id,
Pass = CASE WHEN MIN(CASE WHEN t.status = 'Overall' THEN t.grade END) <=
MIN(CASE WHEN t.status = 'Current' THEN t.grade END)
THEN 1 END
FROM YourTable t
GROUP BY
t.id
) t;
To query the actual failed IDs, simply use a HAVING clause:
SELECT
t.id
FROM YourTable t
GROUP BY
t.id
HAVING MIN(CASE WHEN t.status = 'Overall' THEN t.grade END) >
MIN(CASE WHEN t.status = 'Current' THEN t.grade END);
db<>fiddle

SQL query to perform a lookup with transpose

I would like to achieve the following and to be honest, I don't even know where to start. We have two tables, Customers and Orders. I need to create a third table, which will have combined data, and displayed in a horizontal way.
Those are the current tables:
CUSTOMERS:
Id Email Language
Customer1 1 cust1#email.com en
Customer2 2 cust2#email.com sp
Customer3 3 cust3#email.com ru
ORDERS:
Id CustomerId Total
a 1 200
b 1 300
c 2 400
d 3 500
e 3 500
f 3 500
g 3 500
And the desired outcome:
CustomerID Email Language Order1 Order2 Order3 Order4 Order5 Order6
1 a b - - - -
2 c - - - - -
3 d e f g - -
Each customer can have up to 6 active orders, but the logic can also be that for each customer only the 6 first orders will be listed.
Any suggestions on how to achieve this result? Your help will be greatly appreciated.
SQL tables represent unordered tables. There is no ordering unless a column specifies the ordering. Let me assume that id plays that role.
Then, you can do this with conditional aggregation:
select c.id, c.email, c.language,
max(case when seqnum = 1 then o.id end) as order_1,
max(case when seqnum = 2 then o.id end) as order_2,
max(case when seqnum = 3 then o.id end) as order_3,
max(case when seqnum = 4 then o.id end) as order_4,
max(case when seqnum = 5 then o.id end) as order_5,
max(case when seqnum = 6 then o.id end) as order_6
from customers c left join
(select o.*,
row_number() over (partition by customerid order by id) as seqnum
from orders o
) o
on c.customerid = o.customerid
group by c.id, c.email, c.language;

Select one row from multiple rows based on availability

I have 3 tables:
Emp (Id(PK), Name)
Address (AddressId(PK), AddressType)
EmpAddress (EmpId(FK), AddresId(FK))
One employee may have multiple address.
Sample data:
Emp
1 abc
2 pqr
Address
1 a
2 b
3 c
EmpAddress
1 1
1 2
1 3
Here empid 1 has all 3 addresses.
I want the only one address at a time based on availability.
If adresstype a is available then display only a
If adresstype c is available then display only c
If adresstype b is available then display only b
Priority is a->c->b
If only one available then display that without any Priority .
I wrote this query, but it is not working:
select *
from Emp
inner join EmpAddress on Emp.Id = .EmpAddress .Emp
inner join Address on Address.Id = EmpAddress.Address_Id
where AddressType is NOT NULL
and AddressType = case
when AddressType = 'a' then 'a'
when AddressType = 'c' then 'c'
when AddressType = 'b' then 'b'
end
One approach would be to use ROW_NUMBER() to assign a numerical priority to each address type based on the ordering a > c > b. Then, subquery to retain only the highest ranking address for each employee.
SELECT Id, Emp, AddressType
FROM
(
SELECT e.Id, ea.Emp, a.AddressType,
ROW_NUMBER() OVER (PARTITION BY e.Id
ORDER BY CASE WHEN a.AddressType = 'a' THEN 1
WHEN a.AddressType = 'b' THEN 2
WHEN a.AddressType = 'c' THEN 3 END) rn
FROM Emp e
INNER JOIN EmpAddress ea
ON e.Id = ea.Emp
INNER JOIN Address a
ON a.Id = ea.Address_Id
) t
WHERE t.rn = 1;
select *
from Emp e
cross apply
(
select top 1 ea.AddresId, a.AddressType
from EmpAddress ea
inner join Address a on ea.AddresId = a.AddresId
where ea.EmpId = e.Id
order by case a.AddressType
when 'a' then 1
when 'c' then 2
when 'b' then 3
end
) a
You could also retrieve the records based on Priority via TOP(1) with TIES
SELECT
TOP(1) with TIES e.Id, *
FROM Emp e
INNER JOIN EmpAddress ea ON e.Id = ea.Emp
INNER JOIN Address a ON a.Id = ea.Address_Id
ORDER BY ROW_NUMBER() OVER (PARTITION BY e.Id ORDER BY
CASE (a.AddressType) WHEN 'a' THEN 1
WHEN 'c' THEN 2 WHEN 'b' THEN 3 END)

Different output when using count and group by

When trying to get a count of IDs I get a different answer when grouping by day vs when I am not.
select cv.CONV_DAY, count(distinct cv.CLICK_ID)
from
clickcache.click cc
right join(
select distinct cv.CLICK_ID, cv.CONV_DAY, cv.PIXEL_ID
from clickcache.CONVERSION cv
where cv.CLICK_ID IS NOT NULL) cv ON cv.CLICK_ID = cc.ID
where cc.ADV_ACCOUNT_ID = 25176
and cv.CONV_DAY between '2016-8-01' AND '2016-08-07'
and AMP_CLICK_STATUS_ID = 1
AND pixel_id IN
(SELECT DISTINCT conversion_pixel_id
FROM
ampx.campaign_event_funnel ef
JOIN ampx.campaign cp ON
cp.id = ef.campaign_id
AND cp.campaign_status_id = 1
WHERE
ef.account_id IN(25176)
AND include_optimization = 1 )
group by 1
order by 1 asc
This yields 170 which is the correct answer and the I want. This, on the other hand, displays 157.
select count(distinct cv.CLICK_ID)
from
clickcache.click cc
right join(
select distinct cv.CLICK_ID, cv.CONV_DAY, cv.PIXEL_ID
from clickcache.CONVERSION cv
where cv.CLICK_ID IS NOT NULL) cv ON cv.CLICK_ID = cc.ID
where cc.ADV_ACCOUNT_ID = 25176
and cv.CONV_DAY between '2016-8-01' AND '2016-08-07'
and AMP_CLICK_STATUS_ID = 1
AND pixel_id IN
(SELECT DISTINCT conversion_pixel_id
FROM
ampx.campaign_event_funnel ef
JOIN ampx.campaign cp ON
cp.id = ef.campaign_id
AND cp.campaign_status_id = 1
WHERE
ef.account_id IN(25176)
AND include_optimization = 1 )
My question is why do I get this discrepancy and how to fix it to get a proper count?
Thank you!
Your count dependents from right query, maybe you have duplicate row?
example
table1
id name value
1 2 3
table2
id name value
1 4 5
2 6 3
1 6 3
right join tables on value get result
select * from table1 a right join table2 b on a.value = b.value
1 2 3 2 6 3
1 2 3 1 6 3
select count(distinct a.value)
from (select a.id, a.name, a.value, b.id, b.name, b.value
from table1 a right join table2 b on a.value = b.value)
result is 1
select b.id, count(distinct a.value)
from (select a.id, a.name, a.value, b.id, b.name, b.value
from table1 a right join table2 b on a.value = b.value group)
group by b.id
result is two rows
2 1
1 1
My guess is that, you have a problem for this reason.

Nested Oracle SQL - Multiple Values

I have a table structure like:
Table = contact
Name Emailaddress ID
Bill bill#abc.com 1
James james#abc.com 2
Gill gill#abc.com 3
Table = contactrole
ContactID Role
1 11
1 12
1 13
2 11
2 12
3 12
I want to select the Name and Email address from the first table where the person has Role 12 but not 11 or 13. In this example it should return only Gill.
I believe I need a nested SELECT but having difficulty in doing this. I did the below but obviously it isn't working and returning everything.
SELECT c.Name, c.Emailaddress FROM contact c
WHERE (SELECT count(*) FROM contactrole cr
c.ID = cr.ContactID
AND cr.Role NOT IN (11, 13)
AND cr.Role IN (12)) > 0
You can use a combination of EXISTS and NOT EXISTS
SELECT *
FROM contact c
WHERE
EXISTS(SELECT 1 FROM contactrole cr WHERE cr.ContactID = c.ID AND cr.Role = 12)
AND NOT EXISTS(SELECT 1 FROM contactrole cr WHERE cr.ContactID = c.ID AND cr.Role IN(11, 13))
Another option is to use GROUP BY and HAVING:
SELECT c.*
FROM contact c
INNER JOIN contactrole cr
ON cr.ContactID = c.ID
GROUP BY
c.ID, c.Name, c.Emailaddress
HAVING
SUM(CASE WHEN cr.Role = 12 THEN 1 ELSE 0 END) > 0
AND SUM(CASE WHEN cr.Role IN(11, 13) THEN 1 ELSE 0 END) = 0
Use conditional aggregation in Having clause to filter the records
Try this
SELECT c.NAME,
c.emailaddress
FROM contact c
WHERE id IN (SELECT contactid
FROM contactrole
GROUP BY contactid
HAVING Count(CASE WHEN role = 12 THEN 1 END) > 1
AND Count(CASE WHEN role in (11,13) THEN 1 END) = 0)
If you have only 11,12,13 in role then use can use this
SELECT c.NAME,
c.emailaddress
FROM contact c
WHERE id IN (SELECT contactid
FROM contactrole
GROUP BY contactid
HAVING Count(CASE WHEN role = 12 THEN 1 END) = count(*)
You can do this using JOINs:
SELECT c.*
FROM CONTACT c
INNER JOIN CONTACTROLE cr12
ON cr12.CONTACTID = c.ID AND
cr12.ROLE = 12
LEFT OUTER JOIN CONTACTROLE cr11
ON cr11.CONTACTID = c.ID AND
cr11.ROLE = 11
LEFT OUTER JOIN CONTRACTROLE cr13
ON cr13.CONTACTID = c.ID AND
cr13.ROLE = 13
WHERE cr11.ROLE IS NULL AND
cr13.ROLE IS NULL
The INNER JOIN CONTACTROLE cr12 requires that role 12 exist for the given contact ID; the LEFT OUTER JOIN CONTACTROLE cr11 and LEFT OUTER JOIN CONTRACTROLE cr13 check to see if roles 11 and 13 might exist for the given contact ID; and the WHERE clause verifies that neither roles 11 or 13 exist.
Best of luck.