Availability in a reservation database - sql

I have a database for hotel reservation with tables: room, customer, reservation (id, id_room, id_customer_ arrive_date, departure_date, ...).
When I select a room in my app I need to view a calendar widget with days red colored if in that day the room is busy.
I need a way to retrieve a list of busy days for a room,month,year combination.
My idea is to create a new table from previous with columns: date,day,month,year,room,is_busy and then query it.
SELECT day FROM new_table WHERE month=m AND year=y AND room=r AND is_busy=1
The problem is to update the new table every time.
Is there a simple way?

You can try this script. It will return busy dates for each room for next 255 days from now.
SELECT * INTO reservation
FROM (VALUES (1, 1, '2016-07-03','2016-07-06'),(2, 2, '2016-07-10','2016-07-15'))
a(CustomerID, RoomID, arrive_date, departure_date);
GO
;WITH Pass0 as (select 1 as C union all select 1),
Pass1 as (select 1 as C from Pass0 as A, Pass0 as B),
Pass2 as (select 1 as C from Pass1 as A, Pass1 as B),
Pass3 as (select 1 as C from Pass2 as A, Pass2 as B),
FutureDates(FutureDates) as (SELECT DATEADD(DAY,ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) - 1, CAST(GETDATE() AS DATE)) FROM Pass3)
SELECT r.RoomID, f.FutureDates as BusyDate
FROM reservation as r
INNER JOIN FutureDates as f
ON f.FutureDates >= r.arrive_date and f.FutureDates < r.departure_date;

Related

Snowflake/SQL: create a time-series table such that every ID is visible, and if ID is null, it uses the previous value? (similar to shift)

Suppose I have the following table:
Day
ID
Value
2022-11-05
0
A
2022-11-06
1
B
2022-11-07
0
C
Now given a time window of 1 day, I want to create a time-series table that:
The Day column granular unit is 1 day
Each Day row displays every ID in the table (like cross-join)
Moreover, if for that day, the ID is not recorded, then it uses the Value from the previous day. If it does not exist before this day, we can ignore it.
Let's say I want to view this time series from 2022-11-05 to 2022-11-08, this is the desired output:
Day
ID
Value
2022-11-05
0
A
2022-11-06
0
A
2022-11-06
1
B
2022-11-07
0
C
2022-11-07
1
B
2022-11-08
0
C
2022-11-08
1
B
Explanation: ID=0 is not recorded on 11-06 so it uses the value from the previous day. ID=1 does not record new value on 11-07 so it uses the value from 11-06.
Note that the number of columns can be large, so if possible, I am looking for a solution that handles it too.
Way One:
first we start with some data
then we find the_days in the period we are interested in
then we find the data_start for each id
then we join those values together, and use LAG with the IGNORE NULLS OVER clause to find the "prior values" if the current values in not present via NVL
with data(Day, ID, Value) as (
select * from values
('2022-11-05'::date, 0, 'A'),
('2022-11-06'::date, 1, 'B'),
('2022-11-07'::date, 0, 'C')
), the_days as (
select
row_number() over (order by null)-1 as rn
,dateadd('day', rn, from_day) as day
from (
select
min(day) as from_day
,'2022-11-08' as to_day
,datediff('days', from_day, to_day) as days
from data
), table(generator(ROWCOUNT => 200))
qualify rn <= days
), data_starts as (
select
id,
min(day) as start_day
from data
group by 1
)
select
td.day,
ds.id,
nvl(d.value, lag(d.value) ignore nulls over (partition by ds.id order by td.day)) as value
from data_starts as ds
join the_days as td
on td.day >= ds.start_day
left join data as d
on ds.id = d.id and d.day = td.day
order by 1,2;
gives:
DAY
ID
VALUE
2022-11-05
0
A
2022-11-06
0
A
2022-11-06
1
B
2022-11-07
0
C
2022-11-07
1
B
2022-11-08
0
C
2022-11-08
1
B
Way Two:
with data(Day, ID, Value) as (
select * from values
('2022-11-05'::date, 0, 'A'),
('2022-11-06'::date, 1, 'B'),
('2022-11-07'::date, 0, 'C')
), the_days as (
select
dateadd('day', row_number() over (order by null)-1, '2022-11-05') as day
from table(generator(ROWCOUNT => 4))
)
select
td.day,
i.id,
nvl(d.value, lag(d.value) ignore nulls over (partition by i.id order by td.day)) as _value
from the_days as td
cross join (select distinct id from data) as i
left join data as d
on i.id = d.id and d.day = td.day
qualify _value is not null
order by 1,2;
this requires a unique name for the _values output so it can be referenced in the qualify without needing to duplicate the code.

SQL joining on 2 separate date columns

I am having trouble wrapping my head around a problem and the more I think about it the further away I get.
I have two tables that log user visits into two separate applications. For simplicities sake, let's say there are only 2 columns, timestamp and userid (key). Each row counts as a visit, so technically 3 columns since there is one derived for visits.
I am trying to create a table that in each row records the userid, the day (to_date format), total visits to app 1 and total visits to app 2 for that user on that day.
The issue is, when I join the tables together on userid and date, I get missing data. For example, if a user logged into application A on day X, but did not log into application B on day X, then joining on userid and day causes this record to be omitted since the date only exists in Application A's table.
How can I set this up where the final table would have a singular date column, userid, visits to app A and visits to app B, regardless if the user visited both applications on said day?
Hope this made sense, happy to elaborate if needed. Below is sort of what my SQL looks like as of now. Any thoughts appreciated!
with app_a_visits as (
select to_date(timestamp) as date, userid, count(*) as visits
from app_a),
app_b_visits as (
select to_date(timestamp) as date, userid, count(*) as visits
from app_b)
select a.date, a.userid, a.visits as app_a_visits, b.visits as app_b_visits
from app_a_visits a
full outer join app_b_visits b on a.userid = b.user_id and a.date = b.date;
Use FULL OUTER JOIN and NVL/COALESCE
with app_a_visits(date, userid,visits) as (
select * from values
('2022-01-01'::date, 1, 100),
('2022-01-03'::date, 1, 100),
('2022-01-05'::date, 1, 100)
), app_b_visits(date, userid,visits) as (
select * from values
('2022-01-02'::date, 1, 200),
('2022-01-03'::date, 1, 200),
('2022-01-04'::date, 1, 200)
)
select
NVL(a.date, b.date) as date,
NVL(a.userid, b.userid) as userid,
a.visits as app_a_visits,
b.visits as app_b_visits
from app_a_visits a
full outer join app_b_visits b
on a.userid = b.userid and a.date = b.date
ORDER BY 1,2;
DATE
USERID
APP_A_VISITS
APP_B_VISITS
2022-01-01
1
100
null
2022-01-02
1
null
200
2022-01-03
1
100
200
2022-01-04
1
null
200
2022-01-05
1
100
null

SQL Query get common column with diff values in other columns

I am not very fluent with SQL.. Im just facing a little issue in making the best and efficient sql query. I have a table with a composite key of column A and B as shown below
A
B
C
1
1
4
1
2
5
1
3
3
2
2
4
2
1
5
3
1
4
So what I need is to find rows where column C has both values of 4 and 5 (4 and 5 are just examples) for a particular value of column A. So 4 and 5 are present for two A values (1 and 2). For A value 3, 4 is present but 5 is not, hence we cannot take it.
My explanation is so confusing. I hope you get it.
After this, I need to find only those where B value for 4 (First Number) is less than B value for 5 (Second Number). In this case, for A=1, Row 1 (A-1, B-1,C-4) has B value lesser than Row 2 (A-1, B-2, C-5) So we take this row. For A = 2, Row 1(A-2,B-2,C-4) has B value greater than Row 2 (A-2,B-1,C-5) hence we cannot take it.
I Hope someone gets it and helps. Thanks.
Rows containing both c=4 and c=5 for a given a and ordered by b and by c the same way.
select a, b, c
from (
select tbl.*,
count(*) over(partition by a) cnt,
row_number() over (partition by a order by b) brn,
row_number() over (partition by a order by c) crn
from tbl
where c in (4, 5)
) t
where cnt = 2 and brn = crn;
EDIT
If an order if parameters matters, the position of the parameter must be set explicitly. Comparing b ordering to explicit parameter position
with params(val, pos) as (
select 4,2 union all
select 5,1
)
select a, b, c
from (
select tbl.*,
count(*) over(partition by a) cnt,
row_number() over (partition by a order by b) brn,
p.pos
from tbl
join params p on tbl.c = p.val
) t
where cnt = (select count(*) from params) and brn = pos;
I assume you want the values of a where this is true. If so, you can use aggregation:
select a
from t
where c in (4, 5)
group by a
having count(distinct c) = 2;

Generating a sequence in sql server

I am working on a function that will take a low number and a high number as paramaters and returns a table containing everything between (and including).
I know I could use a cursor and increment a variable adding it to a scope based table every iteration, but I would prefer to avoid a cursor if possible. Does anyone else have a suggestion for a way to do this? (As i'm typing this im thinking possibly a CTE, which I will go investigate).
Yes, you can use a recursive CTE to do this. For example to generate numbers between 10 and 20 inclusive:
WITH f AS
(
SELECT 10 AS x
UNION ALL
SELECT x + 1 FROM f WHERE x < 20
)
SELECT * FROM f
Just create an indexed permanent auxiliary numbers table and be done with it. This will out perform any other method.
See Jeff Moden's answer here for more details and a script to populate such a table. if for some reason that isn't an option this should beat the recursive CTE according to the performance tests in the linked answer.
WITH E00(N) AS (SELECT 1 UNION ALL SELECT 1),
E02(N) AS (SELECT 1 FROM E00 a, E00 b),
E04(N) AS (SELECT 1 FROM E02 a, E02 b),
E08(N) AS (SELECT 1 FROM E04 a, E04 b),
E16(N) AS (SELECT 1 FROM E08 a, E08 b),
E32(N) AS (SELECT 1 FROM E16 a, E16 b),
cteTally(N) AS (SELECT ROW_NUMBER() OVER (ORDER BY N) FROM E32)
SELECT N FROM cteTally
WHERE N BETWEEN 10 AND 20

Generate random SQL Server 2008 time test data

I am trying to generate a large data set which includes time datatype in SQL Server 2008. I already have some non-time data in a table so I'd like to keep the entire process in T-SQL and use an insert-into-select to get the partial data from one table and insert it into the next along with some generated data including the time.
I'd like a way to generate random time(7)s between two points, say a random time between 8:00 and 9:00. I've found some pre-2008 post but nothing that addresses SQL Server 2008's time type.
There are 86,400,000 milliseconds in a day, so you can get a random time value by doing this:
select dateadd(millisecond, cast(86400000 * RAND() as int), convert(time, '00:00'))
For your example where you want times between 8:00 and 9:00, there are 3,600,000 milliseconds in an hour, so modify the query like this.
select dateadd(millisecond, cast(3600000 * RAND() as int), convert(time, '08:00'))
In order to put in into your new table, you might either do a T-SQL loop with updates (s...l...o...w...), or do a SELECT INTO from your original table into a new table.
To generate 100 rows of test data you can use the below.
WITH E00(N) AS (SELECT 1 UNION ALL SELECT 1),
E02(N) AS (SELECT 1 FROM E00 a, E00 b),
E04(N) AS (SELECT 1 FROM E02 a, E02 b),
E08(N) AS (SELECT 1 FROM E04 a, E04 b),
E16(N) AS (SELECT 1 FROM E08 a, E08 b),
E32(N) AS (SELECT 1 FROM E16 a, E16 b)
SELECT TOP 100 CAST(DATEADD(SECOND,ABS(CHECKSUM(NEWID()))%3600,'08:00') AS TIME)
FROM E32