sql join not taking all records from another table - sql

I have a query like this
WITH CTE AS
(
SELECT
U.Name, U.Adluserid AS 'Empid',
MIN(CASE WHEN IOType = 0 THEN Edatetime END) AS 'IN',
MAX(CASE WHEN IOType = 1 THEN Edatetime END) AS 'out',
(CASE
WHEN MAX(E.Status) = 1 THEN 'AL'
WHEN MAX(E.Status) = 2 THEN 'SL'
ELSE 'L'
END) AS leave_status
FROM
Mx_ACSEventTrn
RIGHT JOIN
Mx_UserMst U ON Mx_ACSEventTrn.UsrRefcode = U.UserID
LEFT JOIN
Tbl_Zeo_Empstatus E ON Mx_ACSEventTrn.UsrRefcode = E.Emp_Id
WHERE
CAST(Edatetime AS DATE) BETWEEN '2019-11-03' AND '2019-11-03'
GROUP BY
U.Name, U.Adluserid
)
SELECT
[Name], [Empid], [IN], [OUT],
(CASE
WHEN CAST([IN] AS TIME) IS NULL THEN CAST(leave_status AS NVARCHAR(50))
WHEN CAST([IN] AS TIME) < CAST('08:15' AS TIME) THEN 'P'
ELSE 'L'
END) AS status
FROM
CTE
In my employee master table Mx_UserMst I have 67 employees. But here it is showing only a few employees those who are punched. I want to show all employees from employee master

I believe that the problem is his WHERE clause:
where cast(Edatetime as date) between '2019-11-03' and '2019-11-03'
Why not cast(Edatetime as date) = '2019-11-03'?
I'm not sure in which table the column Edatetime belongs (you should qualify all the columns with the correct table name/alias).
You must move the condition to an ON clause:
WITH CTE AS
(
select U.Name,U.Adluserid as 'Empid',
min(case when IOType=0 then Edatetime end) as 'IN',
max(case when IOType=1 then Edatetime end) as 'out',
case max(E.Status) when 1 then 'AL' when 2 then 'SL' else 'L' end as leave_status
from Mx_UserMst U
left join Mx_ACSEventTrn on Mx_ACSEventTrn.UsrRefcode=U.UserID and (cast(Edatetime as date) between '2019-11-03' and '2019-11-03')
left join Tbl_Zeo_Empstatus E on Mx_ACSEventTrn.UsrRefcode=E.Emp_Id
group by U.Name,U.Adluserid
)
SELECT [Name], [Empid],[IN],[OUT],
case
when cast([IN] as time) is null then cast(leave_status as nvarchar(50))
when cast([IN] as time) < cast('08:15' as time) then 'P'
else 'L'
end as status
FROM CTE
If Edatetime belongs to Tbl_Zeo_Empstatus move the condition to the next join's ON clause.
I also changed the RIGHT to a LEFT join so to make the statement more readable.

If you want to keep everything in a particular table, then that should be the first table in the FROM clause. Subsequent joins should be LEFT JOINs and conditions on subsequent tables should be in the ON clause rather than the WHERE clause.
I would also advise you to use table aliases and to only use single quotes for string and date constants -- NOT column aliases.
The following assumes that IOType and Edatetime are in the table Mx_ACSEventTrn. I should not have to guess. You should qualify all column names in the query.
WITH CTE AS (
SELECT U.Name, U.Adluserid AS Empid,
MIN(CASE WHEN AE.IOType = 0 THEN AE.Edatetime END) AS in_dt,
MAX(CASE WHEN AE.IOType = 1 THEN AE.Edatetime END) AS out_dt,
(CASE WHEN MAX(ES.Status) = 1 THEN 'AL'
WHEN MAX(ES.Status) = 2 THEN 'SL'
ELSE 'L'
END) AS leave_status
FROM Mx_UserMst U LEFT JOIN
Mx_ACSEventTrn AE
ON AE.UsrRefcode = U.UserID AND
CAST(AE.Edatetime AS DATE) BETWEEN '2019-11-03' AND '2019-11-03' LEFT JOIN
Tbl_Zeo_Empstatus ES
ON AE.UsrRefcode = ES.Emp_Id AND
GROUP BY U.Name, U.Adluserid
)
SELECT Name, Empid, IN_DT, OUT_DT,
(CASE WHEN IN_DT IS NULL THEN leave_status
WHEN CAST(IN_DT AS TIME) < CAST('08:15' AS TIME) THEN 'P'
ELSE 'L'
END) AS status
FROM CTE;
Some more points:
Don't name aliases things like IN that are already key words. That is why I gave it the name IN_DT.
There is no reason to cast to a TIME to compare to NULL.
I don't see a reason to cast to NVARCHAR(50) in the outer CASE expression.

Related

Multiple case with different table

I have a query like this:
select U.Name,U.Adluserid as 'Empid',
min(case when IOType=0 then Edatetime end) as 'IN',
max(case when IOType=1 then Edatetime end) as 'out'
from Mx_ACSEventTrn
inner join Mx_UserMst U on Mx_ACSEventTrn.UsrRefcode=U.UserID
left join Tbl_Zeo_Empstatus E on Mx_ACSEventTrn.UsrRefcode=E.Emp_Id
where cast(Edatetime as date) between '2019-10-30' and '2019-10-30'
group by U.Name,U.Adluserid
output
Name Empid IN OUT status
JAS 505 2019-10-30 08:06:37.000 2019-10-30 14:13:29.000 Present
SAAJ 516 2019-10-30 08:05:11.000 2019-10-30 14:17:58.000 Absent
ram 516 2019-10-30 08:20:11.000 2019-10-30 14:17:58.000 Late
I have a another table like this: Tbl_Zeo_Empstatus
Emp_Id Status
123 2
504 2
505 3
I want to show the status column depend on this condition. If IN time
is not null then check the time more than 8.15 AM. If it is more than
8.15 AM then show late other wise show present.
IF in time is null then check the status of employee from this
table "Tbl_Zeo_Empstatus", if status 2 then show "Absent" if status 3
then show "Vacation" like this.
How I can achieve this?
What you need is an additional query level (sub-select) to be able to operate on in and out time which are aggregates (min and max). To be able to join your data with Tbl_Zeo_Empstatus we need to provide outer query with u.UserID and then perform a left join based on that column.
Final query just needs a CASE expression to evaluate your conditions and set expected status based on in time column.
select
t.Name,
t.Empid,
t.t_in,
t.t_out,
case
when t.t_in is null and E.status = 2 then 'Absent'
when t.t_in is null and E.status = 3 then 'Vacation'
when cast(t.t_in as time) > '08:15' then 'Late' else 'Present'
end as status
from (
select
u.UserID,
u.Name,
u.Adluserid as empid,
min(case when IOType=0 then Edatetime end) as t_in,
max(case when IOType=1 then Edatetime end) as t_out,
from
Mx_ACSEventTrn t
inner join Mx_UserMst u on t.UsrRefcode = u.UserID
where
cast(Edatetime as date) between '2019-10-30' and '2019-10-30'
group by
u.UserId, u.Name, u.Adluserid
) t
left join Tbl_Zeo_Empstatus e on t.UserID = e.Emp_Id
try to avoid inner select like this
select
u.UserID,
u.Name,
u.Adluserid as empid,
min(case when IOType=0 then Edatetime end) as t_in,
max(case when IOType=1 then Edatetime end) as t_out
into #tmp
from
Mx_ACSEventTrn t
inner join Mx_UserMst u on t.UsrRefcode = u.UserID
where
cast(Edatetime as date) between '2019-10-30' and '2019-10-30'
group by
u.UserId, u.Name, u.Adluserid
select
t.Name,
t.Empid,
t.t_in,
t.t_out,
case
when t.t_in is null and E.status = 2 then 'Absent'
when t.t_in is null and E.status = 3 then 'Vacation'
when cast(t.t_in as time) > '08:15' then 'Late' else 'Present'
end as status
from
#tmp t
left join Tbl_Zeo_Empstatus e on t.UserID = e.Emp_Id

Using an array in conditional summation with grouping

I have the sql query thats work fine
SELECT
CAST(L.CreationUtcDateTime AS DATE),
SUM(L.Profit),
SUM(CASE WHEN (L.Network = 0 OR L.Network = 1) THEN L.Profit ELSE 0 END),
SUM(CASE WHEN (L.Network != 0 AND L.Network != 1) THEN L.Profit ELSE 0 END)
FROM
[dbo].[Leads] L WITH (NOLOCK)
LEFT JOIN [dbo].[Transactions] S WITH (NOLOCK) ON L.TransactionId = S.Id
GROUP BY
CAST(L.CreationUtcDateTime AS DATE);
I need to make it more generic by using variables instead of hard-coded constants.
I changed the query to:
DECLARE #matchNetworks TABLE (id int)
INSERT #matchNetworks(id) VALUES (0),(1)
SELECT
CAST(L.CreationUtcDateTime AS DATE),
SUM(L.Profit),
SUM(CASE WHEN (L.Network in (SELECT ID from #matchNetworks)) THEN L.Profit ELSE 0 END),
SUM(CASE WHEN (L.Network not in (SELECT ID from #matchNetworks)) THEN L.Profit ELSE 0 END)
FROM
[dbo].[Leads] L WITH (NOLOCK)
LEFT JOIN [dbo].[Transactions] S WITH (NOLOCK) ON L.TransactionId = S.Id
GROUP BY
CAST(L.CreationUtcDateTime AS DATE);
And now i have an error:
Cannot perform an aggregate function on an expression containing an aggregate or a subquery.
How can I use the predefined array of network ids in my query to avoid an error?
Move the comparison to the FROM clause using a LEFT JOIN:
SELECT CAST(L.CreationUtcDateTime AS DATE),
SUM(L.Profit),
SUM(CASE WHEN mn.ID IS NOT NULL THEN L.Profit ELSE 0 END),
SUM(CASE WHEN mn.ID IS NULL THEN L.Profit ELSE 0 END)
FROM [dbo].[Leads] L LEFT JOIN
[dbo].[Transactions] S
ON L.TransactionId = S.Id LEFT JOIN
#matchNetworks mn
ON mn.ID = l.Network
GROUP BY CAST(L.CreationUtcDateTime AS DATE);

Query for count and distinct

I should make a report in T-SQL from several table.
I can join all the table needed but after I don't know excatly how to get my information.
Explanation :
I've got the following table :
Tbl_User (UserId, Username)
Tbl_Customer (CustomeriD, CustomerName)
Tbl_DocA (DocId, CustomerID, DateCreate, DateAdd, UseriD)
Tbl_DocB (DocId, CustomerID, DateCreate, DateAdd, UseriD)
Tbl_DocC (DocId, CustomerID, DateCreate, DateAdd, UseriD)
I am trying to get a report like this :
After I can get this, the idea is to have a filter with the date in SQL reporting.
You can union all the document tables together and join users and customers on it.
SELECT Customer.CustomerID
,Customer.CustomerName
,COUNT(CASE WHEN DocType = 'A' THEN 1 END) AS doc_a_total
,COUNT(CASE WHEN DocType = 'B' THEN 1 END) AS doc_b_total
,COUNT(CASE WHEN DocType = 'C' THEN 1 END) AS doc_c_total
,COUNT(CASE WHEN DocType = 'A' AND user.username ='azerty' THEN 1 END) AS doc_a_made_by_azerty
,COUNT(CASE WHEN DocType = 'B' AND user.username ='azerty' THEN 1 END) AS doc_b_made_by_azerty
,COUNT(CASE WHEN DocType = 'C' AND user.username ='azerty' THEN 1 END) AS doc_c_made_by_azerty
FROM (
(SELECT 'A' AS DocType, * FROM Tbl_DocA)
UNION ALL
(SELECT 'B' AS DocType, * FROM Tbl_DocB)
UNION ALL
(SELECT 'C' AS DocType, * FROM Tbl_DocC)
) AS docs
JOIN Tbl_User AS user ON user.UserId = docs.UseriD
JOIN Tbl_Customer AS Customer ON Customer.CustomeriD = docs.CustomeriD
GROUP BY Customer.CustomerID , Customer.CustomerName
You can use common table expressions to get the count for each report type per customer, with conditional aggregation for reports made by a specific user, and join them to the customers table.
Something like this should get you the desired results:
DECLARE #UserId int = 1; -- or whatever the id of the user you need
WITH CTEDocA AS
(
SELECT CustomerID
, COUNT(DocId) As NumberOfReports
, COUNT(CASE WHEN UserId = #UserId THEN 1 END) As NumberOfReportsByUserAzerty
FROM Tbl_DocA
GROUP BY CustomerID
), CTEDocB AS
(
SELECT CustomerID
, COUNT(DocId) As NumberOfReports
, COUNT(CASE WHEN UserId = #UserId THEN 1 END) As NumberOfReportsByUserAzerty
FROM Tbl_DocB
GROUP BY CustomerID
), CTEDocC AS
(
SELECT CustomerID
, COUNT(DocId) As NumberOfReports
, COUNT(CASE WHEN UserId = #UserId THEN 1 END) As NumberOfReportsByUserAzerty
FROM Tbl_DocC
GROUP BY CustomerID
)
SELECT cust.CustomeriD
,cust.CustomerName
,ISNULL(a.NumberOfReports, 0) As NumberOfDocA
,ISNULL(a.NumberOfReportsByUserAzerty, 0) As NumberOfDocAByAzerty
,ISNULL(b.NumberOfReports, 0) As NumberOfDocB
,ISNULL(b.NumberOfReportsByUserAzerty, 0) As NumberOfDocBByAzerty
,ISNULL(c.NumberOfReports, 0) As NumberOfDocC
,ISNULL(c.NumberOfReportsByUserAzerty, 0) As NumberOfDocCByAzerty
FROM Tbl_Customer cust
LEFT JOIN CTEDocA As a
ON cust.CustomeriD = a.CustomerID
LEFT JOIN CTEDocA As b
ON cust.CustomeriD = b.CustomerID
LEFT JOIN CTEDocA As c
ON cust.CustomeriD = c.CustomerID
To filter by date you can add a where clause to each common table expresstion.
BTW, The fact that you have three identical tables for three document types suggest a bad database design.
If these tables are identical you should consider replacing them with a single table and add a column to that table describing the document type.
There are several ways to do this. One key feature needed is to count a particular user apart from the others. This is done with conditional aggregation. E.g.:
select
customerid,
count(*),
count(case when userid = <particular user ID here> then 1 end)
from tbl_doca
group by customerid;
Here is one possible query using a cross join to get the user in question once and cross apply to get the numbers.
select
c.customerid,
c.customername,
doca.total as doc_a_total,
doca.az as doc_a_by_azerty,
docb.total as doc_b_total,
docb.az as doc_b_by_azerty,
docc.total as doc_c_total,
docc.az as doc_c_by_azerty
from tbl_customer c
cross join
(
select userid from tbl_user where username = 'Azerty'
) azerty
cross apply
(
select
count(*) as total,
count(case when da.userid = azerty.userid then 1 end)n as az
from tbl_doca da
where da.customerid = c.customerid
) doca
cross apply
(
select
count(*) as total,
count(case when db.userid = azerty.userid then 1 end)n as az
from tbl_docb db
where db.customerid = c.customerid
) docb
cross apply
(
select
count(*) as total,
count(case when dc.userid = azerty.userid then 1 end)n as az
from tbl_docc dc
where dc.customerid = c.customerid
) docc
order by c.customerid;
Other options would be to replace the cross apply with left outer join and non-correlated subqueries or to put subqueries into the select clause.
Combining the totals for the documents is another method.
Then use conditional aggregation for the counts.
untested notepad scribble:
;WITH SPECIFICUSER AS
(
SELECT UseriD
FROM Tbl_User
WHERE UserName = 'azerty'
),
DOCTOTALS (
SELECT CustomeriD, UseriD, 'DocA' AS Src, COUNT(DocId) AS Total
FROM Tbl_DocA
GROUP BY CustomeriD, UseriD
UNION ALL
SELECT CustomeriD, UseriD, 'DocB', COUNT(DocId)
FROM Tbl_DocB
GROUP BY CustomeriD, UseriD
UNION ALL
SELECT CustomeriD, UseriD, 'DocC', COUNT(DocId)
FROM Tbl_DocC
GROUP BY CustomeriD, UseriD
)
SELECT
docs.CustomeriD,
cust.CustomerName,
SUM(CASE WHEN usrX.UseriD is not null AND docs.Src = 'DocA' THEN docs.Total ELSE 0 END) AS Total_DocA_userX,
SUM(CASE WHEN Src = 'DocA' THEN docs.Total ELSE 0 END) AS Total_DocA,
SUM(CASE WHEN usrX.UseriD is not null AND docs.Src = 'DocB' THEN docs.Total ELSE 0 END) AS Total_DocB_userX,
SUM(CASE WHEN Src = 'DocB' THEN docs.Total ELSE 0 END) AS Total_DocB,
SUM(CASE WHEN usrX.UseriD is not null AND docs.Src = 'DocC' THEN docs.Total ELSE 0 END) AS Total_DocC_userX,
SUM(CASE WHEN Src = 'DocC' THEN docs.Total ELSE 0 END) AS Total_DocC
FROM DOCTOTALS docs
LEFT JOIN Tbl_Customer cust ON cust.CustomeriD = docs.CustomeriD
LEFT JOIN Tbl_User usr ON usr.UseriD = docs.UseriD
LEFT JOIN SPECIFICUSER usrX ON usrX.UseriD = docs.UseriD
GROUP BY docs.CustomeriD, cust.CustomerName
ORDER BY docs.CustomeriD
Those long column names could be set on the report side

Aggregate case when inside non aggregate query

I have a pretty massive query that in its simplest form looks like this:
select r.rep_id, u.user_id, u.signup_date, pi.application_date, pi.management_date, aum
from table1 r
left join table2 u on r.user_id=u.user_id
left join table3 pi on u.user_id=pi.user_id
I need to add one more condition that gives me count of users with non null application date per rep (like: rep 1 has 3 users with filled application dates), and assign it into categories (since 3 users, rep is a certain status category). This looks something like this:
case when sum(case when application_date is not null then 1 else 0 end) >=10 then 'status1'
when sum(case when application_date is not null then 1 else 0 end) >=5 then 'status2'
when sum(case when application_date is not null then 1 else 0 end) >=1 then 'status3'
else 'no_status' end as category
However, if I was to simply add it to the select statement, all reps will becomes of status1 because the sum() is done over all advisors with application dates filled:
select r.rep_id, u.user_id, u.signup_date, pi.application_date, pi.management_date, aum,
(
select case when sum(case when application_date is not null then 1 else 0 end) >=10 then 'status1'
when sum(case when application_date is not null then 1 else 0 end) >=5 then 'status2'
when sum(case when application_date is not null then 1 else 0 end) >=1 then 'status3'
else 'no_status' end as category
from table3
) as category
from table1 r
left join table2 u on r.user_id=u.user_id
left join table3 pi on u.user_id=pi.user_id
Can you assist with having the addition to my query to be across reps and not overall? Much appreciated!
Based on your description, I think you need a window function:
select r.rep_id, u.user_id, u.signup_date, pi.application_date, pi.management_date, aum,
count(pi.application_date) over (partition by r.rep_id) as newcol
from table1 r left join
table2 u
on r.user_id = u.user_id left join
table3 pi
on u.user_id = pi.user_id;
You can use the count() in a case to get ranges, if that is what you prefer.

Joined SQL query MAX aggregate with condition

I am having trouble with a SQL query. Here is a representation of my schema on SQL Fiddle:
http://sqlfiddle.com/#!15/14c8e/1
The issue is that I want to return rows of data from the Invitations table and join them with a sum of both the 'sent' event_type and 'viewed' event_type from the associated events, as well as the latest created_at date.
I can get all the data and counts working, but am having issue with the last_sent_on. Is there a way I can use a condition in a MAX aggregate function?
e.g.
MAX(
SELECT events.created_at
WHERE event_type='sent'
)
If not, how would I write the proper subselect?
I am currently using Postgresql.
Thank you.
You can use a case statement inside of max just as you've done with sum. The query below will select the maximum created_at for event_type='sent'
SELECT
i.id,
i.name,
i.email,
max(case when e.event_type='sent' then e.created_at end) AS last_sent_on,
sum(case when e.event_type='sent' then 1 else 0 end) AS sent_count,
sum(case when e.event_type='viewed' then 1 else 0 end) AS view_count
FROM
invitations i
LEFT OUTER JOIN
events e
ON e.eventable_id = i.id
WHERE e.eventable_type='Invitation'
GROUP BY i.id, i.name, i.email
SQLFiddle
Try using a subquery to build the max value for sent.
SELECT
i.id,
i.name,
i.email,
sent.last_sent,
sum(case when e.event_type='sent' then 1 else 0 end) AS sent_count,
sum(case when e.event_type='viewed' then 1 else 0 end) AS view_count
FROM
invitations i
LEFT OUTER JOIN
events e
ON e.eventable_id = i.id
LEFT JOIN ( SELECT eventable_id uid, MAX(created_at) AS last_sent
FROM events
WHERE event_type = 'sent'
GROUP BY eventable_id ) AS sent
ON sent.uid = i.id
WHERE e.eventable_type='Invitation'
GROUP BY i.id, i.name, i.email, sent.last_sent