Count records using Date filter related tables SQL Server - sql

I'm trying to calculates the number of reports (report_user table) per user between two date (Calendar table) and by day worked (agenda_user table).
Here is the diagram of my tables:
Calendar table :
DATE Year Month
---------------------------------
2020-01-01 2020 1
2020-01-02 2020 1
2020-01-03 2020 1
2020-01-04 2020 1
AGENDA_USER table :
ID_USER DATE Value
---------------------------------
1 2020-01-01 1
2 2020-01-01 1
1 2020-01-02 0
2 2020-01-02 1
User table :
ID_USER Name
-------------------------
1 Jack
2 Robert
Report_Result table :
ID_USER Date Result
-----------------------------------
1 2020-01-01 good
1 2020-01-01 good
2 2020-01-01 bad
2 2020-01-01 good
2 2020-01-02 good
2 2020-01-02 good
Result I'm trying to find with an SQL query
ID_USER Date Number of report Day work report/work
---------------------------------------------------------------------------
1 2020-01-01 2 1 2/1 = 2
2 2020-01-01 2 1 1
1 2020-01-02 0 0 0
2 2020-01-02 2 1 2
SELECT
REPORT_USER.ID_USER,
COUNT(ID_USER) AS result
FROM [DB].[dbo].REPORT_USER AS report,
JOIN [DB].[dbo].[USER] AS [USER]
ON [USER].ID_USER = report.ID_USER
JOIN [DB].[dbo].AGENDA_USER AS agenda
ON agenda.ID_USER = report.ID_USER
WHERE CAST(agenda.[Date] AS DATE) >= '2020-09-01'
AND CAST(agenda.[Date] AS DATE) <= '2021-07-28'
AND [USER].ID_user = 1167
GROUP BY
report.ID_VENDEUR;

I'm not entirely sure I understand your problem, but I think I'm reasonably close so here is a start, point out my invalid assumptions and we can refine. More data, particularly in Agenda and Reports would really help. An explanation is below (plus see the comment in the code).
The overall flow is to generate a list of days/people you want to report on (cteUserDays), generate a list of how many reports each user generated on each date (cteReps), generate a list of who worked on what days (cteWork), and then JOIN all 3 parts together using a LEFT OUTER JOIN so the report covers all workers on all days.
EDIT: Add cteRepRaw where DATETIME is converted to DATE and "bad" reports are filtered out. Grouping and counting still happens in cteReps, but joining to cteUserDays is not there because it was adding 1 to count if there was a NULL.
DECLARE #Cal TABLE (CalDate DATETIME, CalYear int, CalMonth int)
DECLARE #Agenda TABLE (UserID int, CalDate DATE, AgendaVal int)
DECLARE #User TABLE (UserID int, UserName nvarchar(50))
DECLARE #Reps TABLE (UserID int, CalDate DATETIME, RepResult nvarchar(50))
INSERT INTO #Cal(CalDate, CalYear, CalMonth)
VALUES ('2020-01-01', 2020, 1), ('2020-01-02', 2020, 1), ('2020-01-03', 2020, 1), ('2020-01-04', 2020, 1)
INSERT INTO #Agenda(UserID, CalDate, AgendaVal)
VALUES (1, '2020-01-01', 1), (2, '2020-01-01', 1), (1, '2020-01-02', 0), (2, '2020-01-02', 1)
INSERT INTO #User (UserID , UserName )
VALUES (1, 'Jack'), (2, 'Robert')
INSERT INTO #Reps (UserID , CalDate , RepResult )
VALUES (1, '2020-01-01', 'good'), (1, '2020-01-01', 'good')
, (2, '2020-01-01', 'bad'), (2, '2020-01-01', 'good')
, (2, '2020-01-02', 'good'), (2, '2020-01-02', 'good')
; with cteUserDays as (
--First, you want zeros in your table where no reports are, so build a table for that
SELECT CONVERT(DATE, D.CalDate) as CalDate --EDIT add CONVERT here
, U.UserID FROM #Cal as D CROSS JOIN #User as U
WHERE D.CalDate >= '2020-01-01' AND D.CalDate <= '2021-07-28'
--EDIT Watch the <= date here, a DATE is < DATETIME with hours of the same day
), cteRepRaw as (--EDIT Add this CTE to convert DATETIME to DATE so we can group on it
--Map the DateTime to a DATE type so we can group reports from any time of day
SELECT R.UserID
, CONVERT(DATE, R.CalDate) as CalDate --EDIT add CONVERT here
, R.RepResult
FROM #Reps as R
WHERE R.RepResult='good' --EDIT Add this test to only count good ones
), cteReps as (
--Get the sum of all reports for a given user on a given day, though some might be missing (fill 0)
SELECT R.UserID , R.CalDate , COUNT(*) as Reports --SUM(COALESCE(R.RepResult, 0)) as Reports
FROM cteRepRaw as R--cteUserDays as D
--Some days may have no reports for that worker, so use a LEFT OUTER JOIN
--LEFT OUTER JOIN cteRepRaw as R on D.CalDate = R.CalDate AND D.UserID = R.UserID
GROUP BY R.UserID , R.CalDate
) , cteWork as (
--Unclear what values "value" in Agenda can take, but assuming it's some kind of work
-- unit, like "hours worked" or "shifts" so add them up
SELECT A.UserID , A.CalDate, SUM(A.AgendaVal) as DayWork FROM #Agenda as A
WHERE A.CalDate >= '2020-01-01' AND A.CalDate <= '2021-07-28'
GROUP BY A.CalDate, A.UserID
)
SELECT D.UserID , D.CalDate, COALESCE(R.Reports, 0) as Reports, W.DayWork
--NOTE: While it's probably a mistake to credit a report to a day a worker had
--no shifts, it could happen and would throw an error so check
, CASE WHEN W.DayWork > 0 THEN R.Reports / W.DayWork ELSE 0 END as RepPerWork
FROM cteUserDays as D
LEFT OUTER JOIN cteReps as R on D.CalDate=R.CalDate AND R.UserID = D.UserID
LEFT OUTER JOIN cteWork as W on D.UserID = W.UserID AND D.CalDate = W.CalDate
ORDER BY CalDate , UserID
First, as per the comments in your OP "Agenda" represents when the user is working, you don't say how it's structured so I'll assume it can have multiple entries for a given person on a given day (i.e. a 4 hour shift and an 8 hour shift) so I'll add them up to get total work (cteWork). I also assume that if somebody didn't work, you can't have a report for them. I check for this, but normally I'd expect your data validator to pre-screen those out.
Second, I'll assume reports are 1 per record, and a given user can have multiple per day. You have that in your given, but it's important to this solution so I'm restating in case somebody else reads this later.
Third, I assume you want all days reported for all users, I assure this by generating a CROSS join between users and days (cteUserDays)

Related

Check Hall Booking status

I'm making a Hall Booking System, and I've being struggling with the Booking Module
I have 2 tables Halls & Bookings
Halls table With Sample Data
id hallName hallType numSeats status
---------------------------------------------
1 Hall 1 Normal 500 active
2 Hall 2 VIP 30 active
3 Hall 3 VVIP 5 active
4 Hall 4 Normal 60 active
5 Hall 5 Normal 80 active
6 Hall 4+5 Normal 140 active
Booking Table with Sample Data
id custId hallId beginDate endDate time status
-----------------------------------------------------------------
1 1 1 2022-09-26 2022-09-26 morning confirmed
2 6 4 2022-09-26 2022-09-26 evening cancelled
3 4 3 2022-09-26 2022-09-26 full time pending
4 9 4 2022-09-26 2022-09-30 after noon confirmed
Booking time slots are "morning", "after noon", "evening" & "full time"
I have being trying on the Booking validations as well as a report that shows Halls booking status
Edit
After suggestions in the comments I have edited and removed the second part, maybe will post seperately if I can't figure out
Here is what I want to be the result
The records can be filtered by endDate(e.g 2022-09-26)
if Booking exists which is not cancelled for the provided beginDate, the time slot should be labelled as Booked
if a booking does not exist for the provided beginDate or its canclled, the time slot should be labeled as Available
If a full time Booking slot ecists for the provied beginDate, all the time the 3 slots should be labeled as Booked
Here is the twist that may complicate things
As you can see from the Halls table, Hall 6 is a combination of Halls 4 & 5 which means if any of them is booked, Hall 6 should be marked as unavailable or even Booked will be fine.
Overall, here is a sample result based on the above booking table
hallName hallType morning after noon evening
------------------------------------------------------
hall 1 Normal Booked available available
hall 2 VIP available available available
hall 3 VVIP Booked Booked Booked
hall 4 Normal available Booked available
hall 5 Normal available available available
hall 6 Normal available unavailable available
if we take the Booking Table with Sample data, the result should be as shown above
I'm not that much familiar with Joins, merges, etc which are what I'm thinking the solution is. I tried merge with When matched and when Not matched but could not figure it out to work
I then tried left joining the Halls table to the Booking table seems to be the closest so far and the output is clear.
DECLARE #date NVarchar(50) = '2022-09-26'
SELECT h.id, h.hallName, h.hallType, b.time FROM Halls h LEFT JOIN
Bookings b ON b.hallId=h.id WHERE b.beginDate=#date
this returns only Booked halls with beginDate of that
If I drop the WHERE clause, all the 6 halls are returned but time slots which aren't booked are returned as NULL
btw I'm working on the last module and at firts I was working with a single check in date and requirement changed into Range beginDate & endDate & that is when problems arose.
Firstly you need to fix your design how are you intend to store information that Hall 6 is a combine of Hall 4 + 5
One simple way is to add another column in the Halls table that indicate that. Example
create table Halls
(
id int,
hallName varchar(10),
hallType varchar(10),
numSeats int,
status varchar(10),
combine_id int
);
insert into Halls values
(4, 'Hall 4', 'Normal', 60, 'active', 6),
(5, 'Hall 5', 'Normal', 80, 'active', 6),
(6, 'Hall 4+5', 'Normal', 140, 'active', null);
Once that is in-placed, you need to translate the Bookings to handle the combined Halls. This is perform by the CTE BookingData. It will create row for Hall 6 if Hall 4 or Hall 5 is pending or confirmed. And simiarly the other way round. When Hall 6 is Booked, Hall 4 and Hall 5 will be unavailable.
After that it is just simple pivoting of data
The solution:
DECLARE #date date = '2022-09-26';
with BookingData as
(
select b.hallId, b.time, b.status, beginDate
from Bookings b
union all
select hallId = h.combine_id, b.time,
status = case when b.status in ('pending', 'confirmed')
then 'unavailable'
else 'available'
end,
beginDate
from Bookings b
inner join Halls h on b.hallId = h.id
where h.combine_id is not null
union all
select hallId = h.id, b.time,
status = case when b.status in ('pending', 'confirmed')
then 'unavailable'
else 'available'
end,
beginDate
from Bookings b
inner join Halls h on b.hallId = h.combine_id
where h.combine_id is not null
)
SELECT id,
hallName,
hallType,
[morning] = isnull([morning], 'available'),
[afternoon] = isnull([afternoon], 'available'),
[evening] = isnull([evening], 'available')
FROM
(
SELECT h.id, h.hallName, h.hallType, t.timeSlot,
status = case when b.status in ('pending', 'confirmed')
then 'Booked'
when b.status in ('cancelled')
then 'unavailable'
when b.status in ('unavailable')
then b.status
else NULL
end
FROM Halls h
LEFT JOIN BookingData b ON b.hallId = h.id
and b.beginDate = #date
OUTER APPLY
(
select timeSlot = 'morning' where b.time in ('morning', 'full time')
union all
select timeSlot = 'afternoon' where b.time in ('afternoon', 'full time')
union all
select timeSlot = 'evening' where b.time in ('evening', 'full time')
) t
) D
PIVOT
(
MAX (status)
FOR timeSlot in ( [morning], [afternoon], [evening] )
) P
db<>fiddle demo
Your question is overtly complicated due to the fact the table does not implement normalization properly for "combined halls." We will need to separate Hall 4+5 using string operation and then store the extracted information somewhere. Here is the query that I could think off:
with
composite_halls as
(select *,
substring(hallName, charindex(' ', hallName) + 1,
charindex('+', hallName) - charindex(' ', hallName) - 1) as id1,
right(hallName, len(hallName) - charindex('+', hallName)) as id2,
(null) as id3
from halls where charindex('+', hallName) > 0),
singular_halls as
(select *,
(select ch.id2 from composite_halls as ch where ch.id1 = h.id) as id1,
(select ch.id1 from composite_halls as ch where ch.id2 = h.id) as id2,
(select ch.id from composite_halls as ch where ch.id1 = h.id or ch.id2 = h.id) as id3
from halls as h where charindex('+', hallName) = 0),
all_halls as (
select * from singular_halls
union
select * from composite_halls),
valid_bookings as (
select * from bookings where status = 'confirmed'
and beginDate >= '2022-09-26' and endDate <= '2022-09-26')
select *,
(case when exists (
select *
from valid_bookings as vb
where (time = 'morning' or time = 'full time')
and (vb.hallId = h.id
or (vb.hallId = h.id1 and h.id3 is null)
or (vb.hallId = h.id2 and h.id3 is null)
or vb.hallId = h.id3))
then 'booked'
else 'available'
end) as morning
from all_halls as h
You can try it on fiddle: https://dbfiddle.uk/baQyI1Y7
It looks somewhat detestable and I believe someone else can write shorter queries.
The query above uses 4 CTEs with 3 of them deals with extracting the ids of relevant "combined halls." composite_halls will extract the numbers from hallName and puts them into id1 and id2. singular_halls will put its counterpart's id in either id1 or id2 and its composite in id3. all_halls merges the two result into one. valid_bookings filters bookings for given timeframe and only confirmed bookings are taken.
The next step, it checks if any bookings' hallId correspond to any of id, id1, id2, id3 within the all_halls entries. So far it only handles morning bookings and only provide booked and available status. You can expand it to provide unavailable by adding more case when to specifically handle id1 up to id3. You can handle after noon and evening can be done the same way by changing the time parameter within the case when clause.
I don't think the query above is performant enough for a live application. Especially the fact that we operate on row-by-row basis to extract the relevant halls from the "combined" one.
I believe things would be much easier should you normalize the table a bit more. I feel its like doing excel using TSQL which doesn't seem right.

Returning Records by Week

I'm trying to come up with a way to return a result set from the below data without a loop that shows the number of records by Team for a particular date range by week.
I've got a Date table (https://www.mssqltips.com/sqlservertip/4054/creating-a-date-dimension-or-calendar-table-in-sql-server/) that has every day/week/year referenced, but not sure how to connect it up.
Create Table #Team
(
TeamID int,
TeamName varchar(20)
)
insert into #Team
(
TeamID,
TeamName
)
select
1,
'Team 1'
union all
select
2,
'Team 2'
union all
select
3,
'Team 3'
union all
select
4,
'Team 4'
Create Table #Entries
(
EntryID int,
DateCreated datetime,
TeamID int
)
insert into #Entries
(
EntryID,
DateCreated,
TeamID
)
select
1,
'2 Nov 2020',
1
union all
select
2,
'4 Nov 2020',
2
I've got this query:
select
T.TeamName,
WeekOfYear,
WeekOfMonth,
count(*) as Count(*)
from
#Team T
Left Join #Entries E on
T.TeamID = E.TeamID
Inner Join DimDate D on
cast(E.DateCreated as date) = D.[Date]
group by
T.TeamName,
WeekOfYear,
WeekOfMonth
Where it fails is:
It doesn't include the teams with 0 results
I need to be able to show results for multiple weeks through a date range. In the above example, they would be 0.
I think the trick is to first generate all the rows you need, then LEFT JOIN those onto their results to get what you want.
Note that in your query, you are pulling out WeekOfYear and WeekOfMonth, but you probably also want to pull out Year in case the data crosses years or goes for multiple years.
For the date range
I have two variables #RangeStart and #RangeEnd- both dates - to do filtering
I create a table (probably incorrect) to model the date dimension table
CREATE TABLE #DimDate ([Date] date, WeekOfYear int, WeekOfMonth int, y_year int)
INSERT INTO #DimDate ([Date], WeekOfYear, WeekOfMonth, y_year) VALUES
('20201029', 35, 4, 2020),
('20201030', 35, 4, 2020),
('20201031', 35, 4, 2020),
('20201101', 36, 1, 2020),
('20201102', 36, 1, 2020),
('20201103', 36, 1, 2020),
('20201104', 36, 1, 2020);
-- Note that I called the year 'y_year' - will need to be changed
-- to your value (or converted to YEAR([date]) function)
DECLARE #RangeStart date = '20201030';
DECLARE #RangeEnd date = '20201102';
WITH AllTeamDates AS
(SELECT T.TeamId,
D.[Date],
D.WeekOfMonth,
D.WeekOfYear,
D.y_year
FROM #Team T
CROSS JOIN #DimDate D
WHERE D.[Date] BETWEEN #RangeStart AND #RangeEnd
)
SELECT ATD.y_year,
ATD.WeekOfYear,
ATD.WeekOfMonth,
ATD.TeamID,
COUNT(E.EntryID) AS NumEntries
FROM AllTeamDates ATD
LEFT OUTER JOIN #Entries E
ON ATD.TeamID = E.TeamID AND ATD.Date = E.DateCreated
GROUP BY ATD.y_year,
ATD.WeekOfYear,
ATD.WeekOfMonth,
ATD.TeamID;
Results for the above, with your data and my date table and range dates applied (noting that the date range I selected gets the first value in #Entries for 2 Nov, but doesn't get the second for 4 Nov).
y_year WeekOfYear WeekOfMonth TeamID NumEntries
2020 35 4 1 0
2020 35 4 2 0
2020 35 4 3 0
2020 35 4 4 0
2020 36 1 1 1
2020 36 1 2 0
2020 36 1 3 0
2020 36 1 4 0
Note that in this case I am creating all possible dates, then grouping to get week-by-week at the very end. It is possible to also do this by grouping into week-by-week data as soon as possible (e.g., the CTE will return data by week instead of day, then the outer part of the LEFT JOIN also then needs to be grouped into weeks first).
WITH AllTeamWeeks AS
(SELECT T.TeamId, D.WeekOfMonth, D.WeekOfYear, D.y_year
FROM #Team T
CROSS JOIN #DimDate D
WHERE D.[Date] BETWEEN #RangeStart AND #RangeEnd
GROUP BY T.TeamId, D.WeekOfMonth, D.WeekOfYear, D.y_year
),
AllEntries AS
(SELECT E.TeamId, D.WeekOfMonth, D.WeekOfYear, D.y_year,
COUNT(E.EntryID) AS NumEntries
FROM #Entries E
INNER JOIN #DimDate D ON E.DateCreated = D.Date
WHERE E.[DateCreated] BETWEEN #RangeStart AND #RangeEnd
GROUP BY E.TeamId, D.WeekOfMonth, D.WeekOfYear, D.y_year
)
SELECT ATW.y_year,
ATW.WeekOfYear,
ATW.WeekOfMonth,
ATW.TeamID,
ISNULL(AE.NumEntries,0) AS NumEntries
FROM AllTeamWeeks ATW
LEFT OUTER JOIN AllEntries AE
ON ATW.TeamID = AE.TeamID
AND ATW.WeekOfMonth = AE.WeekOfMonth
AND ATW.WeekOfYear = AE.WeekOfYear
AND ATW.y_year = AE.y_year;
This gives the same results, and possibly provides a performance benefit, but is more complex and you'd probably need to ensure that SQL Server is getting accurate estimates/etc when doing the multiple GROUP BYs.
As such I wouldn't use it unless there is a performance issue with the first one - and if there was, I'd also try turning the CTE into a temporary table first, then joining that to #Entries.

How to display data on separate rows in one row

I have a table with the following setup
ID InOut_Status InOut_Datetime
1 IN 9/12/2017 8:00
2 IN 9/12/2017 10:00
1 OUT 9/12/2017 1:00
2 OUT 9/12/2017 3:00
I want to be able to see both status and date on the same row vs separate rows for example
ID In_Status In_Datetime Out_Status Out_Datetime
1 IN 9/12/2017 8:00 OUT 9/12/2017 1:00
2 IN 9/12/2017 10:00 OUT 9/12/2017 3:00
I would like to return all columns. I just provided a few for example. I also would like to show only the most recent Datetime for each ID and if the user hasn't checked out, I would like for the Out_Datetime to be blank.
Any assistance would be greatly appreciated. Thanks.
You can use self join:
SELECT *
FROM
(
SELECT ins.id
, ins.InOut_Datetime as in_time
, outs.InOut_Datetime as out_time
, row_number() over (partition by ins.id order by ins.InOut_Datetime desc) as ranking
FROM table ins
LEFT JOIN table outs
ON ins.id = outs.id
AND outs.InOut_Status = 'OUT'
AND outs.InOut_Datetime > ins.InOut_Datetime
WHERE ins.InOut_Status = 'IN'
and ins.InOut_Datetime > DATEADD(day, -1, GETDATE())
) t
WHERE t.ranking = 1
Updated query to :
get logins within last 24 hours
get the latest login of a user only
show out time only if it's later than in time
You need to left join, however, you want to limit the join to the first record returned in descending order using a sub query.
SELECT * FROM
(
SELECT
ID,InOut_Status,InOutDateTime,
CheckOutInstanceDescending = ROW_NUMBER() OVER(PARTITION BY ClockOut.ID ORDER BY ClockOut.InOutDateTime DESC)
FROM
MyTable ClockIn
LEFT OUTER JOIN MyTable ClockOut ON ClockOut.ID=ClockIn.ID
WHERE
ClockIn.InOut_Status='IN'
)AS Combined
WHERE
Combined.CheckOutInstanceDescending=1
A simple pivot would fix this the easiest:
https://technet.microsoft.com/en-us/library/ms177410(v=sql.105).aspx. You can run this in SSMS 2008 or higher(I wrote it in SQL 2016).
DECLARE #Temp TABLE (Id INT, InOut_Status VARCHAR(8), InOut_Datetime DATETIME)
INSERT INTO #Temp (Id, InOut_Status, InOut_Datetime) VALUES (1, 'IN', '9-12-2017 8:00'), (2, 'IN', '9-12-2017 10:00'),(1, 'OUT', '9-12-2017 13:00'),(2, 'OUT', '9-12-2017 16:00'),(3, 'IN', '9-12-2017 06:00')
SELECT
pvt.Id
, 'IN' AS In_Status
, pvt.[In]
, 'OUT' AS Out_Status
, pvt.OUT
From #Temp
PIVOT(MAX(InOut_Datetime) FOR InOut_Status IN ([In], [OUT])) AS pvt

Querying a log table with nonlinear data

I'm trying to run some queries on a log-style table, which contains a bunch of nonlinear data. I have the following schema:
Signouts
+------------+----------------+------------+----------+
| signout_id | environment_id | date_start | date_end |
+------------+----------------+------------+----------+
| int | int | datetime | datetime |
+------------+----------------+------------+----------+
Environments
+-----+---------+
| id | name |
+-----+---------+
| int | varchar |
+-----+---------+
Signouts is the log table (and I say "log table" because records are never updated, only marked as "disabled" and added anew). When a user signs out an environment, their chosen start and end time is entered into the signouts table. Currently, to see if an environment is signed out, I simply check if the current date falls between date_start and date_end. If another user wants to sign out that environment, the minimum time they can choose is the ending date of the current signout.
I have a new challenge now, though. I now need to implement a reservation system. All of a sudden, dates can be anywhere in the future, and an environment can be reserved at any time. Now I need to know when an environment can still be signed out, and what those minimum (and now maximum) values are!
I've gotten it down to this naive plan, but I'm having trouble getting it into SQL:
get all signouts where start < curdate & end > curdate
if there is no current signout, get the min start of all signouts where start > curdate
if there is a signout, get the max end
Here is the closest I've gotten, among many other scrapped queries:
SELECT s.date_start_unavailable, s.date_available, e.id AS environment_id
FROM Environments AS e
LEFT OUTER JOIN (
SELECT TOP (100) PERCENT signout_id, environment_id, username, date_start, date_end, project, notes, in_use, max(date_end) as date_available, min(date_start) as date_start_unavailable
FROM dbo.Signouts
WHERE date_end >= GETDATE()
GROUP BY signout_id, environment_id, username, date_start, date_end, project, notes, in_use
ORDER BY date_start DESC
) AS s ON s.environment_id = e.id
This almost works. date_start_unavailable is the time at which the system becomes unavailable for a signout, and dave_available is the time when there are no more signouts. This still has problems, however; someone could reserve an environment years into the future for a month, and normal users wouldn't be able to see that most of the time is unallocated. I'll have to find a way to restrict this, but I can worry about that later.
The signouts last for arbitrary, user-entered amounts of time, otherwise implementing a time-block system would be trivial. If anybody can offer some DBA wisdom, it would be much appreciated!
Setting up my test-environment like this:
create table environment (id int, name varchar(255));
insert into environment values (1, 'DVD');
insert into environment values (2, 'BluRay');
create table signout (id int, environment_id int, date_start date, date_end date);
insert into signout values (1, 1, '01.11.2015', '09.11.2015');
insert into signout values (2, 1, '10.11.2015', '12.11.2015');
insert into signout values (3, 1, '01.12.2015', '24.12.2015');
insert into signout values (4, 2, '01.12.2015', '02.12.2015');
insert into signout values (5, 2, '04.12.2015', '07.12.2015');
insert into signout values (6, 2, '11.12.2015', '13.12.2015');
insert into signout values (7, 2, '14.12.2015', '23.12.2015');
Now, selecting the booked times is trivial:
select e.name, s.date_start d_start, s.date_end d_end, 'booked' as d_status FROM
signout s inner join environment e ON e.id = s.environment_id
Bu what about free times? These would be the times where no booking exists - so you join the table with itself in a given order:
select e.name, dateadd(DAY, 1, s.date_end) d_start,
COALESCE(dateadd(day, -1, s2.date_start), '31.12.2025') d_end, 'free'
FROM signout s
OUTER APPLY (
SELECT TOP 1 date_start, date_end from signout sx
WHERE sx.environment_id = s.environment_id
AND sx.date_start > s.date_end ORDER BY sx.date_start
) s2
inner join environment e ON e.id = s.environment_id
WHERE (s2.date_end is NULL OR s2.date_start > dateadd(DAY, 1, s.date_end))
Now UNION these together and add sort according to environment and date:
select e.name, s.date_start d_start, s.date_end d_end, 'booked' as d_status FROM
signout s inner join environment e ON e.id = s.environment_id
AND s.date_start > getdate()
UNION
select e.name, dateadd(DAY, 1, s.date_end) d_start,
COALESCE(dateadd(day, -1, s2.date_start), '31.12.2025') d_end, 'free'
FROM signout s
OUTER APPLY (
SELECT TOP 1 date_start, date_end from signout sx
WHERE sx.environment_id = s.environment_id
AND sx.date_start > s.date_end ORDER BY sx.date_start
) s2
inner join environment e ON e.id = s.environment_id
WHERE (s2.date_end is NULL OR s2.date_start > dateadd(DAY, 1, s.date_end))
AND s.date_start > getdate()
ORDER BY 1, 2
Here's what this gets me:
BluRay 2015-12-01 2015-12-02 booked
BluRay 2015-12-03 2015-12-03 free
BluRay 2015-12-04 2015-12-07 booked
BluRay 2015-12-08 2015-12-10 free
BluRay 2015-12-11 2015-12-13 booked
BluRay 2015-12-14 2015-12-23 booked
BluRay 2015-12-24 2025-12-31 free
DVD 2015-11-10 2015-11-12 booked
DVD 2015-11-13 2015-11-30 free
DVD 2015-12-01 2015-12-24 booked
DVD 2015-12-25 2025-12-31 free

Selecting specific Data per month

So i have a table that would determine the Region Code of a Branch Depending on the Month,
Lets Say from January to february it's Region code would be 1, from February to March it would be 2 and for the month of april upto date would be 3
so here is the sample look of the table
i have a code that gets the data from a table, but what i want to achieve is that if the LoanDate of the selected Data is within the Dates above (between fld_Datefrom and fld_Dateto) it would use the fld_BranchRegion that is indicated like above above. (EG. the Loandate of the date is 2013-01-12 00:00:00 it would use the RegionCode 4A as indicated above and if the data is 2013-02-04 00:00:00 it would use the Region code 3
here is the code i use
SELECT
TE.LOAN
,bp.REGION
,BP.ID
,TE.AMOUNT
,te.ID
FROM #TrackExpired TE
inner join Transactions.TBLMAIN PM
on TE.ID = PM.ID
inner join #track BP
on BP.ID=cast(TE.ID/1000000000000 as decimal(38,0))
WHERE ((cast(TE.EXPIRATION as date) < cast(TE.newloandate as date))
OR(TE.NewLoanDate is null and (cast(TE.EXPIRATION as date) < cast(PM.REDEEMED as date))) or ((TE.NewLoanDate is null and PM.REDEEMED is null) and (PM.STATUS = 7 or PM.STATUS = 5)) )
The problem with this is that it generates duplicate values so i have 3 occurances of the dates in the #track table the number of the Data is also outputted 3 times with different Region Code!!
Instead of outputting them i would like to achive on selecting the Region Code from **#track
Based on the loan date of the Data.**
i just want to achieve that instead of outputting all of the region code, it would just use the Region code that is between the ranges based on the #track table provided..
Any Help? or other approach?? thank you!. sorry im new to SQL.
EDIT here is the code to create the temp tables.
#trackexpired
SELECT PH.ID
,PH.LOAN
,PH.EXPIRATION
,PH.AMOUNT
,(SELECT T3.LOAN FROM Transactions.HISTO T3 INNER JOIN
(
SELECT MIN(T2.ID) as pawnhisto
FROM Transactions.HISTO T2
WHERE T2.ID > PH.ID
AND PH.ID = T2.ID
) T4
ON T4.pawnhisto = T3.ID
)as 'NewLoanDate'
INTO #TrackExpired
FROM Transactions.HISTO PH
INNER JOIN Transactions.MAIN PM
ON PM.ID=PH.ID
WHERE YEAR(PH.LOAN) = #YEAR
#track
Select bt.CODE
,bp.ID
,AREA
,REGION
,NCODE
,FROM
,isnull(fld_Dateto,GETDATE()) as fld_Dateto
into #sort
from Transactions.tbl_BranchTracking bt
inner join Reference.tbl_BranchProfiles bp
on bt.CODE = bp.CODE
Select * into #track from #sort
where #YEAR >= year(FROM)
and
#YEAR <= year(fld_Dateto)
Test Data
create table #LoanTable (
ID int not null,
RegionCode nvarchar(50) not null,
LoanDate datetime not null
);
insert into #LoanTable values
(1,'5','10/01/2014'),
(2,'5','10/18/2014'),
(3,'5','10/02/2014'),
(4,'3','04/11/2014'),
(5,'3','04/05/2014'),
(6,'4A','01/09/2014'),
(7,'4A','01/05/2014')
create table #LoanDetailsTable (
ID int not null,
LoanAmount INT not null,
LoanDate datetime not null
);
insert into #LoanDetailsTable values
(1,5000,'10/15/2014'),
(2,1000,'10/11/2014'),
(3,2000,'10/09/2014'),
(4,1500,'04/13/2014'),
(5,5000,'04/17/2014'),
(6,500,'01/19/2014'),
(7,2500,'01/15/2014')
Query
;With RegCode
AS
(
SELECT RegionCode, MAX(MONTH(LoanDate)) [Month]
FROM #LoanTable
GROUP BY RegionCode
)
SELECT LDT.* , RC.RegionCode
FROM #LoanDetailsTable LDT INNER JOIN RegCode RC
ON MONTH(LDT.LoanDate) = RC.[Month]
Results
ID LoanAmount LoanDate RegionCode
1 5000 2014-10-15 00:00:00.000 5
2 1000 2014-10-11 00:00:00.000 5
3 2000 2014-10-09 00:00:00.000 5
4 1500 2014-04-13 00:00:00.000 3
5 5000 2014-04-17 00:00:00.000 3
6 500 2014-01-19 00:00:00.000 4A
7 2500 2014-01-15 00:00:00.000 4A
Using CTE extract the Month part of the date along with Region Code associated with it, then join it with you data table on Month of the loan date and extracted month in cte and get the Region code whatever it is at that time. happy days :)