Convert 2 rows to fields in SQL Server - sql-server

In SQL server (2016), I want to convert 2 rows into 1 row with fields of both rows.
I have this example:
IF OBJECT_ID('tempdb.dbo.#MyTable') IS not NULL DROP TABLE #MyTable
CREATE TABLE #MyTable (
Direction varchar(1),
DateKey int,
ID varchar(8),
[Sessions] int
)
insert into #MyTable values('S', 20180301, 'ID123456', 46)
insert into #MyTable values('R', 20180301, 'ID123456', 99)
select * from #MyTable
Output:
Direction DateKey ID Sessions
S 20180301 ID123456 46
R 20180301 ID123456 99
The output I want is:
DateKey ID S_Sessions R_Sessions
20180301 ID123456 46 99
So I tried this query but it won't work:
select DateKey,ID,
case Direction
when 'S' then [Sessions] as S_Sessions -- Incorrect syntax near the keyword 'as'.
else [Sessions] as R_Sessions
end
from #MyTable
Maybe I have to create an extra table, insert rows where direction='S' and then update the records with data where direction='R' but I wonder if there is a better way to do this.

use PIVOT
select *
from #MyTable
pivot
(
max(Sessions)
for Direction in ([S], [R])
) p

assuming that your table contains the "pairs" S and R you can also use a self join
SELECT s.DateKey , s.ID , s.Sessions S_Sessions , r.Sessions R_Sessions
FROM #MyTable S
JOIN #MyTable R
ON s.ID = r.ID
AND s.DateKey = r.DateKey
WHERE S.Direction = 'S'
AND r.Direction = 'R'

CASE in SQL is an expression that returns a single value. It cannot be used to control execution flow like in procedural languages.
You can use conditional aggregation for this:
select DateKey, ID,
max(case Direction when 'S' then [Sessions] end) as S_Sessions,
max(case Direction when 'R' then [Sessions] end) as R_Sessions
from #MyTable
group by DateKey, ID
Demo here

Try It ... It works for me . more variable more case and more left join table.
select a.DateKey,a.ID,
(case a.Direction
when 'S' then a.Sessions
end) as S_Sessions,
(case b.Direction
when 'R' then b.Sessions
end) as R_Sessions
from mytable as a CROSS JOIN mytable as b ON a.ID=b.ID LIMIT 2,1

Related

How to do SQL Range based calculations

I have two columns 'TotalAmount' and 'DefaultFees'
But there is a more granular table that says for the 'TotalAmount' ( which is 837.681561) between '0.010000' and '500.000000' fees should be '2.500000'
and for the rest of the total amount between '500.010000' and '800.000000' which is 300 fees should be '4.876000'
For the rest which is 37.681561, it should be default fees. There can be more rows to the Min/Max table
How can I achieve this in SQL
Expected Output
Although Isaac's answer is more clear, as an alternative solution, you can use a case-when statement with a subqueries as below.
Select TotalAmount,
case when TotalAmount >= (select max(MaximumAmount) from TableB) then DefaultFees
else (Select Fees
From TableB b
where a.TotalAmount<b.MaximumAmount and a.TotalAmount>=b.MinimumAmount
)
end as FinalFee
From TableA a
EDIT:
Based on your updated question, this is the query you are looking for:
CREATE TABLE #TableA (myVal INT, DefaultFee float)
INSERT INTO #TableA VALUES (837,3.66)
CREATE TABLE #TableB (minamount INT, maxamount INT, fees float)
INSERT INTO #TableB VALUES (0,500,2.5),(500,800,4.876)
;WITH cte AS (
SELECT ISNULL(b.minamount, (SELECT MAX(maxamount) FROM #tableB)) as [max]
from #TableA a
left join #TableB b on a.myVal between b.minamount and maxamount
)
SELECT maxamount, fees
FROM #TableB
WHERE maxamount <= (SELECT [max] FROM cte)
UNION
SELECT (a.myval - (SELECT [max] FROM cte)), a.DefaultFee
FROM #TableA a
ORDER BY 1 desc

Linking to an audit table

So I have dbo.Table1 that contains ID, ColA, ColB, ColC.
I also have audit.Table1 that contains AuditID, AuditDate, AuditUserID, AuditType, ID, ColA, ColB, ColC.
AuditType is either I, U, or D (for Insert, Update, or Delete).
The results I'm looking for is a query that I gives me each record in Table1 based on whatever where clauses, but also gives me Last Modified information pulled from the audit.Table1 table (the most recent 'U' value for the ID) mainly I want the date and AuditUserID so I can pull a basic Last Modified on such & such date by so & so user.
I've tried using a Cross Apply, but stopped along that thought when my query was still running after 30 seconds.
There can be instances where there is no 'U' record (the record was only created, no changes have been made).
It isn't very clear exactly what you have going on here. But pretty sure you can use something along these lines.
create table Table1
(
ID int identity
, ColA varchar(10)
, ColB varchar(10)
, ColC varchar(10)
)
insert Table1
select 'Col1', 'Col2', 'Col3'
insert Table1
select 'This', 'has', 'no update'
create table AuditTable1
(
AuditID int identity
, AuditDate datetime
, AuditUserID int
, AuditType char(1)
, ID int
, ColA varchar(10)
, ColB varchar(10)
, ColC varchar(10)
)
insert AuditTable1
select getdate()
, 9 --just some number
, 'U'
, 1
, 'Col1'
, 'Col2'
, 'Col3'
select t.*
, x.AuditID
, x.AuditDate
, x.AuditUserID
from Table1 t
outer apply
(
select top 1 AuditID
, AuditDate
, AuditUserID
from AuditTable1 at1
where at1.ID = t.ID
order by at1.AuditDate desc
) x
So after working further on this, I was able to make everything work by creating a transitional left join that just gave me the AuditID # of the last touched entry... and if the AuditUserID is null from my left join, then I know it hasn't been modified since creation. So my final query is basically...
SELECT [fields wanted from Courses table],
IIF(ISNULL(ac.AuditUserID,0) = 0, [make created by info], [make modified info]) as LastModInfo
FROM dbo.Courses c
LEFT JOIN (select MAX(AuditID), CourseID from audit.Courses group by CourseID) mid
ON mid.CourseID = c.CourseID
LEFT JOIN audit.Course ac on ac.AuditID = mid.AuditID AND ac.AuditType = 'U'
Thank you #seanlange for your attempt

SQL Server Overall Total in a group by

In my SQL Server Query, I am trying to count the number of employees per site. This works, but when I try to add in a percentage of total, it still groups by Site so it is inaccurate.
Is there an easier way to achieve this?
I am using this Query to create a view.
select Site.SiteName,
sum(case when Employee.ActiveStatus = 'Yes' then 1 else 0 end) as
"NumberOfEmployees",
CONVERT(decimal(6,2),(sum(case when Employee.ActiveStatus = 'Yes' then 1
else 0 end))/(convert(decimal(6,2),COUNT(EmployeeID)))) as PercentageOfEmps
from Employee
left join Site
on(Employee.SiteID=Site.SiteID)
GROUP BY Site.SiteName;
GO
You could use subquery:
select
Site.SiteName,
NumberOfEmployees = sum(case when Employee.ActiveStatus = 'Yes' then 1 else 0 end),
PercentageOfEmps = CONVERT(decimal(6,2),(sum(case when Employee.ActiveStatus = 'Yes' then 1
else 0 end))/(SELECT COUNT(EmployeeID) FROM Employee)
from Employee
left join Site
on Employee.SiteID=Site.SiteID
GROUP BY Site.SiteName;
I can't provide an answer for your scenario, as I don't have any sample data to use, therefore I've provided a small dataset.
One method is to use a CTE/Subquery to get a total number and then include the total in the GROUP BY. This method avoids 2 scans of the table:
WITH VTE AS(
SELECT *
FROM (VALUES(1,'Steve',1),
(2,'Jayne',1),
(3,'Greg',2),
(4,'Sarah',3)) V(EmpID, EmpName, SiteID)),
CTE AS(
SELECT V.EmpID,
V.EmpName,
V.SiteID,
COUNT(V.EmpID) OVER () AS TotalCount
FROM VTE V)
SELECT C.SiteID,
COUNT(C.EmpID) AS Employees,
COUNT(C.EmpID) / (C.TotalCount *1.0) AS Perc
FROM CTE C
GROUP BY C.SiteID,
C.TotalCount;
This script should help-
SELECT
Site.SiteName,
COUNT(EmployeeID) AS [NumberOfEmployees],
((COUNT(EmployeeID)*1.0)/(SELECT COUNT(*) FROM Employee WHERE ActiveStatus = 'Yes'))*100.00 as PercentageOfEmps
FROM Employee
INNER JOIN Site
ON Employee.SiteID = Site.SiteID
WHERE Employee.ActiveStatus = 'Yes'
GROUP BY Site.SiteName;
Data creation script
declare #Employee Table(EmployeeID int ,ActiveStatus nvarchar(20) ,SiteID int)
declare #Site Table(SiteName nvarchar(20) ,SiteID int)
insert into #Employee values(1,'Yes',101),(2,'Yes',101),(3,'Yes',102),(4,'Yes',102),
(5,'Yes',101)
insert into #Site values('Site1',101)
insert into #Site values('Site2',102)
//real script to get the %percentage
;with cte as
(
select s.SiteName,sum(case when e.ActiveStatus = 'Yes' then 1 else 0 end) as "NumberOfEmployees"
from #Employee e
left join #Site s
on(e.SiteID=s.SiteID)
GROUP BY s.SiteName
),
cte_sum as
(select sum(NumberOfEmployees) as total from cte )
select c.*, convert (decimal(6,2),c.NumberOfEmployees)/convert (decimal(6,2),cs.total)*100 from cte_sum cs, cte c;

How to simplify following SQL query?

How to simplify following SQL query,
DECLARE #EMPLOYEE1 TABLE (EMPID INT,DEPT1 INT,DEPT2 INT)
DECLARE #EMPLOYEE2 TABLE (EMPID INT,DEPT1 INT,DEPT2 INT)
INSERT INTO #EMPLOYEE1 VALUES
(1,1,1),
(2,2,2),
(3,10,3),
(4,4,4)
INSERT INTO #EMPLOYEE2 VALUES
(1,1,1),
(2,2,2),
(3,10,10),
(4,10,4)
SELECT A.EMPID,
A.DEPT1 EMP1_DEPT,
0 TYPES
FROM #EMPLOYEE1 A
LEFT JOIN #EMPLOYEE2 B ON A.DEPT1=B.DEPT1
WHERE B.DEPT1 IS NULL
UNION ALL
SELECT A.EMPID,
A.DEPT2 EMP2_DEPT,
1 TYPES
FROM #EMPLOYEE1 A
LEFT JOIN #EMPLOYEE2 B ON A.DEPT2=B.DEPT2
WHERE B.DEPT2 IS NULL
can any one sort this problem, thanks in Advance
Here is one way to do it:
SELECT DISTINCT
A.EMPID,
CASE WHEN B.DEPT1 IS NULL THEN A.DEPT1 ELSE A.DEPT2 END As EMP1_DEPT,
CASE WHEN B.DEPT1 IS NULL THEN 0 ELSE 1 END As TYPES
FROM #EMPLOYEE1 A
LEFT JOIN #EMPLOYEE2 B ON A.DEPT1=B.DEPT1
LEFT JOIN #EMPLOYEE2 C ON A.DEPT2=C.DEPT2
WHERE B.DEPT1 IS NULL
OR C.DEPT2 IS NULL
Here is another alternative using CROSS APPLY and VALUES:
SELECT A.EMPID,
A_D.DEPT AS 'EMP1_DEPT',
A_D.[TYPES]
FROM #EMPLOYEE1 A
CROSS APPLY (VALUES (A.DEPT1, 0), (A.DEPT2, 1)) A_D ( DEPT, [TYPES] )
WHERE NOT EXISTS (SELECT 1
FROM #EMPLOYEE2 B
CROSS APPLY (VALUES (B.DEPT1, 0), (B.DEPT2, 1)) B_D ( DEPT, [TYPES] )
WHERE B_D.DEPT = A_D.DEPT
AND B_D.[TYPES] = A_D.[TYPES]);
The advantage of this approach is that each table is only hit once. It uses NOT EXISTS to improve performance in the query plan by using a Left Anti Semi Join.

SQL - Combine rows

I have rows in a table that looks like this:
[date],[name],[duty],[holiday],[hdaypart],[sick],[sdaypart]
2015-04-27, person1, 1,0,NULL,0,NULL
2015-04-27, person1, 0 1,'fd',0,NULL
I would like to combine these rows to:
[date],[name],[duty],[holiday],[hdaypart],[sick],[sdaypart]
2015-04-27, person1, 1,1,'fd',0,NULL
The duty, holiday and sick columns as BIT columns.
Is there way to do this?
The one solution I can come up with is using subqueries, but it consumes a lot of time. A faster solution would be nice.
This is what I have now:
SELECT DISTINCT [name],[date],[region],[cluster]
,CASE WHEN (SELECT SUM(CONVERT(INT,callduty)) FROM planning AS t2
WHERE t1.[Date] = #datum AND t2.[Name] = t1.[name] AND t2.[Date] = t1.[date] ) > 0
THEN 1 ELSE 0 END AS [CallDuty]
,CASE WHEN (SELECT SUM(CONVERT(INT,holiday)) FROM planning AS t2
WHERE t1.[Date] = #datum AND t2.[Name] = t1.[name] AND t2.[Date] = t1.[date] ) > 0
THEN 1 ELSE 0 END AS [Holiday]
FROM planning AS t1
where t1.[Date] = #datum AND t1.[Name] like #naam
group by t1.[date],t1.[name], t1.Region, t1.cluster
order by t1.[name]
You seem to want to group by date and name and select either the maximum or the not null values within each group. MAX aggregate function is suitable for both of these selections:
SELECT [date],[name], MAX([duty]), MAX([holiday]),
MAX([hdaypart]), MAX([sick]), MAX([sdaypart])
FROM mytable
GROUP BY [date],[name]
By looking at your example, I assume that you want to get the maximum values for a specific user.
You could do this using a group by and max
select max([date]),[name],max([duty]),max([holiday]),max([hdaypart]),max([sick]),max([sdaypart])
from yourtable
group by name
This is not really pretty but should perform better than using subqueries.
EDIT:
If you have columns with bit sql types, use
max(cast([bitColumn] as int))
Adding the date column in the group by, as suggested by Giorgos Betsos, the result is
select [date],
[name],
max([duty]),
max([holiday]),
max(cast([hdaypart] as int)),
max(cast([sick] as int)),
max(cast([sdaypart] as int))
from yourtable
group by [date],[name]
declare #t table ([date] date,[name] varchar(10),[duty] varchar(10),[holiday] int,[hdaypart] varchar(10),[sick] int,[sdaypart]
int
)
insert into #t([date],[name],[duty],[holiday],[hdaypart],[sick],[sdaypart])values ('2015-04-27','person1',1,0,NULL,0,NULL),
('2015-04-27','person1',1,0,'fd',0,NULL)
select MAX([date]),MAX([name]),MAX([duty]),MAX([holiday]),MAX([hdaypart]), [sick],[sdaypart] from #t
group by sick,[sdaypart]
OR
select [date],[name],[duty],[holiday],MAX([hdaypart])AS H,[sick],[sdaypart] from #t
group by [date],[name],[duty],[holiday],[sick],[sdaypart]
UNION
select [date],[name],[duty],[holiday],MAX([hdaypart])AS H,[sick],[sdaypart] from #t
group by [date],[name],[duty],[holiday],[sick],[sdaypart]
CREATE TABLE #Combine
(
[date] date,
[name] VARCHAR(10),
[duty] CHAR(1),
[holiday] CHAR(1),
[hdaypart] CHAR(5),
[sick] CHAR(1),
[sdaypart] VARCHAR(10)
)
INSERT INTO #Combine VALUES('2015-04-27', 'person1', '1','0',NULL,'0',NULL),
('2015-04-27', 'person1', '0','1','fd','0',NULL)
SELECT MAX(Date) [date],MAX(name) [name],MAX(Duty) [duty],MAX(holiday) holiday,
MAX(hdaypart) hdaypart,max(sick) sick,max(sdaypart)sdaypart FROM #Combine

Resources