MSSQL - 2 Tables with multiple rows merge in one table - sql

I have 2 selects which returns 2 tables, in each table I have 12 rows, and 2 column, now I want to have just one table.
---- First Table
select f. date as Date, f.value as Month1
from
(
select b.date as date, sum(b.value) as value
from business bu
join bus_cat buc on buc.id = bu.id
join cat ct on ct.id = buc.type_id
join bus2 b on b.id = buc.id
where ct.id = 1
and b.value <> 0
and b.date between #start and #actual
and bu.name = #bu_name
group by b.date, b.value
union all
select b5.date as date, sum(b5.value) as value
from bus bu
join bus_cat buc on buc.id = bu.id
join cat ct on ct.id = buc.type_id
join bus3 b5 on b5.id = buc.id
where ct.id = 1
and b5.value <> 0
and b5.date between #nextM and #endM
and bu.name = #bu_name
group by b5.date, b5.value
) as f
---- Second Table
select f1. date as Date, f1.value as Month2
from
(
select b.date as date, sum(b.value) as value
from business bu
join bus_cat buc on buc.id = bu.id
join cat ct on ct.id = buc.type_id
join bus2 b on b.id = buc.id
where ct.id = 1
and b.value <> 0
and b.date between #start and #actual
and bu.name = #bu_name
group by b.date, b.value
union all
select b5.date as date, sum(b5.value) as value
from bus bu
join bus_cat buc on buc.id = bu.id
join cat ct on ct.id = buc.type_id
join bus3 b5 on b5.id = buc.id
where ct.id = 1
and b5.value <> 0
and b5.date between #nextM and #endM
and bu.name = #bu_name
group by b5.date, b5.value
) as f1
The actual output for first table is:
Date Month1
2015-01-01 23
2015-01-01 77
and for second:
Date Month2
2015-01-01 88
2015-01-01 90
All I want is to merge this 2 tables to look like
Date Month1 Date Month2
2015-01-01 23 2015-01-01 77
2015-01-01 28 2015-01-01 787

As I said in my comment, a simple join should do the trick. Something like:
SELECT *
FROM
(
select b.date as date, sum(b.value) as value
from business bu
join bus_cat buc on buc.id = bu.id
join cat ct on ct.id = buc.type_id
join bus2 b on b.id = buc.id
where ct.id = 1
and b.value <> 0
and b.date between #start and #actual
and bu.name = #bu_name
group by b.date, b.value
union all
select b5.date as date, sum(b5.value) as value
from bus bu
join bus_cat buc on buc.id = bu.id
join cat ct on ct.id = buc.type_id
join bus3 b5 on b5.id = buc.id
where ct.id = 1
and b5.value <> 0
and b5.date between #nextM and #endM
and bu.name = #bu_name
group by b5.date, b5.value
) as f
LEFT JOIN (
select b.date as date, sum(b.value) as value
from business bu
join bus_cat buc on buc.id = bu.id
join cat ct on ct.id = buc.type_id
join bus2 b on b.id = buc.id
where ct.id = 1
and b.value <> 0
and b.date between #start and #actual
and bu.name = #bu_name
group by b.date, b.value
union all
select b5.date as date, sum(b5.value) as value
from bus bu
join bus_cat buc on buc.id = bu.id
join cat ct on ct.id = buc.type_id
join bus3 b5 on b5.id = buc.id
where ct.id = 1
and b5.value <> 0
and b5.date between #nextM and #endM
and bu.name = #bu_name
group by b5.date, b5.value
) as f1
ON f.date = f1.date
edit
By the way, this query can be simplified, looks like there are many similarities in the queries.

Related

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 DATEDIFF Returns

I don't want to show my whole query as it is very specific but I will try to explain briefly.
The following query works perfectly and I got 6000 records as a result.
SELECT
DISTINCT ID,
NAME,
CASE WHEN A.ID IS NULL THEN 'NOT EX.'
ELSE A.Whatever
END AS A_Whatever,
D.Z1 AS A.P
--AND SO ON......
FROM A
INNER JOIN A ON A.ID= B.ID AND A.Nb= B.Nb
LEFT JOIN T AS T2_ID ON T2_D.Z= A.Z
LEFT JOIN L1 ON A.NR = L1.NR AND A.S = L1.S
LEFT JOIN LF ON LF.NR = L1.LNR
--AND SO ON.......
However, when i add a DATEDIFF calculation I get only 100 Rows in the answer:
SELECT DISTINCT
ID,
NAME,
CASE WHEN A.ID IS NULL THEN 'NOT EX.'
ELSE A.Whatever
END AS A_Whatever,
D.Z1 AS A.P,
DATEDIFF(dd,A.ADATE,A.BDATE)
FROM A
INNER JOIN A ON A.ID= B.ID AND A.Nb= B.Nb
LEFT JOIN T AS T2_ID ON T2_D.Z= A.Z
LEFT JOIN L1 ON A.NR = L1.NR AND A.S = L1.S
LEFT JOIN LF ON LF.NR = L1.LNR
--AND SO ON.......
I am expecting 6000 rows with a correct query using DATEDIFF in line with what the following query returns:
SELECT
DISTINCT *,
DATEDIFF(dd,A.ADATE,A.BDATE)
FROM A
INNER JOIN A ON A.ID= B.ID AND A.Nb= B.Nb
LEFT JOIN T AS T2_ID ON T2_D.Z= A.Z
LEFT JOIN L1 ON A.NR = L1.NR AND A.S = L1.S
LEFT JOIN LF ON LF.NR = L1.LNR
--AND SO ON.......
But I do not need all of them I need just the selected ones and the DATEDIFF but combining the queries above did not work for some reason that i do not know. Can anyone see why i am not getting the expected row count in my second query?
You can just wrap your query in a CTE and perform the DATEDIFF on the result set returned by the CTE:
WITH DISTINCT_CTE AS (
SELECT
DISTINCT ID,
NAME,
CASE WHEN A.ID IS NULL THEN 'NOT EX.'
ELSE A.Whatever
END AS A_Whatever,
D.Z1 AS A.P
--AND SO ON......
FROM A
INNER JOIN A ON A.ID= B.ID AND A.Nb= B.Nb
LEFT JOIN T AS T2_ID ON T2_D.Z= A.Z
LEFT JOIN L1 ON A.NR = L1.NR AND A.S = L1.S
LEFT JOIN LF ON LF.NR = L1.LNR
--AND SO ON.......
)
SELECT *, DATEDIFF(dd, ADATE, BDATE)
FROM DISTINCT_CTE
You could try a subquery... first subquery the entire thing.
In the receiving query do another subquery for the DATEDIFF. For this subquery you need the primary key to get back to the correct row for the dates, which if I'm interpreting correctly is A.ID.
SELECT dT.ID, dT.Name, dT.A_Whatever
,(SELECT DATEDIFF(dd, A2.ADATE, A2.BDATE)
FROM A AS A2
WHERE dT.ID = A2.ID --the primary key
) AS [DateDiff]
--AND SO ON........
FROM (
SELECT DISTINCT ID,
NAME,
CASE WHEN A.ID IS NULL THEN 'NOT EX.'
ELSE A.Whatever
END AS A_Whatever,
D.Z1 AS A.P
--AND SO ON......
FROM A
INNER JOIN A ON A.ID= B.ID AND A.Nb= B.Nb
LEFT JOIN T AS T2_ID ON T2_D.Z= A.Z
LEFT JOIN L1 ON A.NR = L1.NR AND A.S = L1.S
LEFT JOIN LF ON LF.NR = L1.LNR
--AND SO ON.......
) AS dT
You can try the following query :
SELECT T1.*, T2.DateDiff
FROM (
SELECT DISTINCT ID,
NAME,
CASE WHEN A.ID IS NULL THEN 'NOT EX.'
ELSE A.Whatever
END AS A_Whatever,
D.Z1 AS A.P
--AND SO ON......
FROM A
INNER JOIN B ON A.ID= B.ID AND A.Nb= B.Nb
LEFT JOIN T AS T2_ID ON T2_D.Z= A.Z
LEFT JOIN L1 ON A.NR = L1.NR AND A.S = L1.S
LEFT JOIN LF ON LF.NR = L1.LNR
--AND SO ON.......
) AS T1
JOIN (SELECT ID, DATEDIFF(dd, ADATE, BDATE) as DateDiff from A as A2) AS T2
ON T1.ID= T2.ID
As the following attempts to display, if you are using select distinct AND removing date columns from view when introducing datediff() into the select clause that could be the cause of the change in rows returned. Note in query 1 that as long as adate or bdate are displayed 5 rows would be returned, but without them (query 2) you just get one row. Alternatively, if you removed distinct,from query 2 you would get all 5 rows but just the one column (this isn't shown below).
try it out at SQL Fiddle
MS SQL Server 2014 Schema Setup:
CREATE TABLE Sample
([ADATE] datetime, [BDATE] datetime)
;
INSERT INTO Sample
([ADATE], [BDATE])
VALUES
('2017-10-01 00:00:00', '2017-10-06 00:00:00'),
('2017-10-02 00:00:00', '2017-10-07 00:00:00'),
('2017-10-03 00:00:00', '2017-10-08 00:00:00'),
('2017-10-04 00:00:00', '2017-10-09 00:00:00'),
('2017-10-05 00:00:00', '2017-10-10 00:00:00')
;
Query 1:
select distinct 'q1' qry, adate, bdate, datediff(day,adate,bdate) days_diff
from sample
order by adate
Results:
| qry | adate | bdate | days_diff |
|-----|----------------------|----------------------|-----------|
| q1 | 2017-10-01T00:00:00Z | 2017-10-06T00:00:00Z | 5 |
| q1 | 2017-10-02T00:00:00Z | 2017-10-07T00:00:00Z | 5 |
| q1 | 2017-10-03T00:00:00Z | 2017-10-08T00:00:00Z | 5 |
| q1 | 2017-10-04T00:00:00Z | 2017-10-09T00:00:00Z | 5 |
| q1 | 2017-10-05T00:00:00Z | 2017-10-10T00:00:00Z | 5 |
Query 2:
select distinct 'q2' qry, datediff(day,adate,bdate) days_diff
from sample
Results:
| qry | days_diff |
|-----|-----------|
| q2 | 5 |

SQL - SUM within subquery

I have the following code that looks at the SalesVol of different products and groups it by transaction_week
SELECT a.transaction_week,
SUM(CASE WHEN record_type IN (6,37,13) THEN quantity ELSE 0 END) as SalesVol
FROM table 1 a
LEFT JOIN table 2 b ON b.Date = a.transaction_date
LEFT JOIN table 3 c ON c.sku = a.product
WHERE series in (62,236,501,52)
GROUP BY a.transaction_week
ORDER BY a.transaction_week
| tw | SalesVol |
| 1 | 4768 |
| 2 | 4567 |
| 3 | 4354 |
| 4 | 4678 |
I want to be able to have multiple subqueries where I change the series numbers for example.
SELECT a.transaction_week,
(SELECT SUM(CASE WHEN record_type IN (6,37,13) THEN quantity ELSE 0 END) as SalesVol
FROM table 1 a
LEFT JOIN table 2 b ON b.Date = a.transaction_date
LEFT JOIN table 3 c ON c.sku = a.product
WHERE series in (62,236,501,52)) as personal care
(SELECT SUM(CASE WHEN record_type IN (6,37,13) THEN quantity ELSE 0 END) as SalesVol
FROM table 1 a
LEFT JOIN table 2 b ON b.Date = a.transaction_date
LEFT JOIN table 3 c ON c.sku = a.product
WHERE series in (37,202,203,456)) as white goods
FROM table 1 a
LEFT JOIN table 2 b ON b.Date = a.transaction_date
LEFT JOIN table 3 c ON c.sku = a.product
GROUP BY a.transaction_week
ORDER BY a.transaction_week
I can't get the subqueries at work as it is giving me the overall sum value and not grouping it by transaction_week
Instead of using subqueries, add series to the condition of the CASE statements:
SELECT a.transaction_week,
sum(CASE WHEN series IN (62,236,501,52) AND record_type IN (6,37,13)
THEN quantity ELSE 0 END) as personal_care,
sum(CASE WHEN series IN (37,202,203,456) AND record_type IN (6,37,13)
THEN quantity ELSE 0 END) as white_goods
FROM table 1 a
LEFT JOIN table 2 b ON b.Date = a.transaction_date
LEFT JOIN table 3 c ON c.sku = a.product
GROUP BY a.transaction_week
ORDER BY a.transaction_week;
You just miss the a.transaction_week in you subquery. The JOIN in outer query is unneccessary.
SELECT a.transaction_week,
(
SELECT SUM(CASE WHEN record_type IN (6,37,13) THEN quantity ELSE 0 END) as SalesVol
FROM table 1 a2
LEFT JOIN table 2 b ON b.Date = a2.transaction_date
LEFT JOIN table 3 c ON c.sku = a2.product
WHERE series in (62,236,501,52) AND a2.transaction_week = a.transaction_week
) as personal care,
(
SELECT SUM(CASE WHEN record_type IN (6,37,13) THEN quantity ELSE 0 END) as SalesVol
FROM table 1 a 2
LEFT JOIN table 2 b ON b.Date = a2.transaction_date
LEFT JOIN table 3 c ON c.sku = a2.product
WHERE series in (37,202,203,456) AND a2.transaction_week = a.transaction_week
) as white goods
FROM table 1 a
GROUP BY a.transaction_week
ORDER BY a.transaction_week
Try this it would work fast as well as up to your requirement:
SELECT a.transaction_week ,
whitegoods.SalesVol AS 'White Goods' ,
personalcare.SalesVol1 AS 'Personal Care'
FROM table1 a
LEFT JOIN table2 b ON b.[Date] = a.transaction_date
LEFT JOIN table3 c ON c.sku = a.product
CROSS APPLY ( SELECT SUM(CASE WHEN record_type IN ( 6, 37, 13 )
THEN quantity
ELSE 0
END) AS SalesVol
FROM table1 a2
WHERE b.[Date] = a2.transaction_date
AND c.sku = a2.product
AND series IN ( 37, 202, 203, 456 )
AND a2.transaction_week = a.transaction_week
) whitegoods
CROSS APPLY ( SELECT SUM(CASE WHEN record_type IN ( 6, 37, 13 )
THEN quantity
ELSE 0
END) AS SalesVol1
FROM table1 a2
WHERE b.[Date] = a2.transaction_date
AND c.sku = a2.product
AND series IN ( 62, 236, 501, 52 )
AND a2.transaction_week = a.transaction_week
) personalcare
GROUP BY a.transaction_week
ORDER BY a.transaction_week
You should use the UNION operator. Please refer to the query below:
select a.transaction_week, SalesVol from
(SELECT a.transaction_week as transaction_week,
SUM(CASE WHEN record_type IN (6,37,13) THEN quantity ELSE 0 END) as SalesVol
FROM table 1 a
LEFT JOIN table 2 b ON b.Date = a.transaction_date
LEFT JOIN table 3 c ON c.sku = a.product
WHERE series in (62,236,501,52)
UNION
SELECT a.transaction_week as transaction_week,
SUM(CASE WHEN record_type IN (6,37,13) THEN quantity ELSE 0 END) as SalesVol
FROM table 1 a
LEFT JOIN table 2 b ON b.Date = a.transaction_date
LEFT JOIN table 3 c ON c.sku = a.product
WHERE series in (37,202,203,456)
) AS tbl1
GROUP BY tbl1.transaction_week
ORDER BY tbl1.transaction_week

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

T-SQL Removing multiple LEFT JOIN

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