How can I get the next record date from a date and the last record date from a date? - sql

I create table Appointments with this structure:
CREATE TABLE Appointments
(
[Id] bigint,
[Name] varchar(250),
[DateInit] date
);
INSERT INTO Appointments ([Id], [Name], [DateInit])
values
(1000, 'Lorena', '03/06/2016'),
(1000, 'Lorena', '01/06/2016'),
(1000, 'Lorena', '08/06/2016'),
(1000, 'Lorena', '10/06/2016'),
(1000, 'Lorena', '02/06/2016'),
(1000, 'Lorena', '20/06/2016'),
(7000, 'Susan', '04/06/2016'),
(7000, 'Susan', '08/06/2016'),
(7000, 'Susan', '09/06/2016'),
(7000, 'Susan', '01/06/2016');
This is the final result:
I need to get the result for the next day and the day before, for example if today is '03/06/2016' I need to get result for the last appointment inserted in the table from today and the next appointment inserted in the table from today, the result I need is something like this:
Name Last Visit Next Visit
----- ---------- -----------
Lorena 2016-06-02 2016-06-08
Susan 2016-06-01 2016-06-04
How can I get this result?
Thanks

Do a GROUP BY, use case expressions to pick max previous appointment, and min future appointment:
select name,
max(case when DateInit < CONVERT(DATE,GETDATE()) then DateInit end) as LastVisit,
min(case when DateInit > CONVERT(DATE,GETDATE()) then DateInit end) as NextVisit
from Appointments
group by name

I'd do this as joins to the previous and next visit, something like this;
SELECT DISTINCT
a.ID
,a.NAME
,l.LastVisit
,n.NextVisit
FROM Appointments a
LEFT JOIN (
SELECT ID
,MIN(DateInit) NextVisit
FROM Appointments
WHERE DateInit > GETDATE()
GROUP BY ID
) n ON a.ID = n.ID
LEFT JOIN (
SELECT ID
,MAX(DateInit) LastVisit
FROM Appointments
WHERE DateInit < GETDATE()
GROUP BY ID
) l ON a.ID = l.ID

DECLARE #Appointments TABLE
(
[Id] bigint,
[Name] varchar(250),
[DateInit] date
);
INSERT INTO #Appointments ([Id], [Name], [DateInit])
values
(1000, 'Lorena','2016/06/03'),
(1000, 'Lorena','2016/06/01'),
(1000, 'Lorena','2016/06/08'),
(1000, 'Lorena','2016/06/10'),
(1000, 'Lorena','2016/06/02'),
(1000, 'Lorena','2016/06/20'),
(7000, 'Susan', '2016/06/04'),
(7000, 'Susan', '2016/06/08'),
(7000, 'Susan', '2016/06/09'),
(7000, 'Susan', '2016/06/01');
DECLARE #Today DATE = GETDATE();
WITH CTE
AS (
SELECT A.NAME
,ROW_NUMBER() OVER (
PARTITION BY ID ORDER BY ID
) RN
,(
SELECT TOP 1 DateInit
FROM #Appointments B
WHERE B.ID = A.ID
AND DateInit < #TODAY
ORDER BY DateInit DESC
) [Last Visit]
,(
SELECT TOP 1 DateInit
FROM #Appointments B
WHERE B.ID = A.ID
AND DateInit > #TODAY
ORDER BY DateInit
) [Next Visit]
FROM #Appointments A
--GROUP BY ID
)
SELECT C.NAME
,C.[Last Visit]
,C.[Next Visit]
,RN
FROM CTE C
WHERE RN = 1

Related

SQL Query to get the value of a product given a date

I have a table which gives the rate of a product on a particular date, #tableA.
create table #tableA
(
Id int not null,
ValueDate date,
Price decimal(9,2)
)
insert into #tableA (Id, ValueDate, Price)
values
(1, '2020-08-01', 100),
(1, '2020-08-05', 110),
(1, '2020-08-07', 50)
My other table has the id and the date the product is active.
create table #tableB
(
Id int not null,
Dates date
)
insert into #tableB (Id, Dates)
values
(1, '2020-08-01'),
(1, '2020-08-02'),
(1, '2020-08-03'),
(1, '2020-08-04'),
(1, '2020-08-05'),
(1, '2020-08-06'),
(1, '2020-08-07'),
(1, '2020-08-04')
I cannot find an efficient query where my resulting table gives the rate of the product on a given date.
I am expecting this result.
Id Dates ValueDate Price
-------------------------------------
1, '2020-08-01', '2020-08-01', 100
1, '2020-08-02', '2020-08-01', 100
1, '2020-08-03', '2020-08-01', 100
1, '2020-08-04', '2020-08-01', 100
1, '2020-08-05', '2020-08-05', 110
1, '2020-08-06', '2020-08-05', 110
1, '2020-08-07', '2020-08-07', 50
Something like this:
SELECT DISTINCT B.[id]
,B.[Dates]
,DS.*
FROM #tableB B
CROSS APPLY
(
SELECT TOP 1 *
FROM #tableA A
WHERE B.[Id] = A.[Id]
AND B.[Dates] >= A.[ValueDate]
AND A.[Price] IS NOT NULL
ORDER BY A.[ValueDate] DESC
) DS;
or this:
WITH DataSource AS
(
SELECT DISTINCT B.[ID]
,B.[Dates]
,A.[ValueDate]
,A.[Price]
,SUM(IIF(A.[ID] IS NOT NULL, 1, 0)) OVER (ORDER BY B.[Dates]) AS [GroupID]
FROM #tableB B
LEFT JOIN #tableA A
ON B.[Id] = A.[Id]
AND B.[Dates] = A.[ValueDate]
AND A.[Price] IS NOT NULL
)
SELECT [ID]
,[Dates]
,MAX([ValueDate]) OVER (PARTITION BY [GroupID]) AS [ValueDate]
,MAX([Price]) OVER (PARTITION BY [GroupID]) AS [Price]
FROM DataSource;

Update table data, fetched from another table

I have a table which is storing the attendance information on an employee and another table that's storing the information about the shift of the employee which is basically a duty roster.
Here is the structure to attendance table
CREATE TABLE Attendance
(
ID INT,
EmpCode INT,
ShiftCode INT,
CheckIn DATETIME,
CheckOut DATETIME
)
INSERT INTO Attendance VALUES (1, 1, 1, '2019-09-01 09:16:23', NULL)
INSERT INTO Attendance VALUES (2, 1, 1, NULL, '2019-09-01 18:01:56')
INSERT INTO Attendance VALUES (3, 1, 2, '2019-09-02 09:00:00', NULL)
INSERT INTO Attendance VALUES (4, 1, 2, NULL, '2019-09-02 18:48:21')
INSERT INTO Attendance VALUES (5, 1, 1, '2019-09-13 09:27:00', NULL)
INSERT INTO Attendance VALUES (6, 1, 1, NULL, '2019-09-13 18:45:00')
INSERT INTO Attendance VALUES (7, 2, 2, '2019-09-01 21:19:17', NULL)
INSERT INTO Attendance VALUES (8, 2, 2, NULL, '2019-09-01 23:30:56')
INSERT INTO Attendance VALUES (9, 2, 2, '2019-09-05 09:23:00', NULL)
INSERT INTO Attendance VALUES (10, 2, 2, NULL, '2019-09-05 17:19:00')
Here is the structure and sample data for Duty roster.
CREATE TABLE Shifts
(
ID INT PRIMARY KEY,
EmpCode INT,
ShiftCode INT,
StartDate DATETIME,
EndDate DATETIME
)
INSERT INTO Shifts VALUES (1, 1, 24, '2019-09-01 00:00:00', '2019-09-05 00:00:00');
INSERT INTO Shifts VALUES (2, 2, 25, '2019-09-01 00:00:00', '2019-09-05 00:00:00');
The idea is to update the ShiftCode in Attendance table wrt to the shifts stored in the duty roster. So if the attendance for employee 1 is between '2019-09-01' and '2019-09-05' then the shift code for this employee should be updated to 24 and same for other employee. If the duty roster does not exist for the dates present in attendance table it should not update it and let it the way it is.
I need an update query.
Something like this:
SELECT *
FROM Attendance A
INNER JOIN Shifts S
ON A.EmpCode = S.[EmpCode]
AND
(
A.CheckIn BETWEEN S.[StartDate] AND S.[EndDate]
OR
A.CheckOut BETWEEN S.[StartDate] AND S.[EndDate]
)
and with update:
UPDATE Attendance
SET ShiftCode = S.[ShiftCode]
FROM Attendance A
INNER JOIN Shifts S
ON A.EmpCode = S.[EmpCode]
AND
(
A.CheckIn BETWEEN S.[StartDate] AND S.[EndDate]
OR
A.CheckOut BETWEEN S.[StartDate] AND S.[EndDate]
);
I have tried this one and it works too:
UPDATE Attendance
SET ShiftCode = ISNULL((SELECT ShiftCode FROM Shifts Roster
WHERE CAST(COALESCE(CheckIn, CheckOut) AS DATE) BETWEEN StartDate AND EndDate AND EmpCode = Attendance.EmpCode),
(SELECT ShiftCode FROM EmployeeInfo WHERE EmployeeInfo.ID = Attendance.EmpCode))
Try this. It will helpful
UPDATE Attendance SET ShiftCode=c.ShiftsShiftCode
FROM Attendance a
JOIN
(
SELECT a.EmpCode, a.ShiftCode, CheckIn, CheckOut, b.ShiftCode AS ShiftsShiftCode FROM Attendance a
JOIN Shifts b ON a.EmpCode=b.EmpCode
AND (a.CheckIn BETWEEN StartDate AND EndDate OR a.CheckOut BETWEEN StartDate AND EndDate)
)c
ON a.EmpCode = c.EmpCode
AND (a.checkin=c.checkin OR a.CheckOut=c.CheckOut)

How to get columns from different unrelated tables having similar column names?

I have 3 tables:
Pay Group:
PayGroupId Name Description Code
1 US Weekly US Weekly USW
2 Can Weekly Canada Weekly CANW
3 US Monthly US Monthly USM
4 Can Monthly Can Monthly CANM
Pay Type:
PayTypeId Name Description Code
1 Hourly Hourly H
2 Salary Salaried S
Pay Code:
PayCodeId Name Description Code
1 Regular Regular REG
2 PTO PTO PTO
3 Sick Sick SICK
I need a report in following format:
PayGroup PayType PayCode
US Weekly Hourly Regular
Can Weekly Salary PTO
US Monthly Sick
Can we do this?
I suspect this gets you the result you are after, but seems like an odd requirement:
WITH PG AS(
SELECT [Name],
ROW_NUMBER() OVER (ORDER BY PayGroupID ASC) AS RN
FROM PayGroup),
PT AS(
SELECT [Name],
ROW_NUMBER() OVER (ORDER BY PayTypeID ASC) AS RN
FROM PayGroup),
PC AS(
SELECT [Name],
ROW_NUMBER() OVER (ORDER BY PayCodeID ASC) AS RN
FROM PayCode)
SELECT PG.[Name] AS PayGroup,
PT.[Name] AS PayType,
PC.[Name] AS PayCode
FROM PG
FULL OUTER JOIN PT ON PG.RN = PT.RN
FULL OUTER JOIN PC ON PG.RN = PC.RN
OR PT.RN = PC.RN;
CREATE TABLE #table1
([PayGroupId] int, [Name] varchar(11), [Description] varchar(13), [Code] varchar(4))
;
INSERT INTO #table1
([PayGroupId], [Name], [Description], [Code])
VALUES
(1, 'US Weekly', 'US Weekly', 'USW'),
(2, 'Can Weekly', 'Canada Weekly', 'CANW'),
(3, 'US Monthly', 'US Monthly', 'USM'),
(4, 'Can Monthly', 'Can Monthly', 'CANM')
;
CREATE TABLE #table2
([PayTypeId] int, [Name] varchar(6), [Description] varchar(8), [Code] varchar(1))
;
INSERT INTO #table2
([PayTypeId], [Name], [Description], [Code])
VALUES
(1, 'Hourly', 'Hourly', 'H'),
(2, 'Salary', 'Salaried', 'S')
;
CREATE TABLE #table3
([PayCodeId] int, [Name] varchar(7), [Description] varchar(7), [Code] varchar(4))
;
INSERT INTO #table3
([PayCodeId], [Name], [Description], [Code])
VALUES
(1, 'Regular', 'Regular', 'REG'),
(2, 'PTO', 'PTO', 'PTO'),
(3, 'Sick', 'Sick', 'SICK')
;
select a.name PayGroup ,isnull(B.Name,'') PayType ,isnull(C.Name,'')PayCode
from #table1 A left join #table2 B on a.[PayGroupId]=b.[PayTypeId]left join
#table3 c on c.[PayCodeId]=a.[PayGroupId]
PayGroup PayType PayCode
US Weekly Hourly Regular
Can Weekly Salary PTO
US Monthly Sick
Can Monthly

Reward points by looking at the already created fields

I have a table here :
I want to reward a gold and a silver(i.e a value of 1 wherever applicable,else 0 )to top 2 persons by looking at pointsRewarded field.
I already have the first table created.I want a new table with the two new fields i.e the gold and silver fields.
i want the output to be something like this:
Please help me with the query or give me some suggestions on how to proceed.
Thanks a lot.
I think you want to use dense_rank() for this:
select t.*,
(case when rnk = 1 then 1 else 0 end) as gold,
(case when rnk = 2 then 1 else 0 end) as silver
from (select t.*,
dense_rank() over (partition by week order by pointsrewarded) as rnk
from t
) t;
dense_rank() will handle the case when there are ties. In that case, multiple "gold" and "silver" values will be assigned.
I should also note that the subquery is not necessary. You can repeat the dense_rank() in the outer query. I just think it is easier to follow the logic this way.
Make sure to order by pointsrewarded descending so first place is the highest points and not the lowest. My code is longer, but I find easier to read (personal preference).
--create table employee (employeeid int, employeename varchar(50), weeknumber int, pointsRewarded int, Hours int)
--insert into employee values (111, 'person1', 1, 400, 2)
--insert into employee values (112, 'person2', 1, 100, 10)
--insert into employee values (113, 'person3', 1, 200, 10)
--insert into employee values (111, 'person1', 2, 100, 2)
--insert into employee values (112, 'person2', 2, 50, 10)
--insert into employee values (113, 'person3', 2, 200, 10)
--insert into employee values (111, 'person1', 3, 20, 4)
--insert into employee values (112, 'person2', 3, 25, 5)
--insert into employee values (113, 'person3', 3, 100, 6)
;WITH Medals AS
(
SELECT
employeeid
,employeename
,weeknumber
,pointsRewarded
,hours
,ROW_NUMBER() OVER (PARTITION BY weeknumber ORDER BY pointsrewarded DESC) medal
FROM
employee
)
SELECT
employeeid
,employeename
,weeknumber
,pointsRewarded
,hours
,CASE WHEN medal = 1 THEN 1 ELSE 0 END AS gold
,CASE WHEN medal = 2 THEN 1 ELSE 0 END AS silver
FROM
Medals

Summing up the records as per given conditions

I have a table like below, What I need that for any particular fund and up to any particular date logic will sum the amount value. Let say I need the sum for 3 dates as 01/28/2015,03/30/2015 and 04/01/2015. Then logic will check for up to first date how many records are there in table . If it found more than one record then it'll sum the amount value. Then for next date it'll sum up to the next date but from the previous date it had summed up.
Id Fund Date Amount
1 A 01/20/2015 250
2 A 02/28/2015 300
3 A 03/20/2015 400
4 A 03/30/2015 200
5 B 04/01/2015 500
6 B 04/01/2015 600
I want result to be like below
Id Fund Date SumOfAmount
1 A 02/28/2015 550
2 A 03/30/2015 600
3 B 04/01/2015 1100
Based on your question, it seems that you want to select a set of dates, and then for each fund and selected date, get the sum of the fund amounts from the selected date to the previous selected date. Here is the result set I think you should be expecting:
Fund Date SumOfAmount
A 2015-02-28 550.00
A 2015-03-30 600.00
B 2015-04-01 1100.00
Here is the code to produce this output:
DECLARE #Dates TABLE
(
SelectedDate DATE PRIMARY KEY
)
INSERT INTO #Dates
VALUES
('02/28/2015')
,('03/30/2015')
,('04/01/2015')
DECLARE #FundAmounts TABLE
(
Id INT PRIMARY KEY
,Fund VARCHAR(5)
,Date DATE
,Amount MONEY
);
INSERT INTO #FundAmounts
VALUES
(1, 'A', '01/20/2015', 250)
,(2, 'A', '02/28/2015', 300)
,(3, 'A', '03/20/2015', 400)
,(4, 'A', '03/30/2015', 200)
,(5, 'B', '04/01/2015', 500)
,(6, 'B', '04/01/2015', 600);
SELECT
F.Fund
,D.SelectedDate AS Date
,SUM(F.Amount) AS SumOfAmount
FROM
(
SELECT
SelectedDate
,LAG(SelectedDate,1,'1/1/1900') OVER (ORDER BY SelectedDate ASC) AS PreviousDate
FROM #Dates
) D
JOIN
#FundAmounts F
ON
F.Date BETWEEN DATEADD(DAY,1,D.PreviousDate) AND D.SelectedDate
GROUP BY
D.SelectedDate
,F.Fund
EDIT: Here is alternative to the LAG function for this example:
FROM
(
SELECT
SelectedDate
,ISNULL((SELECT TOP 1 SelectedDate FROM #Dates WHERE SelectedDate < Dates.SelectedDate ORDER BY SelectedDate DESC),'1/1/1900') AS PreviousDate
FROM #Dates Dates
) D
If i change your incorrect sample data to ...
CREATE TABLE TableName
([Id] int, [Fund] varchar(1), [Date] datetime, [Amount] int)
;
INSERT INTO TableName
([Id], [Fund], [Date], [Amount])
VALUES
(1, 'A', '2015-01-28 00:00:00', 250),
(2, 'A', '2015-01-28 00:00:00', 300),
(3, 'A', '2015-03-30 00:00:00', 400),
(4, 'A', '2015-03-30 00:00:00', 200),
(5, 'B', '2015-04-01 00:00:00', 500),
(6, 'B', '2015-04-01 00:00:00', 600)
;
this query using GROUP BY works:
SELECT MIN(Id) AS Id,
MIN(Fund) AS Fund,
[Date],
SUM(Amount) AS SumOfAmount
FROM dbo.TableName t
WHERE [Date] IN ('01/28/2015','03/30/2015','04/01/2015')
GROUP BY [Date]
Demo
Initially i have used Row_number and month function to pick max date of every month and in 2nd cte i did sum of amounts and joined them..may be this result set matches your out put
declare #t table (Id int,Fund Varchar(1),Dated date,amount int)
insert into #t (id,Fund,dated,amount) values (1,'A','01/20/2015',250),
(2,'A','01/28/2015',300),
(3,'A','03/20/2015',400),
(4,'A','03/30/2015',200),
(5,'B','04/01/2015',600),
(6,'B','04/01/2015',500)
;with cte as (
select ID,Fund,Amount,Dated,ROW_NUMBER() OVER
(PARTITION BY DATEDIFF(MONTH, '20000101', dated)ORDER BY dated desc)AS RN from #t
group by ID,Fund,DATED,Amount
),
CTE2 AS
(select SUM(amount)Amt from #t
GROUP BY MONTH(dated))
,CTE3 AS
(Select Amt,ROW_NUMBER()OVER (ORDER BY amt)R from cte2)
,CTE4 AS
(
Select DISTINCT C.ID As ID,
C.Fund As Fund,
C.Dated As Dated
,ROW_NUMBER()OVER (PARTITION BY RN ORDER BY (SELECT NULL))R
from cte C INNER JOIN CTE3 CC ON c.RN = CC.R
Where C.RN = 1
GROUP BY C.ID,C.Fund,C.RN,C.Dated )
select C.R,C.Fund,C.Dated,cc.Amt from CTE4 C INNER JOIN CTE3 CC
ON c.R = cc.R
declare #TableName table([Id] int, [Fund] varchar(1), [Date] datetime, [Amount] int)
declare #Sample table([SampleDate] datetime)
INSERT INTO #TableName
([Id], [Fund], [Date], [Amount])
VALUES
(1, 'A', '20150120 00:00:00', 250),
(2, 'A', '20150128 00:00:00', 300),
(3, 'A', '20150320 00:00:00', 400),
(4, 'A', '20150330 00:00:00', 200),
(5, 'B', '20150401 00:00:00', 500),
(6, 'B', '20150401 00:00:00', 600)
INSERT INTO #Sample ([SampleDate])
values ('20150128 00:00:00'), ('20150330 00:00:00'), ('20150401 00:00:00')
-- select * from #TableName
-- select * from #Sample
;WITH groups AS (
SELECT [Fund], [Date], [AMOUNT], MIN([SampleDate]) [SampleDate] FROM #TableName
JOIN #Sample ON [Date] <= [SampleDate]
GROUP BY [Fund], [Date], [AMOUNT])
SELECT [Fund], [SampleDate], SUM([AMOUNT]) FROM groups
GROUP BY [Fund], [SampleDate]
Explanation:
The CTE groups finds the earliest SampleDate which is later than (or equals to) your
data's date and enriches your data accordingly, thus giving them the group to be summed up in.
After that, you can group on the derived date.