How do i find available date ranges from date ranges - sql

Sql Server
I already added bookings from my hotel room management system reservation data. I want sql query for retrieve rooms available date ranges and also i want find specific date range is available

You can use something like the following. It's not an easy query, I'll try to explain as simple as possible.
Use a recursive CTE to generate dates from a specified start date to a specified end date.
Join each date to the different room IDs you might have in your table to create all potential available dates.
Determine which dates are unavailable for each room.
Determine which dates are available for each room by joining all potential available dates and removing unavailable ones (point 2 vs 3).
Determine how to group by each range (I used a ROW_NUMBER with a DENSE_RANK).
Display results in intervals, for each room.
Script:
-- Period to consider
DECLARE #StartDate DATE = '2018-06-20'
DECLARE #EndDate DATE = '2018-09-01'
;WITH GeneratedDates AS
(
SELECT
GeneratedDate = #StartDate
UNION ALL
SELECT
GeneratedDate = DATEADD(DAY, 1, G.GeneratedDate)
FROM
GeneratedDates AS G
WHERE
G.GeneratedDate < #EndDate
),
ExistingRooms AS
(
SELECT DISTINCT
RoomId
FROM
HotelReservation.dbo.Reservation AS R
),
UnavailableDatesByRoom AS
(
SELECT DISTINCT
R.RoomID,
UnavailableDate = G.GeneratedDate
FROM
HotelReservation.dbo.Reservation AS R
INNER JOIN GeneratedDates AS G ON G.GeneratedDate BETWEEN R.CheckIn AND R.CheckOut
),
AvailableDaysByRoom AS
(
SELECT
AvailableDate = G.GeneratedDate,
E.RoomID,
DateRanking = ROW_NUMBER() OVER (PARTITION BY E.RoomID ORDER BY G.GeneratedDate ASC)
FROM
GeneratedDates AS G
CROSS JOIN ExistingRooms AS E
WHERE
NOT EXISTS (
SELECT
'unavailable date for that room'
FROM
UnavailableDatesByRoom AS U
WHERE
U.RoomID = E.RoomID AND
G.GeneratedDate = U.UnavailableDate)
),
AvailableDaysByRoomGroupings AS
(
SELECT
A.*,
MagicRanking = DENSE_RANK() OVER (PARTITION BY A.RoomID ORDER BY DateRanking - DATEDIFF(DAY, '2010-01-01', A.AvailableDate))
FROM
AvailableDaysByRoom AS A
)
SELECT
G.RoomID,
FirstAvailableStartDate = MIN(G.AvailableDate),
LastAvailableStartDate = MAX(G.AvailableDate)
FROM
AvailableDaysByRoomGroupings AS G
GROUP BY
G.RoomID,
G.MagicRanking
ORDER BY
G.RoomID,
FirstAvailableStartDate
OPTION
(MAXRECURSION 32000)

Related

I want to create table or view from a recursion used to generate a date in ssms

I wrote a recursive query to generate a column pf dates. I want the dates to be stored as a table in a db but can't seem to find a way.
declare #startdate date = '2014-01-01';
declare #enddate date = '2023-12-31';
with calendar as
(
select #startdate as [orderDate]
union all
select DATEADD(dd,1,[orderdate])
from calendar
where DATEADD(dd,1,[orderdate])<= #enddate
)
select * from calendar
option (maxrecursion 0);
you can try this one to fill a new table your_table with the dates.
You can use that as a basis for your further operations.
WITH x AS (SELECT n FROM (VALUES (0),(1),(2),(3),(4),(5),(6),(7),(8),(9)) v(n))
select
convert(date, dat ) Dat
into your_table
from
(
SELECT top 100 percent
ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) Line,
dateadd(day, ROW_NUMBER() OVER (ORDER BY (SELECT NULL)), '2014-01-01') Dat
FROM x ones, x tens, x hundreds, x thousands
ORDER BY 1
) basis
where dat <= '2023-12-31'

Query to pick value depending on date

I have a table with exchange rates which update only when a new exchange rate comes, that is, the only the date that the new rate entered is recorded. however the system has logic to say if any date fall within a particular date, it picks the corresponding exchange rate
i would like to have a query which picks the required exchange rate given any date supplied, i.e., pick the rate from the period.
WITH ListDates(AllDates) AS
( SELECT cast('2015-11-01' as date) AS DATE
UNION ALL
SELECT DATEADD(DAY,1,AllDates)
FROM ListDates
WHERE AllDates < getdate())
SELECT ld.AllDates,cr.effective_from,cr.rate_against_base
FROM ListDates ld
left join CurrencyRatetable cr on cr.effective_from between cr.effective_from and ld.alldates
option (maxrecursion 0)
I guess you might want to achieve the required result using the window function LEAD. Following an example:
DECLARE #t TABLE(effective_from date, rate_against_base decimal(19,4))
INSERT INTO #t VALUES
('2000-01-01', 1.6)
,('2016-10-26', 1)
,('2020-07-13', 65.8765);
DECLARE #searchDate DATE = '2023-01-17';
WITH cte AS(
SELECT effective_from
,ISNULL(LEAD(effective_from) OVER (ORDER BY effective_from), CAST('2049-12-31' AS DATE)) AS effective_to
,rate_against_base
FROM #t
)
SELECT rate_against_base
FROM cte
WHERE #searchDate >= effective_from
AND #searchDate < effective_to
You can use a CROSS APPLY or OUTER APPLY together with a TOP 1 subselect.
Something like:
WITH ListDates(AllDates) AS (
SELECT cast('2015-11-01' as date) AS DATE
UNION ALL
SELECT DATEADD(DAY,1,AllDates)
FROM ListDates
WHERE AllDates < getdate()
)
SELECT ld.AllDates, cr.effective_from, cr.rate_against_base
FROM ListDates ld
OUTER APPLY (
SELECT TOP 1 *
FROM CurrencyRatetable cr
WHERE cr.effective_from <= ld.alldates
ORDER BY cr.effective_from DESC
) cr
ORDER BY ld.AllDates
option (maxrecursion 0)
Both CROSS APPLY or OUTER APPLY are like a join to a subselect. The difference is that CROSS APPLY is like an inner join and OUTER APPLY is like a left join.
Make sure that CurrencyRatetable has an index on effective_from for efficient access.
See this db<>fiddle.

How to extrapolate dates in SQL Server to calculate the daily counts?

This is how the data looks like. It's a long table
I need to calculate the number of people employed by day
How to write SQL Server logic to get this result? I treid to create a DATES table and then join, but this caused an error because the table is too big. Do I need a recursive logic?
For future questions, don't post images of data. Instead, use a service like dbfiddle. I'll anyhow add a sketch for an answer, with a better-prepared question you could have gotten a complete answer. Anyhow here it goes:
-- extrema is the least and the greatest date in staff table
with extrema(mn, mx) as (
select least(min(hired),min(retired)) as mn
, greatest(max(hired),max(retired)) as mx
from staff
), calendar (dt) as (
-- we construct a calendar with every date between extreme values
select mn from extrema
union all
select dateadd(day, 1, d)
from calendar
where dt < (select mx from extrema)
)
-- finally we can count the number of employed people for each such date
select dt, count(1)
from calendar c
join staff s
on c.dt between s.hired and s.retired
group by dt;
If you find yourself doing this kind of calculation often, it is a good idea to create a calendar table. You can add other attributes to it such as if it is a day of in the middle of the week etc.
With a constraint as:
CHECK(hired <= retired)
the first part can be simplified to:
with extrema(mn, mx) as (
select min(hired) as mn
, max(retired) as mx
from staff
),
Assuming Current Employees have a NULL retirement date
Declare #Date1 date = '2015-01-01'
Declare #Date2 date = getdate()
Select A.Date
,HeadCount = count(B.name)
From ( Select Top (DateDiff(DAY,#Date1,#Date2)+1)
Date=DateAdd(DAY,-1+Row_Number() Over (Order By (Select Null)),#Date1)
From master..spt_values n1,master..spt_values n2
) A
Left Join YourTable B on A.Date >= B.Hired and A.Date <= coalesce(B.Retired,getdate())
Group BY A.Date
You need a calendar table for this. You start with the calendar, and LEFT JOIN everything else, using BETWEEN logic.
You can use a real table. Or you can generate it on the fly, like this:
WITH
L0 AS ( SELECT c = 1
FROM (VALUES(1),(1),(1),(1),(1),(1),(1),(1),
(1),(1),(1),(1),(1),(1),(1),(1)) AS D(c) ),
L1 AS ( SELECT c = 1 FROM L0 A, L0 B, L0 C, L0 D ),
Nums AS ( SELECT rownum = ROW_NUMBER() OVER(ORDER BY (SELECT 1))
FROM L1 ),
Dates AS (
SELECT TOP (DATEDIFF(day, '20141231', GETDATE()))
Date = DATEADD(day, rownum, '20141231')
FROM Nums
)
SELECT
d.Date,
NumEmployed = COUNT(*)
FROM Dates d
JOIN YourTable t ON d.Date BETWEEN t.Hired AND t.Retired
GROUP BY
d.Date;
If your dates have a time component then you need to use >= AND < logic
Try limiting the scope of your date table. In this example I have a table of dates named TallyStickDT.
SELECT dt, COUNT(name)
FROM (
SELECT dt
FROM tallystickdt
WHERE dt >= (SELECT MIN(hired) FROM #employees)
AND dt <= GETDATE()
) A
LEFT OUTER JOIN #employees E ON A.dt >= E.Hired AND A.dt <= e.retired
GROUP BY dt
ORDER BY dt

How to add a set of dates for each category in a dimension?

I have data that looks like this where there is a monthly count of a particular animal for each month. By default, it aggregates in the month where there is data.
However, I would like to like to have a default set of dates for each animal up to the current month date with 0 if there's no data. Desired Result -
Is there a way to handle with a on sql server and not in Excel?
Much appreciated in advance.
You can generate the months you want using a numbers table or recursive CTE (or calendar table). Then cross join with the animals to generate the rows and use left join to bring in the existing data:
with dates as (
select min(date) as dte
from t
union all
select dateadd(month, 1 dte)
from dates
where dte < getdate()
)
select a.animal, d.dte, coalesce(t.monthly_count, 0) as monthly_count
from dates d cross join
(select distinct animal from t) a left join
data t
on t.date = d.dte and t.animal = a.animal
order by a.animal, d.dte;

Summing up columns from two different tables

I have two different tables FirewallLog and ProxyLog. There is no relation between these two tables. They have four common fields :
LogTime ClientIP BytesSent BytesRec
I need to Calculate the total usage of a particular ClientIP for each day over a period of time (like last month) and display it like below:
Date TotalUsage
2/12 125
2/13 145
2/14 0
. .
. .
3/11 150
3/12 125
TotalUsage is SUM(FirewallLog.BytesSent + FirewallLog.BytesRec) + SUM(ProxyLog.BytesSent + ProxyLog.BytesRec) for that IP. I have to show Zero if there is no usage (no record) for that day.
I need to find the fastest solution to this problem. Any Ideas?
First, create a Calendar table. One that has, at the very least, an id column and a calendar_date column, and fill it with dates covering every day of every year you can ever be interested in . (You'll find that you'll add flags for weekends, bankholidays and all sorts of other useful meta-data about dates.)
Then you can LEFT JOIN on to that table, after combining your two tables with a UNION.
SELECT
CALENDAR.calendar_date,
JOINT_LOG.ClientIP,
ISNULL(SUM(JOINT_LOG.BytesSent + JOINT_LOG.BytesRec), 0) AS TotalBytes
FROM
CALENDAR
LEFT JOIN
(
SELECT LogTime, ClientIP, BytesSent, BytesRec FROM FirewallLog
UNION ALL
SELECT LogTime, ClientIP, BytesSent, BytesRec FROM ProxyLog
)
AS JOINT_LOG
ON JOINT_LOG.LogTime >= CALENDAR.calendar_date
AND JOINT_LOG.LogTime < CALENDAR.calendar_date+1
WHERE
CALENDAR.calendar_date >= #start_date
AND CALENDAR.calendar_date < #cease_date
GROUP BY
CALENDAR.calendar_date,
JOINT_LOG.ClientIP
SQL Server is very good at optimising this type of UNION ALL query. Assuming that you have appropriate indexes.
If you don't have a calendar table, you can create one using a recursive CTE:
declare #startdate date = '2013-02-01';
declare #enddate date = '2013-03-01';
with dates as (
select #startdate as thedate
union all
select dateadd(day, 1, thedate)
from dates
where thedate < #enddate
)
select driver.thedate, driver.ClientIP,
coalesce(fwl.FWBytes, 0) + coalesce(pl.PLBytes, 0) as TotalBytes
from (select d.thedate, fwl.ClientIP
from dates d cross join
(select distinct ClientIP from FirewallLog) fwl
) driver left outer join
(select cast(fwl.logtime as date) as thedate,
SUM(fwl.BytesSent + fwl.BytesRec) as FWBytes
from FirewallLog fwl
group by cast(fwl.logtime as date)
) fwl
on driver.thedate = fwl.thedate and driver.clientIP = fwl.ClientIP left outer join
(select cast(pl.logtime as date) as thedate,
SUM(pl.BytesSent + pl.BytesRec) as PLBytes
from ProxyLog pl
group by cast(pl.logtime as date)
) pl
on driver.thedate = pl.thedate and driver.ClientIP = pl.ClientIP
This uses a driver table that generates all the combinations of IP and date, which it then uses for joining to the summarized table. This formulation assumes that the "FirewallLog" contains all the "ClientIp"s of interest.
This also breaks out the two values, in case you also want to include them (to see which is contributing more bytes to the total, for instance).
I would recommend creating a Dates Lookup table if that is an option. Create the table once and then you can use it as often as needed. If not, you'll need to look into creating a Recursive CTE to act as the Dates table (easy enough -- look on stackoverflow for examples).
Select d.date,
results.ClientIp
Sum(results.bytes)
From YourDateLookupTable d
Left Join (
Select ClientIp, logtime, BytesSent + BytesRec bytes From FirewallLog
Union All
Select ClientIp, logtime, BytesSent + BytesRec bytes From ProxyLog
) results On d.date = results.logtime
Group By d.date,
results.ClientIp
This assumes the logtime and date data types are the same. If logtime is a date time, you'll need to convert it to a date.