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

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

Related

How to display a ROW based on value in CASE STATEMENT

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

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 server merge rows and get latest value

I have a table like this,
Id A B C D touchedwhen
1 NULL yes NULL yes 2015-02-26 14:10:01.870
1 NULL NULL no no 2015-02-26 14:10:40.370
and need to merge them in to one row like this,
Id A B C D touchedwhen
1 NULL yes no no 2015-02-26 14:10:40.370.
Note : if value is present in both rows take the latest one by date..
Tried this query:
select id,
max(a),
max(b),
max(c),
max(d), -- data in both rows hence take the latest
max(touchedwhen)
from
[dbo].[Table_1]
group by id;
If you were just looking for the last value per id, you could use:
last_value(a) over(
partition by id
order by touchedwhen desc
rows between unbounded preceding and unbounded following) as a
But you're looking for the last value per id that is not null. The only thing I can come up with is a four-part join, with each subquery calculating the latest non-null value for a, b, c, d:
select ids.id
, a.a
, b.b
, c.c
, d.d
, ids.tw
from (
select id
, max(touchedwhen) as tw
from YourTable
group by
id
) ids
left join
(
select row_number() over (
partition by id
order by touchedwhen desc) rn
, a
, id
from YourTable
where a is not null
) a
on a.id = ids.id
and a.rn = 1
left join
(
select row_number() over (
partition by id
order by touchedwhen desc) rn
, b
, id
from YourTable
where b is not null
) b
on b.id = ids.id
and b.rn = 1
left join
(
select row_number() over (
partition by id
order by touchedwhen desc) rn
, c
, id
from YourTable
where c is not null
) c
on c.id = ids.id
and c.rn = 1
left join
(
select row_number() over (
partition by id
order by touchedwhen desc) rn
, d
, id
from YourTable
where d is not null
) d
on d.id = ids.id
and d.rn = 1
-- Get all latest values in one row for each primary key
WITH CTE(row_num, Id, A, B, C, D, touchedwhen) AS (
SELECT ROW_NUMBER() OVER(PARTITION BY Id ORDER BY touchedwhen DESC), Id, A, B, C, D FROM Table_1)
UPDATE CTE SET
A = (SELECT TOP 1 t.A FROM Table_1 t WHERE t.A IS NOT NULL AND t.Id = CTE.Id ORDER BY t.touchedwhen DESC),
B = (SELECT TOP 1 t.B FROM Table_1 t WHERE t.B IS NOT NULL AND t.Id = CTE.Id ORDER BY t.touchedwhen DESC),
C = (SELECT TOP 1 t.C FROM Table_1 t WHERE t.C IS NOT NULL AND t.Id = CTE.Id ORDER BY t.touchedwhen DESC),
D = (SELECT TOP 1 t.D FROM Table_1 t WHERE t.D IS NOT NULL AND t.Id = CTE.Id ORDER BY t.touchedwhen DESC)
WHERE row_num = 1
-- Delete extra rows per primary key after copying latest values to one row
WITH CTE(row_num) AS (
SELECT ROW_NUMBER() OVER(PARTITION BY Id ORDER BY touchedwhen DESC) FROM Table_1)
DELETE FROM CTE WHERE row_num > 1

Is it possible to JOIN a table on the TOP 1 if there is no unique identifier?

For Example
SELECT
a.SomethingInCommon,
tbl1.Status AS Status1,
tbl2.Status AS Status2,
tbl3.Status AS Status3
FROM Maintable a
LEFT OUTER JOIN SecondTable tbl1 ON
tbl1.ID = (SELECT TOP 1 ID
FROM SecondTable SomethingInCommon = a.SomethingInCommon)
LEFT OUTER JOIN SecondTable tbl2 ON
tbl2.ID = (SELECT TOP 1 ID
FROM SecondTable WHERE SomethingInCommon = a.SomethingInCommon
AND ID NOT IN (SELECT TOP 1 ID
FROM SecondTABLE
WHERE SomethingInCommon = a.SomethingInCommon))
LEFT OUTER JOIN SecondTable tbl3 ON
tbl23.ID = (SELECT TOP 1 ID
FROM SecondTable
WHERE SomethingInCommon = a.SomethingInCommon
AND ID NOT IN (SELECT TOP 2 ID
FROM SecondTABLE WHERE SomethingInCommon = a.SomethingInCommon))
This query joins SecondTable three times to show a record like
SomethingInCommon | Status1 | Status2 | Status 3
Is there anyway to accomplish these results if SecondTable does not have the unique identifier column (ID) ?
Perhaps maybe creating a temporary unique ID on the fly?
If you don't have IDs but know the order you want, you could create artificial IDs using ROW_NUMBER() and then do your TOP 1's off of that.
WITH TEMP AS (
SELECT 3 a, 1 b UNION ALL
SELECT 2, 1 UNION ALL
SELECT 1, 1 UNION ALL
SELECT 2, 2 UNION ALL
SELECT 1, 2)
SELECT A, B, ROW_NUMBER() OVER (PARTITION BY B ORDER BY B ASC) as RowNumber FROM TEMP
;WITH TEMP AS (
SELECT 3 a, 1 b UNION ALL
SELECT 2, 1 UNION ALL
SELECT 1, 1 UNION ALL
SELECT 2, 2 UNION ALL
SELECT 1, 2)
SELECT A, B, ROW_NUMBER() OVER (ORDER BY A ASC) as RowNumber FROM TEMP
As Raphael said in the comment, this can be done with CTE like below
with cte
as
(
SELECT M.SomethingInCommon, S.ID, ROW_NUMBER() OVER ( Partition by S.SomethingInCommon ORDER BY S.ID desc) as rn
FROM Maintable M
LEFT JOIN SecondTable S
on M.SomethingInCommon = S.SomethingInCommon
)
SELECT cte.SomethingInCommon
case when rn =1 then cte.ID end as Status1,
case when rn =2 then cte.ID end as Status2,
case when rn =3 then cte.ID end as Status3
where rn <=3
If you want the top three statuses, then you can use conditional aggregation:
select m.somethingincommon,
max(case when seqnum = 1 then status end) as status1,
max(case when seqnum = 2 then status end) as status2,
max(case when seqnum = 3 then status end) as status3
from maintable m left join
(select s.*,
row_number() over (partition by s.somethingincommon order by (select NULL)) as seqnum
from secondtable
) s
on m.somethingincommon = s.somethingincommon
group by m.somethingincommon;
If you prefer, you can do this with multiple joins:
with s as (
select s.*,
row_number() over (partition by s.somethingincommon order by (select NULL)) as seqnum
from secondtable
)
select m.*, s1.status as status, s2.status as status2, s3.status as status3
from maintable m left join
s s1
on m.somethingincommon = s1.somethingincommon and
s1.seqnum = 1 left join
s s2
on m.somethingincommon = s2.somethingincommon and
s2.seqnum = 2 left join
s s3
on m.somethingincommon = s3.somethingincommon and
s3.seqnum = 3;

tsql left outer join with multiple records displayed on a single line

Hi I have two table with a 1-N relationship where N is maximum 3. A Group has at least 1 user and a maximum of 3 users. I would like to display the group and all possible users on a single row with a select query.
Group :
ID Name
1 Group1
2 Group2
Users :
ID Username IDGroup
1 User1 1
2 User2 2
3 User3 1
4 User4 1
Result (Where no Username to display it's ok null or empty string) :
IDGroup GroupName Username1 Username2 Username3
1 Group1 User1 User3 User4
2 Group2 User2 Null Null
You could use Pivot.
select P.IDGroup,
P.GroupName,
P.[1] as Username1,
P.[2] as Username2,
P.[3] as Username3
from
(
select G.ID as IDGroup,
G.Name as GroupName,
U.Username,
row_number() over(partition by G.ID order by U.Username) as rn
from Groups as G
left outer join Users as U
on G.ID = U.IDGroup
) as T
pivot
(
max(T.Username) for T.rn in ([1],[2],[3])
) as P
SQL Fiddle
Update:
If there are more fields that is needed I would do it like this instead.
select T.IDGroup,
T.GroupName,
max(case when T.rn = 1 then T.Username end) as Username1,
max(case when T.rn = 1 then T.Email end) as Email1,
max(case when T.rn = 2 then T.Username end) as Username2,
max(case when T.rn = 2 then T.Email end) as Email2,
max(case when T.rn = 3 then T.Username end) as Username3,
max(case when T.rn = 3 then T.Email end) as Email3
from (
select G.ID as IDGroup,
G.Name as GroupName,
U.Username,
U.Email,
row_number() over(partition by G.ID order by U.Username) as rn
from Groups as G
left outer join Users as U
on G.ID = U.IDGroup
) as T
group by T.IDGroup,
T.GroupName
I also want to provide this answer as it is also good and in my opinion more flexible if you want to add additional fields :
select
T.IDGroup
,T.GroupName
,[1] = max(case when rn = 1 then T.Username end)
,[2] = max(case when rn = 2 then T.Username end)
,[3] = max(case when rn = 3 then T.Username end)
from
(
select G.ID as IDGroup,
G.Name as GroupName,
U.Username,
row_number() over(partition by G.ID order by U.Username) as rn
from Groups as G
left outer join Users as U
on G.ID = U.IDGroup
) as T
group by T.IDGroup, T.GroupName