T-SQL Removing multiple LEFT JOIN - sql

I have such query. It returns ColA and ColB from TableA and UserName from table Users. Then it displays several fields from TableB as additional columns to results. It works but is there any better way than using these multiple LEFT JOINS ?
SELECT a.COlA, a.ColB, u.UserName,
b1.Value,
b2.Value,
b3.Value,
b4.Value,
FROM TableA a JOIN Users u ON a.UserId = u.UserId
LEFT JOIN TableB b1 ON a.EventId = b1.EventId AND b1.Code = 5
LEFT JOIN TableB b2 ON a.EventId = b2.EventId AND b2.Code = 15
LEFT JOIN TableB b3 ON a.EventId = b3.EventId AND b3.Code = 18
LEFT JOIN TableB b4 ON a.EventId = b4.EventId AND b4.Code = 40
WHERE (a.UserId = 3) ORDER BY u.UserName ASC
TableB looks like:
Id | EventId | Code | Value
----------------------------
1 | 1 | 5 | textA
2 | 1 | 15 | textB
3 | 1 | 18 | textC
Sometimes Code is missing but for each event there are no duplicated Codes (so each LEFT JOIN is just another cell in the same result record).

I cannot understand why you want to change something that is working, but here's another way (which does those LEFT joins, but in a different way):
SELECT a.COlA, a.ColB, u.UserName,
( SELECT b.Value FROM TableB b WHERE a.EventId = b.EventId AND b.Code = 5 ),
( SELECT b.Value FROM TableB b WHERE a.EventId = b.EventId AND b.Code = 15 ),
( SELECT b.Value FROM TableB b WHERE a.EventId = b.EventId AND b.Code = 18 ),
( SELECT b.Value FROM TableB b WHERE a.EventId = b.EventId AND b.Code = 40 )
FROM TableA a JOIN Users u ON a.UserId = u.UserId
WHERE (a.UserId = 3)
ORDER BY u.UserName ASC

SELECT
a.COlA, a.ColB, u.UserName
,MAX(CASE WHEN b.Value = 5 THEN b.value ELSE 0 END) AS V5
,MAX(CASE WHEN b.Value = 15 THEN b.value ELSE 0 END) AS V15
,MAX(CASE WHEN b.Value = 18 THEN b.value ELSE 0 END) AS V18
,MAX(CASE WHEN b.Value = 40 THEN b.value ELSE 0 END) AS V45
,COUNT(CASE WHEN b.Value not IN (5,15,18,40) THEN 1 ELSE NULL END) AS CountVOther
FROM TableA a
INNER JOIN Users u ON a.UserId = u.UserId
LEFT JOIN TableB b ON (a.EventId = b.EventId)
WHERE (a.UserId = 3)
GROUP BY a.colA, a.colB, u.Username
ORDER BY u.UserName ASC

Related

How to manage COUNT, GROUP BY and HAVING?

I'm not experimented in SQL and I try maybe to do something which is impossible. SQL give this sensation it is possible to do it on only 1 request, but maybe not...
I have 4 joined tables A -> S -> T -> F.
A simple request give me these data :
select a.id, s.id, t.type, f.name
from table_a a
inner join table_s s on a.id = s.id
inner join table_t t on t.id = s.t_id
inner join table_f f on f.id = t.f_id
where f.name = 'C';
a.id | s.id | t.type | f.name
-----------------------------
1 | 1 | E | C
1 | 2 | R | C
2 | 3 | E | C
3 | 4 | R | C
I would like to find ALL A ids which have multiple S rows associated.
And I would like to find ALL a ids which have only one S row of T type = R.
For the first one, I made this SQL query :
select a.id from table_a a
inner join table_s s on a.id = s.id
inner join table_t t on t.id = s.t_id
inner join table_f f on f.id = t.f_id
where f.name = 'C'
group by a.id
having count(s.*) > 1;
But now, for the second query I don't understand how to filter on t.type and count.
I try this request but the response is not good (a.id = 1 is returned)
select a.id from table_a a
inner join table_s s on a.id = s.id
inner join table_t t on t.id = s.t_id
inner join table_f f on f.id = t.f_id
where f.name = 'C'
group by a.id
having count(s.*) = 1 and t.type = 'R';
Any idea ?
Thank you
I would like to find ALL a ids which have only one S row of T type = R.
Does this do what you want?
select a.id from table_a a
inner join table_s s on a.id = s.id
inner join table_t t on t.id = s.t_id
inner join table_f f on f.id = t.f_id
where f.name = 'C'
group by a.id
having count(*) = 1 and min(t.type) = 'R'
This gives you groups that have only one row, whose type is "R".

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.

Conditional Left Join SQL

table A
----------------------------
NAME | CODE | BRANCH
----------------------------
bob | PL | B
david | AA | B
susan | PL | C
joe | AB | C
alfred | PL | B
table B
----------------------------
CODE | DESCRIPTION
----------------------------
PL | code 1
PB | code 2
PC | code 3
table C
----------------------------
CODE | DESCRIPTION
----------------------------
AA | code 4
AB | code 5
AC | code 6
Is there any way to join table A, B and C. without join all the table?
select A.*, COALESCE(B.DESCRIPTION, C.DESCRIPTION) AS DESCRIPTION from A
left join B on A.CODE = B.CODE
left join C on A.CODE = C.CODE
In my real case there will be more than 10 to join with the same column.
So I need conditional left join, something like this
SELECT A* , DESCRIPTION
FROM A LEFT JOIN (
CASE
WHEN A.CODE = 'B' THEN SELECT * FROM B
WHEN A.CODE = 'C' THEN SELECT * FROM C
END
) BC ON A.CODE = BC.CODE
You cannot use CASE to implement flow control. In SQL CASE is an expression that returns a single value.
You can instead use the following query:
select A.*,
CASE A.BRANCH
WHEN 'B' THEN B.DESCRIPTION
WHEN 'C' THEN C.DESCRIPTION
END AS DESCRIPTION
from A
left join B on A.CODE = B.CODE AND A.BRANCH = 'B'
left join C on A.CODE = C.CODE AND A.BRANCH = 'C'
You could use this to generate queries. Then you write a PL/SQL block to loop through all these queries and execute dynamically to give you separate results.
SELECT 'SELECT A.* , DESCRIPTION
FROM TABLEA A LEFT JOIN '
|| CASE WHEN A.BRANCH = 'B' THEN 'TABLEB B' END
|| CASE WHEN A.BRANCH = 'C' THEN 'TABLEC C' END
|| ' ON '
|| 'A.CODE = '
|| CASE WHEN A.BRANCH = 'B' THEN 'B.CODE' END
|| CASE WHEN A.BRANCH = 'C' THEN 'C.CODE' END
v_query
FROM TableA A;
Output
V_QUERY
--------------------------------------------------------------------------------
SELECT A.* , DESCRIPTION
FROM TABLEA A LEFT JOIN TABLEB B ON A.CODE = B.CODE
SELECT A.* , DESCRIPTION
FROM TABLEA A LEFT JOIN TABLEB B ON A.CODE = B.CODE
SELECT A.* , DESCRIPTION
FROM TABLEA A LEFT JOIN TABLEC C ON A.CODE = C.CODE
SELECT A.* , DESCRIPTION
FROM TABLEA A LEFT JOIN TABLEC C ON A.CODE = C.CODE
SELECT A.* , DESCRIPTION
FROM TABLEA A LEFT JOIN TABLEB B ON A.CODE = B.CODE

Flattened SQL query

This is in SQL Server 2014.
Without changing the table structures what is the easiest way you can suggest to change this query (output shown below)
select
a.Id, a.Sku, a.lbl,
Desc,
(select b.Valu where b.Attribute = 'rel') as 'rel',
(select b.Valu where b.Attribute = 'uom') as 'uom',
(select b.Valu where b.Attribute = 'clas') as 'clas'
from
items a
join
itemattributes b on b.id = a.id
Output:
id sku lbl desc rel uom clas
2 X111 X111-456789 red NULL NULL C
2 X111 X111-456789 red NULL Cs NULL
2 X111 X111-456789 red 3 NULL NULL
3 X222 X222-567890 white NULL NULL B
3 X222 X222-567890 white NULL Cs NULL
3 X222 X222-567890 white 2 NULL NULL
4 X333 X333-678901 blue NULL NULL C
4 X333 X333-678901 blue NULL Ea NULL
4 X333 X333-678901 blue 9 NULL NULL
To this output:
id sku lbl desc rel uom clas
2 X111 X111-456789 red 3 Cs C
3 X222 X222-567890 white 2 Cs B
4 X333 X333-678901 blue 9 Ea C
You can use conditional aggregation to group by different attribute values.
select a.Id
, a.Sku
, a.lbl
, [Desc]
, max(case when b.Attribute = 'rel' then b.Valu end) as rel
, max(case when b.Attribute = 'uom' then b.Valu end) as uom
, max(case when b.Attribute = 'clas' then b.Valu end) as clas
from items a
join itemattributes b
on b.id = a.id
group by a.Id
, a.Sku
, a.lbl
, [Desc]
You could try:
select a.Id
, a.Sku
, a.lbl
, Desc
, rel.Valu as 'rel'
, uom.Valu as 'uom'
, clas.Valu as 'clas'
from items a
join itemattributes rel
on rel.id = a.id and rel.Attribute = 'rel'
join itemattributes uom
on uom.id = a.id and uom.Attribute = 'uom'
join itemattributes clas
on clas.id = a.id and clas.Attribute = 'clas'
This assumes you will only want records that have all three values. If this is not a true assumption, try left joins. Note I put the attribute in the join to make it easier if you have to use a left join. if you use inner joins then you could put those in the where clause.
You could do multiple joins:
select a.Id
, a.Sku
, a.lbl
, Desc
, b.Valu as 'rel'
, c.Valu as 'uom'
, d.Valu as 'clas'
from items a
join itemattributes b on b.id = a.id
join itemattributes c on c.id = a.id
join itemattributes d on d.id = a.id
where b.Attribute = 'rel'
and c.Attribute = 'uom'
and d.Attribute = 'clas'
You should also be able to eliminate the joins altogether:
select a.Id
, a.Sku
, a.lbl
, Desc
, (select b.Valu from itemattributes b where b.id = a.id and b.Attribute = 'rel') as 'rel'
, (select b.Valu from itemattributes b where b.id = a.id and b.Attribute = 'uom') as 'uom'
, (select b.Valu from itemattributes b where b.id = a.id and b.Attribute = 'clas') as 'clas'
from items a

Optimizing SQL Server view

I am trying to optimize a big query that contains about 10 subqueries on a table with 30+ columns and over 2 million records.
I would like to reduce the amount of selects on this massive table but I don't really know how I could optimize following query to prevent this.
I would like to use some kind of filter on a so that I can just write down a WHERE clause in the column subquery instead of querying tableA again, but I have no clue on how I could do this:
SELECT
col1, col2,
(SELECT COUNT(col3)
FROM tableA ta
INNER JOIN tableB b ON tl.TaskId = b.col6
INNER JOIN tableC c ON b.Id = c.TaskId
WHERE c.ResultCode = 1
AND ta.col4 = a.col4
AND ta.col5 = a.col5) as Executed,
(SELECT COUNT(col3)
FROM tableA ta
INNER JOIN tableB b ON tl.TaskId = b.col6
INNER JOIN tableC c ON b.Id = c.TaskId
WHERE c.ResultCode = 9
AND ta.col4 = a.col4
AND ta.col5 = a.col5) as NotExecuted
FROM
tableA a
GROUP BY
col1, col2, col4, col5
I have few questions about your query (asked in comment), however it is might what you are looking for:
SELECT
col1 ,
col2 ,
COUNT(CASE WHEN c.ResultCode = 1 THEN 1
ELSE NULL
END) AS Executed ,
COUNT(CASE WHEN c.ResultCode = 9 THEN 1
ELSE NULL
END) AS NotExecuted
FROM
tableA a
JOIN tableB b ON a.id = b.tableA_id
JOIN tableC c ON a.id = c.tableB_id
WHERE
c.ResultCode IN ( 1, 9 )
GROUP BY
col1 ,
col2;
If you do not need it in separate columns, then you can aggregate it in separate rows. That would probably work faster:
SELECT
col1 ,
col2 ,
CASE c.ResultCode
WHEN 1 THEN 'Executed'
WHEN 9 THEN 'Not Executed'
END,
COUNT(*)
FROM
tableA a
JOIN tableB b ON a.id = b.tableA_id
JOIN tableC c ON a.id = c.tableB_id
WHERE
c.ResultCode IN ( 1, 9 )
GROUP BY
col1 ,
col2 ,
c.ResultCode;
SELECT
col1, col2, sum(case when c.ResultCode = 1 THEN 1 ELSE 0 END) as Executed,
sum(case when c.ResultCode = 9 THEN 1 ELSE 0 END) as NotExecuted
FROM tableA a
INNER JOIN tableB b ON a.TaskId = b.col6
INNER JOIN tableC c ON b.Id = c.TaskId
GROUP BY col1, col2