How to convert rows in columns - sql

I have some problems with a SQL query, I try to convert row in columns.
select U.FirstName,U.LastName,Sh.[Day],Sh.StartTime,Sh.EndTime from dbo.[User] as U
inner join dbo.Employee as E on U.UserId = E.UserId
inner join dbo.ScheduleStaff as SH on SH.EmployeeId = E.EmployeeId
where ((Sh.[Day] < getdate()+ 4 ) and sh.[Day] >= getdate())
group by U.FirstName, U.LastName,Sh.[Day],Sh.StartTime,Sh.EndTime
This query gives me the below result:
How can I make that there is one employee per line, and the days turn into columns?
I try this:
select FirstName,LastName,Day1,Day2,Day3
from
(
select U.FirstName,U.LastName,Sh.[Day],Sh.StartTime,Sh.EndTime,concat('Day',ROW_NUMBER() over (partition by
U.FirstName,U.LastName order by U.FirstName,U.LastName)) as Tests
from dbo.[User] as U
inner join dbo.Employee as E on U.UserId = E.UserId
inner join dbo.ScheduleStaff as SH on SH.EmployeeId = E.EmployeeId
where ((Sh.[Day] < getdate()+ 4 ) and sh.[Day] >= getdate()-1)
group by U.FirstName, U.LastName,Sh.[Day],Sh.StartTime,Sh.EndTime
)Temp
pivot
(
Max([Day])
for Tests in (Day1,Day2,Day3)
)Piv
But this is not exactly what I need, how can I make the days as columns and their time as values?

This will work if you have static [Date] value. But if you want [Date] dynamic then you need to collect date value and put into pivot section.
Your question was[how can I make the days as columns and their time as values]
select FirstName,LastName,[2020-08-28],[2020-08-29] from
(select FirstName,LastName,[Date], CONCAT(StartTime,'-to-'+EndTime) as [Time] from tbl_user) t
pivot(MAX(t.[Time]) for [Date] in([2020-08-28],[2020-08-29]) )pvt
Note: for dynamic date value. You can follow Terence link mentioned in comment section.

Related

Order by on a nested query

Is their a way to order this by the Time column? I am not sure how to do this. The time is the schedule and I just need it to go from the morning to the evening.
Can I just nest another select statement and use that?
Thank you.
SELECT
DoseLevel,
LastName,
FirstName,
DOB,
EMPLID,
Time,
(
SELECT v.ColorCode
FROM ABCDocumentation cd1
LEFT JOIN ABCDocumentation cd2ON cd1.ABCDocumentationID = cd2.PairID
LEFT JOIN Medicine v ON v.MedicineID = cd1.MedicineID
LEFT JOIN Manufacturers mfg ON v.MFG_Seq = mfg.MFG_Seq
WHERE cd2.ABCDocumentationID = dt.ABCDocumentationID
) AS ParentColorCode,
(
SELECT mfg.Description
FROM ABCDocumentation cd1
LEFT JOIN ABCDocumentation cd2 ON cd1.ABCDocumentationID = cd2.PairID
LEFT JOIN Medicine v ON v.MedicineID = cd1.MedicineID
LEFT JOIN Manufacturers mfg ON v.MFG_Seq = mfg.MFG_Seq
WHERE cd2.ABCDocumentationID = dt.ABCDocumentationID
) AS ParentManuDesc
FROM
(
SELECT
cd.DoseLevel,
e.LastName,
e.FirstName,
e.DOB,
cvse.EMPLID,
cvse.AdminScheduleSlotsEmployeeID,
cd.ABCDocumentationID,
cvss.Time,
cd.ModifyDate AS 'StartTime'
FROM ABCAdminSchedule cvs
LEFT JOIN ABCAdminScheduleSlots cvss ON cvs.AdminScheduleID = cvss.AdminScheduleID
LEFT JOIN ABCAdminScheduleSlotsEmployee cvse ON cvss.AdminScheduleSlotsID = cvse.AdminScheduleSlotsID
LEFT JOIN ABCDocumentation cd ON cvse.AdminScheduleSlotsEmployeeID = cd.AdminScheduleSlotsEmployeeID
LEFT JOIN Employee e ON cvse.EmplID = e.EMPLID
WHERE CAST(TIME AS Date) = CAST(GETDATE() AS Date) AND CampusID = '06'
AND cvse.AdminScheduleSlotsEmployeeID IS NOT NULL
) dt
First off, there is no need for the derived table dt, as you are not doing any further processing.
Secondly, you can combine the two correlated subqueries into one with an APPLY.
Thirdly, conversions on columns can cause performance issues, so you can change the date check to a half-open interval, converting just GETDATE().
Finally you can add at the end an ORDER BY clause to sort.
SELECT
cd.DoseLevel,
e.LastName,
e.FirstName,
e.DOB,
cvse.EMPLID,
cvss.Time,
Parent.ColorCode,
Parent.Description
FROM ABCAdminSchedule cvs
LEFT JOIN ABCAdminScheduleSlots cvss ON cvs.AdminScheduleID = cvss.AdminScheduleID
LEFT JOIN ABCAdminScheduleSlotsEmployee cvse ON cvss.AdminScheduleSlotsID = cvse.AdminScheduleSlotsID
LEFT JOIN ABCDocumentation cd ON cvse.AdminScheduleSlotsEmployeeID = cd.AdminScheduleSlotsEmployeeID
LEFT JOIN Employee e ON cvse.EmplID = e.EMPLID
OUTER APPLY
(
SELECT v.ColorCode, mfg.Description
FROM ABCDocumentation cd1
LEFT JOIN ABCDocumentation cd2ON cd1.ABCDocumentationID = cd2.PairID
LEFT JOIN Medicine v ON v.MedicineID = cd1.MedicineID
LEFT JOIN Manufacturers mfg ON v.MFG_Seq = mfg.MFG_Seq
WHERE cd2.ABCDocumentationID = dt.ABCDocumentationID
) AS Parent
WHERE TIME >= CAST(GETDATE() AS Date) AND TIME < CAST(DATEADD(day, 1, GETDATE()) AS DATE)
AND CampusID = '06'
AND cvse.AdminScheduleSlotsEmployeeID IS NOT NULL
ORDER BY TIME
please
declare #tm table (id int identity, timee time(7))
insert into #tm (timee) values ('01:05:45'),
('10:15:18'),
('14:18:59'),
('09:15:10'),
('18:19:21'),
('21:05:17')
this is a default
select * from #tm order by id
this is a, what do you need
select tm.*,
iif(tm.part_time = 1, 'morning', 'evening') m_e from (select
case
when timee between '09:00:00' and '19:00:00' then 1
else 2 end part_time,
*
from #tm) tm
order by part_time, timee

Confusing sum result

I've 3 tables as below.
Users
StatusTable
Time_Tracker
And the relation is as below.
users have username and userid, StatusTable has userName and Time_Tracker has userName
Below is my table Data.
And here I'm trying to get the sum
Problem:
I want to update the productiontime as sum of time taken[minutes] by joining case owner to username and TT.userId = Users.userId and the login = GetDate()
My Query is
update Time_Tracker set ProductionTime = (select sum(ST.[time taken(minutes)])
from statustable as st inner join users as u
on st.[case owner] = u.username
inner join Time_Tracker as TT
on u.userId = TT.userId
where cast(st.[Start Time] AS DATE) = CAST(GETDATE() as Date)
Group By TT.UserId, u.UserId) where CAST(Login AS DATE) = CAST(GETDATE() as Date)
And the blue box in my image is my current O/p.
When I run
select sum([time taken(minutes)]) as totalTme from StatusTable where cast([Start Time] AS DATE) = CAST(GETDATE() as Date)
I get the O/p as 2.05 which is correct one.
please let me know where am I going wrong in my first query(printing 10.25) instead of 2.05.
How can I fix this.
Thanks
I think you have too many joins in the subquery:
update Time_Tracker tt
set ProductionTime = (select sum(ST.[time taken(minutes)])
from statustable st inner join
users u
on st.[case owner] = u.username
where u.userId = tt.userId and
cast(st.[Start Time] AS DATE) = CAST(GETDATE() as Date)
)
where CAST(Login AS DATE) = CAST(GETDATE() as Date);
Your version is multiplying the result for each row in TimeTracker. The correlation clause is what you need.
Also very important. Such a correlated subquery should not have a GROUP BY clause. The GROUP BY could return multiple rows, but by definition the correlated subquery should return at most one row.

Column 'Pay_records.ActualTime' is invalid in the select list because it is not contained in either an aggregate function or the GROUP BY clause

I am trying to get the TimeCodeTotal per TimeCode. SQL Server doesn't like my current setup and wants me to add PR.Actual time to the GROUP BY.
This is not a solution because adding PR.ActualTime to the GROUP BY changes the results to incorrect values;
I only want to GROUP BY the values provided in the query below.
Is there another way to seperate by timecode without using the OVER (BY PARTITION) clause?
SELECT YEAR(PR.date) AS TimeYear
,SUBSTRING(DATENAME(MONTH,DATEADD(MONTH,MONTH(PR.Date),0)- 1),1,3) AS TimeMonth
,TC.TimeCode
,SUM(PR.ActualTime) OVER (PARTITION BY TC.TimeCode) AS TimeCodeTotal
,SUM(PR.ActualTime) AS MonthTotal
FROM Pay_records PR
INNER JOIN Employee E ON E.employee_no = PR.employee_no
INNER JOIN Dept_Names D ON D.Dept = E.department
INNER JOIN DepartmentTimeCategory DTC ON DTC.Dept = D.Dept
INNER JOIN TimeCategory TMC ON TMC.ID = DTC.TimeCategoryID
INNER JOIN TimeCode TC ON TC.TimeCodeID = TMC.TimeCodeID
WHERE PR.DATE BETWEEN '01/01/2015' AND '02/17/16'
AND D.Dept IN ('02Z103')
AND E.Billable IN (1,0)
GROUP BY YEAR(PR.date), MONTH(PR.date), TC.TimeCode
ORDER BY YEAR(PR.date), MONTH(PR.date)
I think an easy fix is to remove SUM(PR.ActualTime) OVER (PARTITION BY TC.TimeCode) AS TimeCodeTotal from the query, wrap the whole thing in a CTE, and then select everything from the CTE and add a SUM(TimeCodeTotal).
;WITH CTE AS (
SELECT YEAR(PR.date) AS TimeYear
,SUBSTRING(DATENAME(MONTH,DATEADD(MONTH,MONTH(PR.Date),0)- 1),1,3) AS TimeMonth
,TC.TimeCode
,SUM(PR.ActualTime) AS TimeCodeSubTotal
FROM Pay_records PR
INNER JOIN Employee E ON E.employee_no = PR.employee_no
INNER JOIN Dept_Names D ON D.Dept = E.department
INNER JOIN DepartmentTimeCategory DTC ON DTC.Dept = D.Dept
INNER JOIN TimeCategory TMC ON TMC.ID = DTC.TimeCategoryID
INNER JOIN TimeCode TC ON TC.TimeCodeID = TMC.TimeCodeID
WHERE PR.DATE BETWEEN '01/01/2015' AND '02/17/16'
AND D.Dept IN ('02Z103')
AND E.Billable IN (1,0)
GROUP BY YEAR(PR.date)
,MONTH(PR.date)
,TC.TimeCode
)
SELECT TimeYear
, TimeMonth
, TimeCode
, TimeCodeSubTotal
, Sum(TimeCodeSubTotal)
FROM CTE
GROUP BY TimeYear
, TimeMonth
, TimeCode
, TimeCodeSubTotal
ORDER BY YEAR(PR.date)
, MONTH(PR.date)

Group by a list in the where clause and then show the row whether it has a value or is null

I was working on this query for awhile and I'm trying to figure out how to get this query to show all the initials in the where clause in the output table regardless as to whether that person has a p.Id or not. This is because I will be putting this information into a pre-formatted Excel table. Thanks in advance! EDITED:
SELECT
e.Initials, COUNT(p.Id) As "NT > 7"
FROM
Employees AS e
LEFT JOIN
Projects AS p
ON
e.Id = p.NTEmployeeId AND
cast(p.NTDate as Datetime) < cast(dateadd(day, -7, (getdate())) as Datetime)
JOIN
Statuses AS s
ON
s.Id = p.StatusId AND
s.Code in ('WIPR', 'RISK', 'PEND')
WHERE
e.Initials IN('af', 'cm' , 'jy','br','dfv','rxc','tm','axk','hd','sa','rw')
GROUP BY (e.Initials);
You need an additional left join and to move the conditions on all but the first table into on clauses. Remember, the where clause will turn the outer joins to inner joins, because NULL values don't (generally) match conditions.
SELECT e.Initials, COUNT(p.Id) As "NT > 7"
FROM Employees e LEFT JOIN
Projects p
ON e.Id = p.NTEmployeeId AND
cast(p.NTDate as Date) < cast(getdate() as date) -- do date comparisons as dates, not strings
LEFT JOIN -- NEED LEFT JOIN HERE, or `ON` clause turns it into inner join
Statuses s
ON s.Id = p.StatusId AND
s.Code in ('WIPR', 'RISK', 'PEND') -- `LIKE`/`OR` isn't wrong but `IN` is easier
WHERE e.Initials IN ('af', 'cm' , 'jy', 'br','dfv', 'rxc', 'tm', 'axk', 'hd', 'sa', 'rw')
GROUP BY e.Initials;
I made a couple other changes:
Use dates for comparing dates, not strings. I think I have the logic right.
Use IN instead of chains of ORs if you can.
And for what you are doing, you might want to be counting the matching statuses rather than the matching people.
I figured out what I needed to do to get the correct answer to display. Please see below:
SELECT
e.Initials, COUNT(CASE WHEN s.Code in ('WIPR', 'RISK', 'PEND')
and cast(p.NTDate as Datetime) < cast(dateadd(day, -7, (getdate())) as Datetime) THEN p.Id END) As "NT > 7"
FROM
Employees AS e
LEFT JOIN
Projects AS p
ON
e.Id = p.NTEmployeeId
JOIN
Statuses AS s
ON
s.Id = p.StatusId
WHERE
e.Initials IN('af', 'cm' , 'jy','br','dfv','rxc','tm','axk','hxd','rw')
GROUP BY (e.Initials);

Combining two counts with joins in one query

Ok - hoping someone can help as I'm struggling here. Have tried using sum(case when), count(*), subqueries with no success.
Essentially I have two queries I am trying to combine into one, grouped by the same parameter. The queries:
Query 1:
SELECT
u.username,
count(*) as 'Total'
FROM log1 L1
JOIN users u on u.userref = L1.userref
WHERE L1.date between #FromDate and #ToDate
GROUP BY u.username
ORDER BY u.username
Query 2:
SELECT
u.username,
count(*) as 'Total'
FROM log2 L2
LEFT JOIN users u on u.userref = L2.userref
WHERE L2.date between #FromDate and #ToDate and L2.message like '%None%'
GROUP BY u.username
ORDER BY u.username
What I would like is a single query with a username column (u.username), a column showing the results of query 1, and a column showing the results of query two. Any help appreciated!
You can put a case statement inside the count function to only count when certain criteria is met:
SELECT u.username,
Total = COUNT(*),
Total2 = COUNT(CASE WHEN L.message LIKE '%None%' THEN 1 END)
FROM log1 AS L
JOIN users AS u
ON u.userref = L.userref
WHERE L.date BETWEEN #FromDate and #ToDate
GROUP BY u.username
ORDER BY u.username;
Of note:
BETWEEN can cause problems, especially when working with dates
Using string literals for column aliases is on the deprecation list
EDIT
Completely missed that you had two separate log tables:
SELECT u.username,
Total = COUNT(Count1),
Total2 = COUNT(Count2)
FROM ( SELECT l.UserRef, Count1 = 1, Count2 = NULL
FROM log1 AS L
WHERE L.date BETWEEN #FromDate and #ToDate
UNION ALL
SELECT l.UserRef, Count1 = NULL, Count2 = 1
FROM log2 AS L
WHERE L.date BETWEEN #FromDate and #ToDate
AND L.message LIKE '%None%'
) AS l
JOIN users AS u
ON u.userref = L.userref
GROUP BY u.username
ORDER BY u.username;
SELECT X.USERNAME,X.TOTAL,Y.TOTAL
FROM(SELECT
u.username,
count(*) as 'Total'
FROM log1 L1
JOIN users u on u.userref = L1.userref
WHERE L1.date between #FromDate and #ToDate
GROUP BY u.username
) as x
INNER JOIN
(
SELECT
u.username,
count(*) as 'Total'
FROM log2 L2
LEFT JOIN users u on u.userref = L2.userref
WHERE L2.date between #FromDate and #ToDate and L2.message like '%None%'
GROUP BY u.username
) AS Y ON Y.USERNAME = X.USERNAME