Kinda pivot table thing question in SQL Server - sql

I have a Staff table that includes staff previous promotion and title history.
earliest date in 'startDate' for a staff is the date of the he/she/apache started.
lastest date of 'EndDate' for a staff is today. and that title is today's title.
I need to run a script that contains,
Id, Name, StartDate (as day he/she hired), enddate (lastest day of EndDate column) and Title (lastest title)
I tried something but no success..
Sample table as follows:
create table staff
(
PersonId int,
Name varchar(50),
StartDate date,
EndDate date,
Title varchar(50)
);
insert into staff (PersonId,Name,StartDate,EndDate,Title) values
( '2798','Daniel','20200131','20200331','Assistant'),
( '2798','Daniel','20200401','20200630','Senior'),
( '2798','Daniel','20200701','20210331','Manager'),
( '553','Rebecca','20200131','20200430','Senior'),
( '553','Rebecca','20200501','20210331','Manager')
;
select * from staff;
DB Fiddle

Calculate the start and end dates for each person, then join back to the original table on person and end date to get the final title:
;with empRange as (
SELECT PersonID, MIN(StartDate) AS firstStart, MAX(EndDate) AS lastEnd
FROM staff GROUP BY PersonID
)
SELECT
e.PersonID
,e.firstStart as StartDate
,s.EndDate
,s.Title
FROM empRange e
JOIN staff s ON e.PersonID = s.PersonID
AND s.EndDate = e.lastEnd

is that what you are looking for? Solution ist not optimized ;-)
;with cte_first_date (PersonID, first_startdate)
as
(select PersonId, MIN(startdate) as first_startdate from staff group by PersonId)
,cte_last_enddate (PersonID, last_enddate)
as
(select PersonId, MAX(enddate) as last_enddate from staff group by PersonId)
,cte_last_title (PersonID, last_title)
as
(select a.PersonId, Title from #staff a join cte_last_enddate b on a.PersonId=b.PersonID and a.EndDate=b.last_enddate)
select distinct
a.PersonId, a.Name
,b.first_startdate
,c.last_enddate
,d.last_title
from staff a
join cte_first_date b on a.PersonId=b.PersonId
join cte_last_enddate c on a.PersonId=c.PersonId
join cte_last_title d on a.PersonId=d.PersonID

Related

Sql select distinct row by a columns highest value

I am having an issue trying to select one row per city name. This is the following collection I am getting:
This is my query so far:
select pl.PlaceId,
pl.Name,
pop.NumberOfPeople,
pop.Year
from dbo.Places pl
inner join dbo.Populations pop
on pop.PlaceId = pl.PlaceId
where pop.NumberOfPeople >= 1000
and pop.NumberOfPeople <= 99999
I am trying to get it to where it only selects a city one time, but uses the most recent date. So in the above picture, I would only see Abbeville for 2016 and not 2015. I believe I need to do either a group by or do a sub query to flatten the results. If anybody has any advice on how I can handle this, it will be greatly appreciated.
Assuming you are using SQLSERVER,you can use Rownumber
;with cte
as
(select pl.PlaceId,
pl.Name,
pop.NumberOfPeople,
pop.Year,
row_number() over(partition by pl.Name order by year desc) as rownum
from dbo.Places pl
inner join dbo.Populations pop
on pop.PlaceId = pl.PlaceId
where pop.NumberOfPeople >= 1000
and pop.NumberOfPeople <= 99999
)
select * from cte where rownum=1
The following query serves the purpose.
CREATE TABLE #TEMP_TEST
(
PlaceId INT,
Name VARCHAR(50),
NumberOfPeople INT,
YEAR INT
)
INSERT INTO #TEMP_TEST
SELECT 1,'Abbeville',2603,2016
UNION
SELECT 5,'Alabester',32948,2016
UNION
SELECT 9,'Aubum',63118,2016
UNION
SELECT 1,'Abbeville',2402,2015
UNION
SELECT 5,'Alabester',67902,2017
SELECT PlaceId, Name, NumberOfPeople, YEAR FROM
(
SELECT ROW_NUMBER() OVER (PARTITION BY PlaceId ORDER BY YEAR DESC) RNO,
PlaceId, Name, NumberOfPeople, YEAR
FROM #TEMP_TEST
)T
WHERE RNO = 1
DROP TABLE #TEMP_TEST

SQL Server : finding consecutive absence counts for students over custom dates

I have a table which stores attendances of students for each day. I need to get students who are consecutively absent for 3 days. However, the dates when attendance is taken is not in a order, some days like nonattendance, holidays, weekends are excluded. The dates when students attended are the dates where records exist in that table .
The data is like
StudentId Date Attendance
-----------------------------------------
178234 1/1/2017 P
178234 5/1/2107 A
178234 6/1/2107 A
178234 11/1/2107 A
178432 1/1/2107 P
178432 5/1/2107 A
178432 6/1/2107 P
178432 11/1/2107 A
In the above case the result should be
StudentId AbsenceStartDate AbsenceEndDate ConsecutiveAbsences
----------------------------------------------------------------------------
178234 5/1/2017 11/1/2017 3
I have tried to implement this solution Calculating Consecutive Absences in SQL However that only worked for dates in order only. Any suggestions will be great, thanks
Oh, you have both absences and presents in the table. You can use the difference of row_numbers() approach:
select studentid, min(date), max(date)
from (select a.*,
row_number() over (partition by studentid order by date) as seqnum,
row_number() over (partition by studentid, attendance order by date) as seqnum_a
from attendance a
) a
where attendance = 'A'
group by studentid, (seqnum - seqnum_a)
having count(*) >= 3;
The difference of row numbers gets consecutive values that are the same. This is a little tricky to understand, but if you run the subquery, you should see how the difference is constant for consecutive absences or presents. You only care about absences, hence the where in the outer query.
try this:
declare #t table (sid int, d date, att char(1))
insert #t (sid,d, att) values
(178234, '1/1/2017','P'),
(178234, '5/1/2017','A'),
(178234, '6/1/2017','A'),
(178234, '11/1/2017','A'),
(178432, '1/1/2017','P'),
(178432, '5/1/2017','A'),
(178432, '6/1/2017','P'),
(178432, '11/1/2017','A')
Select s.sid, Min(s.d) startDt, Max(e.d) endDt, s.att, e.att, count(*)
from #t s join #t e on e.d <=
(select max(d) from #t m
Where sid = s.sid
and d > s.d
and att = 'A'
and not exists
(Select * from #t
where sid = s.sid
and d between s.d and m.d
and att = 'P'))
Where s.att = 'A'
and s.d = (Select Min(d) from #t
Where sid = s.sid
and d < e.d
and att = 'A')
group by s.sid, s.d, s.att, e.att
this is also tricky to explain:
basically, it joins the table to itself using aliases s (for start) and e (for end), where the s-row is the first row in a set of contiguous absences, and the e. rows are all following absences that are before the next date where the stud is present. This will generate a set of all the 'A' that do not have a P row within them. Then the sql groups by the appropriate values to return the earliest and latest date, and the count of rows, in each group.
The last where clause ensures that the s row is the first row in the group.

select the latest result based on DateTime field

I have a simple table with only 4 fields.
http://sqlfiddle.com/#!3/06d7d/1
CREATE TABLE Assessment (
id INTEGER IDENTITY(1,1) PRIMARY KEY,
personId INTEGER NOT NULL,
dateTaken DATETIME,
outcomeLevel VARCHAR(2)
)
INSERT INTO Assessment (personId, dateTaken, outcomeLevel)
VALUES (1, '2014-04-01', 'L1')
INSERT INTO Assessment (personId, dateTaken, outcomeLevel)
VALUES (1, '2014-04-05', 'L2')
INSERT INTO Assessment (personId, dateTaken, outcomeLevel)
VALUES (2, '2014-04-03', 'E3')
INSERT INTO Assessment (personId, dateTaken, outcomeLevel)
VALUES (2, '2014-04-07', 'L1')
I am trying to select for each "personId" their latest assessment result based on the dateTaken.
So my desired output for the following data would be.
[personId, outcomeLevel]
[1, L2]
[2, L1]
Thanks,
Danny
Try this:
;with cte as
(select personId pid, max(dateTaken) maxdate
from assessment
group by personId)
select personId, outcomeLevel
from assessment a
inner join cte c on a.personId = c.pid
where c.maxdate = a.dateTaken
order by a.personId
;with Cte as (Select personId,outcomeLevel, C= ROW_NUMBER()
over(PARTITION By personId Order By dateTaken desc)
From #Assessment
)
Select * from cte where C=1
Sample here
SELECT asst.personId,
asst.outcomeLevel
FROM dbo.Assessment asst
WHERE asst.dateTaken=(SELECT MAX(ast.dateTaken)
FROM assessment ast
WHERE asst.personid=ast.personId)
ORDER BY asst.personId
Result will be like this
personId outcomeLevel
1 L2
2 L1
Here is a possible solution using common table expression:
WITH cte AS (
SELECT
ROW_NUMBER() OVER (PARTITION BY personId ORDER BY dateTaken DESC) AS rn
, personId
, outcomeLevel
FROM
[dbo].[Assessment]
)
SELECT
personId
, outcomeLevel
FROM
cte
WHERE
rn = 1
About CTEs
A common table expression (CTE) can be thought of as a temporary result set that is defined within the execution scope of a single SELECT, INSERT, UPDATE, DELETE, or CREATE VIEW statement. A CTE is similar to a derived table in that it is not stored as an object and lasts only for the duration of the query. Unlike a derived table, a CTE can be self-referencing and can be referenced multiple times in the same query. From MSDN: Using Common Table Expressions
try this:
SELECT a.personId, a.outcomeLevel
FROM Assessment a
INNER JOIN
(
SELECT max(dateTaken) as datetaken1, personId
FROM Assessment
GROUP BY personId ) b
ON a.dateTaken = b.datetaken1
demo: http://sqlfiddle.com/#!3/06d7d/9
Idea is to first derive a table with the max dates per person and then join that with the original table on the date field so you can get the outcome level for this maxed date...
This should work perfectly without cte :
SELECT [Table4].[personId], [Table4].[outcomeLevel]
FROM (
SELECT [Table1].[personId]
FROM [Assessment] AS [Table1]
GROUP BY [Table1].[personId]
) AS [Table2]
CROSS APPLY (
SELECT TOP (1) [Table3].[personId], [Table3].[outcomeLevel], [Table3].[dateTaken]
FROM [Assessment] AS [Table3]
WHERE [Table2].[personId] = [Table3].[personId]
ORDER BY [Table3].[dateTaken] DESC
) AS [Table4]
ORDER BY [Table4].[dateTaken] DESC

Query last event each person attended

I have a table called CampRegistration with the following structure:
[EID] INT, [CampName] VARCHAR(100), [StartDt] DATETIME
EID is the identifier of the person who attended the camp. I wish to select the last camp each person in the CampRegistration table has attended prior to a given date.
Here was my first attempt:
#DECLARE #currentCampDt DATETIME = '2012-8-4';
SELECT [EID], [CampName], MAX([StartDt]) [StartDt]
FROM [CampRegistration]
WHERE [StartDt] < #currentCampDt
GROUP BY [EID],[CampName]
order by [EID]
The problem here is that if someone has attended multiple camps w/ different names I'll get multiple results for that person (the last attended of each camp name). I only wish to get a single record back for each person, the last camp of any name they attended. I do ultimately need to get those three pieces of info for each record (EID, CampName, and the camp's StartDt) so I'm not sure if I really can remove CampName from the Group By.
I'm using SQL Server 2012 and would greatly appreciate any suggestions on how to implement this type of query result.
Thanks.
One approach would be to use a CTE (Common Table Expression).
With this CTE, you can partition your data by some criteria - i.e. your EID - and have SQL Server number all your rows starting at 1 for each of those "partitions", ordered by some criteria.
So try something like this:
;WITH CampEvents AS
(
SELECT
EID, CampName, StartDt,
RowNum = ROW_NUMBER() OVER(PARTITION BY EID ORDER BY StartDt DESC)
FROM
dbo.CampRegistration
)
SELECT
EID, CampName, StartDt
FROM
CampEvents
WHERE
RowNum = 1
Here, I am selecting only the "first" entry for each "partition" (i.e. for each EID) - ordered by the descending StartDt - so the newest, most recent event has RowNum = 1.
Does that approach what you're looking for??
This won't be fast, but will do it:
#DECLARE #currentCampDt DATETIME = '2012-8-4';
SELECT o.[EID], o.[CampName], o.[StartDt]
FROM [CampRegistration] o
WHERE o.[StartDt] = (
SELECT MAX(i.[StartDt])
FROM [CampRegistration] i WHERE i.[StartDt] < #currentCampDt
AND
i.[EID] = o.[EID]
)
order by o.[EID]
You can do this:
WITH LastCampsAttended
As
(
SELECT *,
ROW_NUMBER() OVER(PARTITION BY EID ORDER BY StartDt DESC) AS rownum
FROM CampRegistration
WHERE [StartDt] < #currentCampDt
)
SELECT
EID,
CampName,
StartDt
FROM LastCampsAttended
WHERE rownum = 1;
SQL Fiddle Demo
Or:
SELECT
camps.*
FROM CampRegistration camps
INNER JOIN
(
SELECT EID, MAX(StartDt) LatestDate
FROM CampRegistration
GROUP BY EID
) LatestCamps ON camps.EID = LatestCamps.EID
AND camps.StartDt = LatestCamps.LatestDate
WHERE camps.StartDt < #currentCampDt ;
Updated SQL fiddle Demo

Sql Server Find Next Most Recent Changed Record

In my employee history table I'm trying to find what the salary was and then what it was changed to. Each Salary change inserts a new record because the new salary is considered a new "job" so it has a start and end date attached. I can select all these dates fine but I keep getting duplicates because I can't seem to compare the current record only against its most recent prior record for that employee. (if that makes sense)
I would like the results to be along the lines of:
Employe Name, OldSalary, NewSalary, ChangeDate(EndDate)
Joe 40,000 42,000 01/10/2011
Example data looks like
EmployeeHistId EmpId Name Salary StartDate EndDate
1 45 Joe 40,000.00 01/05/2011 01/10/2011
2 45 Joe 42,000.00 01/11/2011 NULL
3 46 Bob 20,000.00 01/12/2011 NULL
The Swiss army ROW_NUMBER() to the rescue:
with cte as (
select EmployeeHistId
, EmpId
, Name
, Salary
, StartDate
, EndDate
, row_number () over (
partition by EmpId order by StartDate desc) as StartDateRank
from EmployeeHist)
select n.EmpId
, n.Name
, o.Salary as OldDalary
, n.Salary as NewSalary
, o.EndData as ChangeDate
from cte n
join cte o on o.EmpId = n.EmpId
and n.StartDateRank = 1
and o.StartDateRank = 2;
Use outer join to get employees that never got a raise too.
These kind of queries are always tricky because of data purity issues, if StartDate and EndDate overlap for instance.
I assume the StartDate and EndDate will be same for the new job and previous job.
If thats the case try this.
SELECT a.Name AS EmployeeName, b.Salary AS NewSalary a.Salary AS NewSalary, a.StartDate AS ChangeDate
FROM EMPLOYEE A, EMPLOYEE B
WHERE a.EmpID = b.EmpID
AND a.EndDate IS NULL
AND a.StartDate = b.EndDate
You can use the correlated join operator APPLY which can solve these types of challenges easily
select a.name, curr.salary, prev.salary, prev.enddate
from employee e
cross apply ( -- to get the current
select top(1) *
from emphist h
where e.empid = h.empid -- related to the employee
order by startdate desc) curr
outer apply ( -- to get the prior, if any
select top(1) *
from emphist h
where e.empid = h.empid -- related to the employee
and h.EmployeeHistId <> curr.EmployeeHistId -- prevent curr=prev
order by enddate desc) prev -- last ended