How to display a ROW based on value in CASE STATEMENT - sql

I have a query as below, and want to display a row only if the value is 1 using CASE. Please can you advice how I can do that
SELECT DISTINCT
a.AccountID,
a.ForeName,
a.Surname,
a.Gender,
CASE
WHEN B.Value = '1145' THEN '1'
WHEN B.Value = '1007' THEN '2' ELSE '0'
END AS Value,
b.Address,
b.Town
FROM
Customer a
LEFT OUTER JOIN
AdditionalDetails b
ON
b.ID = a.AccountID
The result I am getting:
AccountID ForeName Surname Gender NoName Address Town
00012 Eric Manse Male 0 Porto Porto
00013 Peter Mark Male 0 Porto Porto
00014 Tom Jerry Male 0 Porto Porto
00014 Tom Jerry Male 1 Porto Porto
00015 Sarah Parker Female 0 Porto Porto
00015 Sarah Parker Female 1 Porto Porto
If there is a 1 in the CASE statement, it should not display the 0 just the row with the value 1

I speculate that you want either MAX() or MIN():
SELECT c.AccountID, c.ForeName, c.Surname, c.Gender,
MAX(CASE WHEN ad.Value = '1145' THEN '1'
WHEN ad.Value = '1007' THEN '2'
ELSE '0'
END),
ad.Address, ad.Town
FROM Customer c LEFT OUTER JOIN
AdditionalDetails ad
ON c.ID = ad.ID
GROUP BY c.AccountID, c.ForeName, c.Surname, c.Gender, ad.Address, ad.Town;
EDIT:
You seem to want prioritization:
SELECT cad.*
FROM (SELECT c.AccountID, c.ForeName, c.Surname, c.Gender,
ad.Address, ad.Town,
ROW_NUMBER() OVER (PARTITION BY c.ACCOUNTID
ORDER BY (CASE WHEN ad.Value = '1145' THEN 1
WHEN ad.Value = '1007' THEN 2
ELSE 0'
END) DESC
) as seqnum
FROM Customer c LEFT OUTER JOIN
AdditionalDetails ad
ON c.ID = ad.ID
) cad
WHERE seqnum = 1;

You have altered your question. You are not looking for distinct rows, but you want to rank the rows and only display best matches.
Depending on your exact requirements you'd use RANK or ROW_NUMBER with an appropriate ORDER BY and PARTITION BY clause for this.
For instance:
select c.*, ad.address, ad.town
from customer c
left join
(
select
address,
town,
customer_id,
rank() over (partition by customer_id
order by case value when 1145 then 1 when 1007 then 2 else 0 end desc) as rnk
from additionaldetails
) ad on ad.customer_id = c.id and d.rnk = 1;

you could try like below
with cte as (
SELECT a.AccountID, a.ForeName, a.Surname, a.Gender,
b.Address, b.Town,
row_number() over(partition by a.AccountID
order by
(CASE WHEN b.Value = '1145' THEN 1 WHEN b.Value = '1007' THEN 2 ELSE 0
END) desc) as val
FROM Customer a
LEFT OUTER JOIN AdditionalDetails b
ON b.ID = a.AccountID
) select * from cte where val=1

Related

SQL get record when finding in 2 records

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.

How to limit my results to only the rows I need?

This is the code I have currently that pulls the top 2 contacts but because every company has more than 2 contacts when I bring in the USERID into picture (that is required) it gives me rows with all Userids for a company although they are not my top 2 Contacts.
select companyid,
userid,
(case when seqnum = 1 then username end) as Contact1,
(case when seqnum = 2 then username end) as Contact2,
from (
select *, row_number() over (partition by companyid order by username) as
seqnum from
( SELECT b.userid, username, a.companyid from [UsersInCompanies] a
JOIN [Companies] c on a.companyid = c.companyid
join [aspnet_Users] b on a.userid = b.userid ) t ) l
Result set I'm getting
CompanyID Userid Contact1 Contact2
1 xyz-78 Jane Doe1 NULL
1 uik-90 NULL JD2
1 jkl-70 NULL NULL
1 abc-60 NULL NULL
Desired result
CompanyID Userid Contact1 Contact2
1 xyz-78 JaneDoe1 NULL
1 uik-90 NULL JaneDoe2
Should I be using some sort of COUNT & TOP functions ?
You need filtration (i.e. seqnum <= 2) , but i would rewrite it as :
with t as (
SELECT b.userid, username, a.companyid,
ROW_NUMBER() OVER (PARTITION BY a.companyid order by b.username) as seqnum
FROM [UsersInCompanies] a INNER JOIN
[Companies] c
ON a.companyid = c.companyid INNER JOIN
[aspnet_Users] b
ON a.userid = b.userid
)
select companyid, userid,
(case when seqnum = 1 then username end) as Contact1,
(case when seqnum = 2 then username end) as Contact2
from t
where seqnum <= 2;
I think this is cleaner
with cte as
(
SELECT b.userid, username, a.companyid,
ROW_NUMBER() OVER (PARTITION BY a.companyid order by b.username) as rn
FROM [UsersInCompanies] a
JOIN [Companies] c
ON a.companyid = c.companyid
JOIN [aspnet_Users] b
ON a.userid = b.userid
)
select ct1.*, cte2.username
from cte as cte1
join cte as cte2
on cte1.companyid = cte2.companyid
and cte1.rn = 1
and cte2.rn = 2

Optimizing SQL query having DISTINCT keyword and functions

I have this query that generates about 40,000 records and the execution time of this query is about 1 minute 30 seconds.
SELECT DISTINCT
a.ID,
a.NAME,
a.DIV,
a.UID,
(select NAME from EMPLOYEE where UID= a.UID and UID<>'') as boss_id,
(select DATE(MAX(create_time)) from XYZ where XYZ_ID= 1 and id = a.ID) as TERM1,
(select DATE(MAX(create_time)) from XYZ where XYZ_ID= 2 and id = a.ID) as TERM2,
(select DATE(MAX(create_time)) from XYZ where XYZ_ID= 3 and id = a.ID) as TERM3,
(select DATE(MAX(create_time)) from XYZ where XYZ_ID= 4 and id = a.ID) as TERM4,
(select DATE(MAX(create_time)) from XYZ where XYZ_ID= 5 and id = a.ID) as TERM5,
(select DATE(MAX(create_time)) from XYZ where XYZ_ID= 6 and id = a.ID) as TERM6,
(select DATE(MAX(create_time)) from XYZ where XYZ_ID= 7 and id = a.ID) as TERM7,
(select DATE(MAX(create_time)) from XYZ where XYZ_ID= 8 and id = a.ID) as TERM8
FROM EMPLOYEE a
WHERE ID LIKE 'D%'
I tried using group by, different kinds of join to improve the execution time but couldn't succeed.Both the tables ABC and XYZ are indexed.
Also, I think that the root cause of this problem is either the DISTINCT keyword or the MAX function.
How can I optimize the above query to bring down the execution time to at least less than a minute?
Any help is appreciated.
Query is not tested, this is just an idea on how you could get this done in two different ways.
(SQL Server solutions here)
Using LEFT JOIN for each ID should look something like this:
SELECT a.ID,
a.NAME,
a.DIV,
a.UID,
b.Name as boss_id,
MAX(xyz1.create_time) as TERM1,
MAX(xyz2.create_time) as TERM2,
MAX(xyz3.create_time) as TERM3,
MAX(xyz4.create_time) as TERM4,
MAX(xyz5.create_time) as TERM5,
MAX(xyz6.create_time) as TERM6,
MAX(xyz7.create_time) as TERM7,
MAX(xyz8.create_time) as TERM8
FROM EMPLOYEE a
JOIN EMPLOYEE b on a.UID = b.UID and b.UID <> ''
LEFT JOIN XYZ xyz1 on a.ID = xyz1.ID and xyz1.XYZ_ID = 1
LEFT JOIN XYZ xyz2 on a.ID = xyz2.ID and xyz1.XYZ_ID = 2
LEFT JOIN XYZ xyz3 on a.ID = xyz3.ID and xyz1.XYZ_ID = 3
LEFT JOIN XYZ xyz4 on a.ID = xyz4.ID and xyz1.XYZ_ID = 4
LEFT JOIN XYZ xyz5 on a.ID = xyz5.ID and xyz1.XYZ_ID = 5
LEFT JOIN XYZ xyz6 on a.ID = xyz6.ID and xyz1.XYZ_ID = 6
LEFT JOIN XYZ xyz7 on a.ID = xyz7.ID and xyz1.XYZ_ID = 7
LEFT JOIN XYZ xyz8 on a.ID = xyz8.ID and xyz1.XYZ_ID = 8
WHERE a.ID LIKE 'D%'
GROUP BY a.ID, a.NAME, a.DIV, a.UID, b.Name
Using PIVOT would look something like this:
select * from (
SELECT DISTINCT
a.ID,
a.NAME,
a.DIV,
a.UID,
b.NAME as boss_id,
xyz.xyz_id,
xyz.create_time
FROM EMPLOYEE a
JOIN EMPLOYEE b on a.UID = b.UID and b.UID <> ''
LEFT JOIN (SELECT DATE(MAX(create_time)) create_time, XYZ_ID, ID
from XYZ
where XYZ_ID between 1 and 8
group by XYZ_ID, ID) xyz on a.ID = xyz1.ID
WHERE a.ID LIKE 'D%') src
PIVOT (
max(create_time) for xyz_id IN (['1'], ['2'], ['3'], ['4'],
['5'], ['6'], ['7'], ['8'])
) PIV
Give it a shot
I would recommend group by and conditional aggregation:
SELECT e.ID, e.NAME, e.DIV, e.UID,
DATE(MAX(CASE WHEN XYZ_ID = 1 THEN create_time END)) as term1,
DATE(MAX(CASE WHEN XYZ_ID = 2 THEN create_time END)) as term2,
DATE(MAX(CASE WHEN XYZ_ID = 3 THEN create_time END)) as term3,
DATE(MAX(CASE WHEN XYZ_ID = 4 THEN create_time END)) as term4,
DATE(MAX(CASE WHEN XYZ_ID = 5 THEN create_time END)) as term5,
DATE(MAX(CASE WHEN XYZ_ID = 6 THEN create_time END)) as term6,
DATE(MAX(CASE WHEN XYZ_ID = 7 THEN create_time END)) as term7,
DATE(MAX(CASE WHEN XYZ_ID = 8 THEN create_time END)) as term8
FROM EMPLOYEE e LEFT JOIN
XYZ
ON xyz.ID = e.id
WHERE e.ID LIKE 'D%'
GROUP BY e.ID, e.NAME, e.DIV, e.UID;
I don't understand the logic for boss_id, so I left that out. This should improve the performance significantly.

SQL group table by user with custom column on same row

I have 3 tables
PATIENT:
patientid name comment result
--------------------------------
1 davis test p
2 brown test p
3 mike test p
PHONE TYPE:
phone_id patient_id phone_type
-------------------------------
3324 1 1
3325 1 4
5467 2 1
PHONE DETAILS:
phone_id number
-----------------------
3324 8253322
3325 180040204
5467 5674543
The goal is to add a column on the select statement based on the value of phone type (1 = phone, 4 = fax) and to group the column so that the phone and
fax are on the same line, not alternating.
Desired result:
name phone fax comment result
---------------------------------------------
davis 8253322 180040204 test p
brown 5674543 null test p
mike null null test p
I have this so far
select
a.name, a.comment, a.result,
(CASE WHEN (b.phone_type = 1) THEN c.number ELSE NULL END) AS Phone
(CASE WHEN (b.phone_type = 4) THEN c.number ELSE NULL END) AS Fax
The problem with this is that it doesn't group the values by the user.
name phone fax comment result
-----------------------------------------------
davis 8253322 null test p
davis null 180040204 test p
mike null null test p
If we can assume 1 phone/fax per name.
select a.name
, a.comment
, a.result
, max(CASE WHEN (b.phone_type = 1) THEN c.number ELSE NULL END) AS Phone
, max((CASE WHEN (b.phone_type = 4) THEN c.number ELSE NULL END) AS Fax
FROM...
GROUP BY a.name
, a.comment
, a.result
If not... then break phone and fax into two data sets based the type and generate row number for each set. Then use a full outer join on patentId and that row number.
With Phone as (SELECT phone_id, patient_id, phone_type, PD.Number, row_number() over (partition by Patient_ID order by PD.Number) as RN
FROM Phone_type PT
INNER JOIN Phone_Details PD
ON PT.Phone_ID = PD.Phone_ID
WHERE Phone_Type = 1),
Fax as (SELECT phone_id, patient_id, phone_type, PD.Number, row_number() over (partition by Patient_ID order by PD.Number) as RN
FROM Phone_type PT
INNER JOIN Phone_Details PD
ON PT.Phone_ID = PD.Phone_ID
WHERE Phone_Type = 1)
SELECT *
FROM PHONE P
FULL OUTER JOIN FAX F
on P.Patent_Id =F.PatentID
and P.RN=F.RN
You can use an aggregation function if you are sure you will only ever have one value at most for phone or fax.
select a.name, a.comment, a.result,
Min((CASE WHEN (b.phone_type = 1) THEN c.number ELSE NULL END) AS Phone,
Min((CASE WHEN (b.phone_type = 4) THEN c.number ELSE NULL END)) AS Fax
FROM (...)
GROUP BY a.name, a.comment, a.result
You can use the following SQL statement
WITH patient_phone AS (
SELECT a.name,
a.comment,
a.result,
(CASE WHEN (b.phone_type = 1) THEN c.number ELSE NULL END) AS Phone,
(CASE WHEN (b.phone_type = 4) THEN c.number ELSE NULL END) AS Fax
FROM patient a
LEFT OUTER JOIN phone_type b
ON a.patient_id = b.patient_id
LEFT OUTER JOIN phone_details c
ON b.phone_id = c.phone_id
GROUP BY a.name,
a.comment,
a.result,
b.phone_type,
c.number
)
SELECT name,
MAX(phone) phone,
MAX(fax) fax,
comment,
result
FROM patient_phone
GROUP BY name,
comment,
result
Result
name phone fax comment result
brown 5674543 NULL test p
davis 8253322 180040204 test p
mike NULL NULL test p

SQL condition sum from two joined tables

I have two tables as below:
Invoice
InvId | Amount | Name
-----------------------
1 | 50 | John
2 | 30 | Mike
3 | 20 | John
Detail
MetalType| Weight | InvId
-------------------------
Gold | 2 | 2
Silver | 4 | 3
Silver | 3 | 3
Gold | 5 | 1
I would like to have the following output, but my query will only provide the total for silver and gold for John. How can I build a query that will also include the total invoice amount for John.
Total Invoice Amount For John = 70
Total Silver Weight = 7
total Gold Weith = 5
SELECT
SUM(IFF(D.MetalType=”Gold”, D.Weight, 0)) AS TotGold,
SUM((IFF(D.MetalType=”Silver”, D.Weight, 0)) AS TotSilver
FROM Invoice I INNER JOIN Detail D ON I.InvId = D.InvId WHERE I.Name = “John”
Try this:
For Sql-Server:
SELECT
SUM(TotalAmount) AS TotalAmount,
SUM(TotGold) AS TotGold,
SUM(TotSilver) AS TotSilver
FROM(
SELECT
SUM (I.Amount) OVER (Partition by D.Invid) AS TotalAmount,
SUM(CASE WHEN D.MetalType='Gold' THEN D.Weight ELSE 0 END) AS TotGold,
SUM(CASE WHEN D.MetalType='Silver' THEN D.Weight ELSE 0 END) AS TotSilver
FROM Invoice I INNER JOIN Detail D ON I.InvId = D.InvId
WHERE I.Name = 'John'
GROUP BY D.InvId, I.Amount) n
Here is an SQL Fiddle - now it kills the duplicate detail and counts it only once.
EDITED for Access:
SELECT
n.Name,
MAX(TotalAmount),
SUM(TotGold) AS TotGold,
SUM(TotSilver) AS TotSilver
FROM(
SELECT
I.Name,
SUM(CASE WHEN D.MetalType='Gold' THEN D.Weight ELSE 0 END) AS TotGold,
SUM(CASE WHEN D.MetalType='Silver' THEN D.Weight ELSE 0 END) AS TotSilver
FROM Invoice I
INNER JOIN Detail D ON I.InvId = D.InvId
GROUP BY I.Name, D.InvId, I.Amount) n
INNER JOIN (
SELECT
I.Name, SUM (I.Amount) AS TotalAmount
FROM Invoice I
GROUP BY I.Name) m ON m.Name = n.Name
GROUP BY n.Name
Try with this:
With tbl3 (Amt,Gold,Silver)
as
(
SELECT
SUM (I.Amount) OVER (Partition by D.Invid) AS TotalAmount,
SUM(CASE WHEN D.MetalType='Gold' THEN D.Weight ELSE 0 END) AS TotGold,
SUM(CASE WHEN D.MetalType='Silver' THEN D.Weight ELSE 0 END) AS TotSilver
FROM Invoice I Right JOIN Detail D ON I.InvId = D.InvId
WHERE I.Name = 'John' Group by D.InvId, I.Amount
)
Select SUM(Amt) as Total_Invoice_Amount_For_John,
SUM(Gold) as Total_Silver_Weight,
SUM(Silver) as Total_Gold_Width from tbl3
SQL Fiddle
I havent tried out the other queries already posted and they might already be suitable for what you want but here's my take on it :-
SELECT X.NAME, X.METALTYPE, X.WEIGHT, Y.TOTAL
FROM
(SELECT NAME, METALTYPE, SUM(Weight) AS WEIGHT
FROM INVOICE i
INNER JOIN DETAIL d ON i.InvId = d.InvId
GROUP BY NAME, METALTYPE) X
INNER JOIN
(SELECT SUM(AMOUNT) AS Total, NAME
FROM INVOICE
GROUP BY NAME)Y
ON X.NAME = Y.NAME
ORDER BY NAME, TOTAL, METALTYPE
select name, sum(Amount) as 'total invoice',sum(Gold) as 'Gold',sum(Silver) as Silver from(
select aa.Name,aa.Amount,
sum(case when bb.MetalType='Gold' then bb.Weight else 0 end) as 'Gold',
sum(case when bb.MetalType='Silver' then bb.Weight else 0 end) as 'Silver'
from a aa left outer join b bb on aa.InvID=bb.InvID group by aa.InvID) as c group by c.name