SQL join two record into one row with multiple column - sql

i want to join two record (from same table) into one row with multiple column.
employment history structure as follows:
StaffID StartDate EndDate DeptID
==================================================
1 2010-10-01 2011-01-19 1
1 2011-01-20 2012-12-31 2
1 2013-01-01 2013-05-29 4
how can i join the two rows into one row if same StaffID and the 2nd record startdate is 1 day after the enddate of 1st record (continuous employment)
the output should like this
StaffID EffectiveDate New_DeptID Prev_DeptID
==================================================
1 2011-01-20 2 1
1 2013-01-01 4 2
the following is my sql statement but it doesn't work
select distinct
ca1.StaffID,
ca1.ProjectDepartment as Prev_DeptID, ca1.StartDate, ca1.EndDate,
ca2.ProjectDepartment as New_DeptID, ca2.StartDate, ca2.EndDate
from
emp_hist as ca1,
emp_hist as ca2
where
(ca1.StaffID = ca2.StaffID)
and ca1.StartDate<>ca2.StartDate
and ca1.EndDate <>ca2.EndDate
and ca2.startdate= DATEADD(day, 1, ca1.enddate)
for example,
two records (true data) in the table:
StaffID StartDate EndDate DeptID
===========================================================================
1 2010-04-12 12:00:00.000 2013-02-28 00:00:00.000 1
1 2013-03-01 12:00:00.000 2013-08-29 11:02:59.877 2
i cannot retrieve this record by using my sql statement

Your problem is that the dates have a time component. You appear to be using SQL Server. You can fix your query by doing this:
select ca1.StaffID,
ca1.ProjectDepartment as Prev_DeptID, ca1.StartDate, ca1.EndDate,
ca2.ProjectDepartment as New_DeptID, ca2.StartDate, ca2.EndDate
from emp_hist as ca1 join
emp_hist as ca2
on ca1.StaffID = ca2.StaffID and
cast(ca1.StartDate as date) <> cast(ca2.StartDate as date) and
cast(ca1.EndDate as date) <> cast(ca2.EndDate as date) and
cast(ca2.startdate as date) = DATEADD(day, 1, cast(ca1.enddate as date));
I also replaced the implicit join with improved join syntax.

If you're using SQL 2012 try the lag functions.
select distinct
ca1.StaffID,
ca1.EndDate,
ca1.ProjectDepartment as New_DeptID,
LAG(ca1.ProjectDepartment) OVER (PARTITION BY ca1.StaffId ORDER BY ca1.EndDate) as Prev_DeptID
from
emp_hist as ca1
If you're not, use the RANK function and a subquery
select
eh.StaffID,
eh.EndDate,
eh.ProjectDepartment as New_DeptID,
eh1.ProjectDepartment as Prev_DeptID
from
(select *, RANK(EndDate) OVER (PARTITION BY StaffId ORDER BY EndDate) as Rank
from emp_hist) eh left join (
select distinct
StaffID,
EndDate,
ProjectDepartment,
RANK(EndDate) OVER (PARTITION BY StaffId ORDER BY EndDate) as Rank
from
emp_hist) eh1 on eh1.staffid=a.staffid and eh1.rank=eh.rank-1

Related

Calculate MinDate and MaxDate based on table relationship

Following is the condition to write a create a query has per the requirement.
Visit 1 Date for the Patient(Abc) should be calculated fom the "Screening" visit of the patient.
For example if the Patient (Abc) has visited on 23/Mar/2019 then with Min Date (22/Mar/2019) and MaxDate (25/Mar/2019).
In the VisitWindow I am linking the VisitWindowId to VisitId in the table for VisitEntry.
So if you see the visitWindowId you can see that I have mention MinDays (1) and MaxDays(2) which is to be calculated using the VisitDate for the VisitName is equal to "Screening".
For example I expecting the query or write the query to give the below result.
I struck with writing the desired query to get the result
Table - VisitEntry
--------------------
RecordId VisitId VisitName VisitDate PatientId PatientName
1 1 Screening 23/Mar/2019 100 Abc
2 2 Visit 1 Date 23/Mar/2019 100 Abc
Table - VisitWindow
-------------------
RecordId VisitId VisitWindowId MinDays MaxDays
1 2 1 1 2
Expected QueryResult
--------------------
RecordId VisitId VisitName VisitDate PatientId MinDate MaxDate
1 1 Screening 23/Mar/2019 100 NUll Null
2 2 Visit 1 Date 23/Mar/2019 100 22/Mar/2019 25/Mar/2019
You didn't mention the db, so I'll give you 2.
SQL Server:
SELECT ve.RecordID, ve.VisitID, ve.VisitName, ve.VisitDate, ve.PatientID, ve.PatientName,
dateadd(d, sd.VisitDate, -1 * vw.MinDays) MinDate,
dateadd(d, sd.VisitDate, vw.MaxDays) MaxDate
FROM VisitEntry ve
LEFT JOIN VisitEntry sd ON (ve.PatientId=sd.PatientId AND sd.VisitName='Screening')
LEFT JOIN VisitWindow vw ON ve.VisitID=vw.VisitID
Oracle:
SELECT ve.RecordID, ve.VisitID, ve.VisitName, ve.VisitDate, ve.PatientID, ve.PatientName,
sd.VisitDate - vw.MinDays MinDate,
sd.VisitDate + vw.MaxDays MaxDate
FROM VisitEntry ve
LEFT JOIN VisitEntry sd ON (ve.PatientId=sd.PatientId AND sd.VisitName='Screening')
LEFT JOIN VisitWindow vw ON ve.VisitID=vw.VisitID

Get Room Price of Overlapping dates [closed]

Closed. This question needs to be more focused. It is not currently accepting answers.
Want to improve this question? Update the question so it focuses on one problem only by editing this post.
Closed 5 years ago.
Improve this question
I have table in this structure:
id accom_id room_type_id date_from date_to price_per_room
3 1 2 2017-09-01 2017-09-10 70.00
5 1 2 2017-09-11 2017-09-20 100.00
Lets say I want to stay from 2017-09-07 to 2017-09-15. So with DATEDIFF I need to count how many days the price is 70 and how many days the price is 100. At the end I want to show the total.
Can anyone help me build this query? I hope its clear what ask!
Assuming no overlapping ranges are defined, and assuming that all of the ranges given are meant to be inclusive, we can obtain the data using a CTE and then a simple matter of aggregation:
declare #t table (date_from date,date_to date, price_per_room int)
insert into #t (date_from,date_to,price_per_room) values
('20170901','20170910',70.00 ),
('20170911','20170920',100.00)
declare #Start date
declare #End date
select #Start = '20170907',#End = '20170915'
;With IncludedPeriods as (
select
CASE WHEN #Start > date_from THEN #Start ELSE date_from END as fromDT,
CASE WHEN #End < date_to THEN #End ELSE date_to END as ToDT,
price_per_room
from
#t
where
date_from <= #End and
#Start <= date_to
)
select
SUM(price_per_room * (1 + DATEDIFF(day,fromDT,ToDT)))
from
IncludedPeriods
Note that we're adding one to the DATEDIFF result since it counts transitions, but I'm assuming that a period from '20170911' to '20170911' should count as one day and longer periods similarly.
Unlike some of the other answers which attempt to enumerate various "cases" for overlaps, this uses the simple rule - two periods overlap if the first starts before the second ends and if the second starts before the first ends - that's the logic applied in the where clause inside the CTE. To determine the extent of the overlap, we take the later of the two start dates and the earlier of the two end dates - that's what the CASE expressions are doing. If we had scalar MIN and MAX functions that operated on dates I'd prefer to use those but no such functions built into SQL Server.
In SQL Server you can do this:
select dateadd(d, number, '20170801')
from master.dbo.spt_values
where type='P' and (number <= datediff(d, '20170801', '20170810'))
You calculate the total days (in the where clause) and you select the numbers from 0 to total days (master.dbo.spt_values is a helpful table in SQL Server). Otherwise you can create a table with ascending numbers from 0 to ?). And then you add to the startdate the number. So you get all days of the booking:
2017-08-01
2017-08-02
2017-08-03
2017-08-04
2017-08-05
2017-08-06
2017-08-07
2017-08-08
2017-08-09
2017-08-10
Then you can join your given price_table on date_from and date_to and you can calculate your sum.
EDIT: Just to complete it... :-)
SELECT SUM(price_per_room)
FROM master.dbo.spt_values
LEFT OUTER JOIN room_prices AS rp ON (date_from <= DATEADD(d, number, my_start_date) AND DATEADD(d, number, my_start_date) <= date_to)
WHERE type='P' AND (number <= DATEDIFF(d, my_start_date, my_end_date))
I would suggest using a Date table(table containing all dates) or a derived table to populate these dates(you can find it online easily) like this:
SELECT p.id,p.room_type_id,sum(p.price_per_room)
FROM(
SELECT t.date,s.price_per_room,s.id,s.room_type_id
FROM (DATES QUERY \ DATE_TABLE) t
LEFT JOIN s.yourTable
ON(t.date between s.date_from and s.date_to)) p
WHERE p.id = YourID and p.room_type_id = YourTypeId
AND p.date between yourStartDate and yourEndDate
GROUP BY p.id,p.room_type_id
That populate a row for each date inside the range of dates for the price, so it will know how much is the price per day, and then just sum the price between your desired dates.
You can use a calendar or dates table for this sort of thing.
Without taking the actual step of creating a table, you can use an adhoc tables of dates with a common table expression like so:
declare #fromdate date = '20170907';
declare #thrudate date = '20170915';
;with n as (select n from (values(0),(1),(2),(3),(4),(5),(6),(7),(8),(9)) t(n))
, dates as (
select top (datediff(day, #fromdate, #thrudate)+1)
[Date]=convert(date,dateadd(day,row_number() over(order by (select 1))-1,#fromdate))
from n as deka cross join n as hecto cross join n as kilo
cross join n as tenK cross join n as hundredK
order by [Date]
)
select total_price = sum(price_per_room)
from dates d
inner join t
on d.date >= t.date_from
and d.date <= t.date_to
rextester demo: http://rextester.com/MCF52261
returns:
+-------------+
| total_price |
+-------------+
| 780 |
+-------------+
For a more detailed breakdown of the price and dates, you can swap the above select for this one:
select
fromdate = min(date)
, thrudate = max(date)
, days = count(*)
, price_per_room = avg(price_per_room)
, total = sum(price_per_room)
from dates d
inner join t
on d.date >= t.date_from
and d.date <= t.date_to
group by grouping sets ((price_per_room),())
rextester demo: http://rextester.com/NKZD1468
returns:
+------------+------------+------+----------------+-------+
| fromdate | thrudate | days | price_per_room | total |
+------------+------------+------+----------------+-------+
| 2017-09-07 | 2017-09-10 | 4 | 70 | 280 |
| 2017-09-11 | 2017-09-15 | 5 | 100 | 500 |
| 2017-09-07 | 2017-09-15 | 9 | 86 | 780 |
+------------+------------+------+----------------+-------+
Number and Calendar table reference:
Generate a set or sequence without loops - 2 - Aaron Bertrand
The "Numbers" or "Tally" Table: What it is and how it replaces a loop - Jeff Moden
Creating a Date Table/Dimension in sql Server 2008 - David Stein
Calendar Tables - Why You Need One - David Stein
Creating a date dimension or calendar table in sql Server - Aaron Bertrand
Using UNION ALL for all four possible cases may do the job as well
SELECT SUM(x.p)
FROM
(
SELECT (DATEDIFF(DAY, #from, date_to)+1)* price p
FROM tab
WHERE date_to BETWEEN #from AND #to AND NOT date_from BETWEEN #from AND #to
UNION ALL
SELECT ISNULL(SUM((DATEDIFF(DAY, date_from, date_to)+1)* price), 0) p
FROM tab
WHERE date_to BETWEEN #from AND #to AND date_from BETWEEN #from AND #to
UNION ALL
SELECT (DATEDIFF(DAY, date_from, #to)+1)* price p
FROM tab
WHERE date_from BETWEEN #from AND #to AND NOT date_to BETWEEN #from AND #to
UNION ALL
-- in the case that the (#from, #to) interval is completely in one row interval
SELECT (DATEDIFF(DAY, #from, #to)+1)* price p
FROM tab
WHERE date_from <= #from AND date_to >= #to
)x;
sqlfiddle demo

SQL count per day and add together all following days

I'm trying to write a SQL query to count users per day and add the result together. I'm stuck because I don't know how to dynamically add the results together.
Table:
UserId | CreateDate
-------+----------------------------
1 | 2016-06-23 13:59:24.0000000
2 | 2016-06-23 15:59:24.0000000
3 | 2016-06-24 05:59:24.0000000
...
I have following query to count the registrations per day.
SELECT
CONVERT(date, u.CreateDate) as [Date], Count(UserId) as [Count]
FROM
User as u
GROUP BY
CONVERT(date, u.CreateDate)
ORDER BY
[Date]
I have no idea how to add up the results. I hope that SQL Server 2008 R2 has a built-in function for my case. Or do I need to create temp tables?
Assuming Day 1 has 2 registrations and Day 2 has 1 registration and Day 3 has 4 registrations, I would like to receive a result like this:
2016-06-23 | 2
2016-06-24 | 2 + 1 = 3
2016-06-25 | 3 + 4 = 7
I would need a dynamic solution because the query will have another restriction by date => Where CreateDate bewteen x and y.
Thanks
Ruedi
You can use sum window function to get the running total.
select [Date],sum([Count]) over(order by [Date])
from (SELECT CONVERT(date, u.CreateDate) as [Date], Count(UserId) as [Count]
FROM User as u
GROUP BY CONVERT(date, u.CreateDate)
) x
order by 1
Add a where clause to filter for a specific date range in the inner query.
with redshift database
select [Date],sum([Count]) over(order by [Date] ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW)
from (SELECT CONVERT(date, u.CreateDate) as [Date], Count(UserId) as [Count]
FROM User as u
GROUP BY CONVERT(date, u.CreateDate)
) x
order by 1

Open Ticket Count Per Day

I have a table that looks like this
id | Submit_Date | Close_Date
------------------------------
1 | 2015-02-01 | 2015-02-05
2 | 2015-02-02 | 2015-02-04
3 | 2015-02-03 | 2015-02-05
4 | 2015-02-04 | 2015-02-06
5 | 2015-02-05 | 2015-02-07
6 | 2015-02-06 | 2015-02-07
7 | 2015-02-07 | 2015-02-08
I can get a count of how many ticket were open on a particular day with this:
Select count(*) from tickets where '2015-02-05' BETWEEN Submit_Date and Close_Date
This gives me 4, but I need this count for each day of a month. I don't want to have to write 30 queries to handle this. Is there a way to capture broken down by multiple days?
I created a solution a way back using a mix of #Heinzi s solution with the trick from Generate a resultset of incrementing dates in TSQL
declare #dt datetime, #dtEnd datetime
set #dt = getdate()
set #dtEnd = dateadd(day, 100, #dt)
SELECT dates.myDate,
(SELECT COUNT(*)
FROM tickets
WHERE myDate BETWEEN Submit_Date and Close_Date
)
FROM
(select Dates_To_Checkselect dateadd(day, number, #dt) mydate
from
(select distinct number from master.dbo.spt_values
where name is null
) n
where dateadd(day, number, #dt) < #dtEnd) dates
Code is combined from memory, I don't have it in front of me so there can be some typo's
First, you'll need a table that contains each date you want to check. You can use a temporary table for that. Let's assume that this table is called Dates_To_Check and has a field myDate:
SELECT myDate,
(SELECT COUNT(*)
FROM tickets
WHERE myDate BETWEEN Submit_Date and Close_Date)
FROM Dates_To_Check
Alternatively, you can create a huge table containing every possible date and use a WHERE clause to restrict the dates to those you are interested in.
If you're in SQL Server 2012 or newer you can do this using window functions with a small trick where you add 1 to the open days -1 to the closing days and then do a running total of this amount:
select distinct date, sum(opencnt) over (order by date) from (
select
Submit_Date as date,
1 as opencnt
from
ticket
union all
select
dateadd(day, 1, Close_Date),
-1
from
ticket
) TMP
There's a dateadd + 1 day to include the close date amount to that day
You could generate the list of dates and then retrieve the count for each date in your dateset.
The cte part generates the date list since the beginning of the year (an ssumption) and the next part calculates the count from your data set.
with cte as
(select cast('2015-01-01' as date) dt // you should change this part to the correct start date
union all
select dateadd(DD,1,dt) dt from cte
where dt<getdate()
)
select count(*)
from tickets
inner join cte
on cte.dt between Submit_Date and Close_Date
group by cte.dt

Finding a sql query to get the latest associated date for each grouping

I have a sql table of payroll data that has wage rates and effective dates associated with those wage rates, as well as hours worked on various dates. It looks somewhat like this:
EMPID DateWorked Hours WageRate EffectiveDate
1 1/1/2010 10 7.00 6/1/2009
1 1/1/2010 10 7.25 6/10/2009
1 1/1/2010 10 8.00 2/1/2010
1 1/10/2010 ...
2 1/1/2010 ...
...
And so on. Basically, the data has been combined in such a way that for every day worked, all of the employee's wage history is joined together, and I want to grab the wage rate associated with the LATEST effective date that is not later than the date worked. So in the example above, the rate of 7.25 that become effective on 6/10/2009 is what I want.
What kind of query can I put together for this? I can use MAX(EffectiveDate) alongwith a criteria based on being before the work date, but that only gives me the latest date itself, I want the associated wage. I am using Sql Server for this.
Alternatively, I have the original tables that were used to create this data. One of them contains the dates worked, and the hours as well as EMPID, the other contains the list of wage rates and effective dates. Is there a way to join these instead that would correctly apply the right wage rate for each work day?
I was thinking that I'd want to group by EMPID and then DateWorked, and do something from there. I want to get a result that gives me the wage rate that actually is the latest effective rate for each date worked
select p.*
from (
select EMPID, DateWorked, Max(EffectiveDate) as MaxEffectiveDate
from Payroll
where EffectiveDate <= DateWorked
group by EMPID, DateWorked
) pm
inner join Payroll p on pm.EMPID = p.EMPID and pm.DateWorked = p.DateWorked and pm.MaxEffectiveDate = p.EffectiveDate
Output:
EMPID DateWorked Hours WageRate EffectiveDate
----------- ----------------------- ----------- --------------------------------------- -----------------------
1 2010-01-01 00:00:00.000 10 7.25 2009-06-10 00:00:00.000
try this:
DECLARE #YourTable table (EMPID int, DateWorked datetime, Hours int
,WageRate numeric(6,2), EffectiveDate datetime)
INSERT INTO #YourTable VALUES (1,'1/1/2010' ,10, 7.00, '6/1/2009')
INSERT INTO #YourTable VALUES (1,'1/1/2010' ,10, 7.25, '6/10/2009')
INSERT INTO #YourTable VALUES (1,'1/1/2010' ,10, 8.00, '2/1/2010')
INSERT INTO #YourTable VALUES (1,'1/10/2010',10, 20.00,'12/1/2010')
INSERT INTO #YourTable VALUES (2,'1/1/2010' ,8 , 12.00, '2/1/2009')
SELECT
e.EMPID,e.WageRate,e.EffectiveDate
FROM #YourTable e
INNER JOIN (SELECT
EMPID,MAX(EffectiveDate) AS EffectiveDate
FROM #YourTable
WHERE EffectiveDate<GETDATE()+1
GROUP BY EMPID
) dt ON e.EMPID=dt.EMPID AND e.EffectiveDate=dt.EffectiveDate
ORDER BY e.EMPID
OUTPUT
EMPID WageRate EffectiveDate
----------- --------------------------------------- -----------------------
1 8.00 2010-02-01 00:00:00.000
2 12.00 2009-02-01 00:00:00.000
(2 row(s) affected)
Something like this ought to work:
SELECT T.* FROM T
INNER JOIN (
SELECT EMPID, MAX(EFFECTIVEDATE) EFFECTIVEDATE
FROM T
WHERE DATEWORKED <= EFFECTIVEDATE
GROUP BY EMPID) t2
ON T2.EMPID = T.EMPID
AND T2.EFFECTIVEDATE = T.EFFECTIVEDATE
SELECT TOP 1 EMPID, WageRate
FROM wages
WHERE ......
ORDER BY EffectiveDate DESC