MySql - build a view from 2 table with some calculations - mysql

I try to put together i bookingsystem and this part is left to understand. Can this be done with a CASE statement or how should i think?
I am a beginner and like short/easy code.
How do i get it from here...
date_source_table
---id---+-----date------+---times---+---
1 2020-01-02 3
2 2020-01-03 2
3 2020-01-04 3
time_source_table
---id---+----time------
1 09:00
2 09:30
3 10:00
date_time_result_view
---id---+-----date------+---time---+---
1 2020-01-02 09:00
2 2020-01-02 09:30
3 2020-01-02 10:00
4 2020-01-03 09:00
5 2020-01-03 09:30
6 2020-01-04 09:00
7 2020-01-04 09:30
8 2020-01-04 10:00
Best regards
/Svante

In MySQL 8+ You can use the row_number() window function to number the records in time_source_table. You can then join them to date_source_table on these numbers being less than or equal to date_source_table.times. You can also use row_number() to get the "IDs" for the output, if theses should in fact be just numbers from 1 to n.
SELECT row_number() OVER (ORDER BY dso.date,
tso.time) id,
dso.date,
tso.time
FROM date_source_table dso
INNER JOIN (SELECT tsi.time,
row_number() OVER (ORDER BY tsi.time) rn
FROM time_source_table tsi) tso
ON tso.rn <= dso.times;
If time_source_table.id is guaranteed to be in order of time and has no gaps you can also use that to join on. But if that column is just an AUTO_INCREMENT these guarantees aren't given. That may produce unwanted results all of a sudden. So use that with great care, if you must.

Related

How to execute a query on an unrelated table for every row in SQL

Let's say I have a table like this that tracks the balance of an asset I have in an account:
Delta
NetBalance
Timestamp
2
2
2020-01-01 00:00:00.000
4
6
2020-01-02 00:00:00.000
-1
5
2020-01-03 00:00:00.000
Let's say I have another unrelated table that keeps of track of pricing for my asset:
Price
Timestamp
1.00
2020-01-01 00:00:00.000
1.02
2020-01-01 23:59:00.000
2.01
2020-01-02 10:00:00.000
2.02
2020-01-02 18:00:00.000
3.01
2020-01-03 12:00:00.000
3.02
2020-01-03 13:59:00.000
I'm looking for a query that will yield a result set with the columns from the first table, plus the closest price (from the exact moment, or the past) from the second table and its associated timestamp, so, something like this:
Delta
NetBalance
Timestamp
MostRecentPrice
MostRecentPriceTimestamp
2
2
2020-01-01 00:00:00.000
1.00
2020-01-01 00:00:00.000
4
6
2020-01-02 00:00:00.000
1.02
2020-01-01 23:59:00.000
-1
5
2020-01-03 00:00:00.000
2.02
2020-01-02 18:00:00.000
Working with MySQL here. Would prefer to avoid things like cross joins because the tables themselves are pretty huge, but open to suggestions.
You can try to use LAG window function get previous Timestamp from account then do join with unrelated table.
Then use ROW_NUMBER window function to get MostRecent data rows.
SELECT *
FROM (
SELECT *,
row_number() OVER(PARTITION BY MONTH(Timestamp),DAY(Timestamp) ORDER BY MostRecentPriceTimestamp DESC) rn
FROM (
SELECT a.Delta,
a.NetBalance,
a.Timestamp,
u.Timestamp MostRecentPriceTimestamp,
u.Price MostRecentPrice
FROM (
SELECT *,LAG(Timestamp,1,Timestamp) OVER(ORDER BY Timestamp) prev_Timestamp
FROM account a
) a
INNER JOIN unrelated u
ON u.Timestamp BETWEEN a.prev_Timestamp AND a.Timestamp
) t1
) t1
WHERE rn = 1
sqlfiddle

Loading multiple time series simultaneously using SQL

Suppose I have this exact dataset:
date
widget ID
widget price
widget expiry date
2020-01-01
A
1
2020-03-01
2020-01-01
B
2
2020-04-01
2020-01-01
C
3
2020-05-01
2020-01-01
D
4
2020-06-01
2020-01-02
A
1.1
2020-03-01
2020-01-02
B
2.05
2020-04-01
2020-01-02
C
3.7
2020-05-01
2020-01-02
D
3.8
2020-06-01
2020-01-03
A
1.15
2020-03-01
2020-01-03
B
2.09
2020-04-01
2020-01-03
C
3.54
2020-05-01
2020-01-03
D
4.2
2020-06-01
2020-01-04
A
1.19
2020-03-01
2020-01-04
B
2.14
2020-04-01
2020-01-04
C
3.73
2020-05-01
2020-01-04
D
4.30
2020-06-01
Say I wanted to simultaneously retrieve the full time series of the two following widgets using a single SQL query:
the widget which on date 2020-01-01 had price as close as possible to 1 and expiry date as close as possible to 2020-03-10.
the widget which on date 2020-01-03 had price as close as possible to 3.5 and expiry date as close as possible to 2020-05-15.
In other words, this exact table:
date
widget ID
widget price
widget expiry date
2020-01-01
A
1
2020-03-01
2020-01-01
C
3
2020-05-01
2020-01-02
A
1.1
2020-03-01
2020-01-02
C
3.7
2020-05-01
2020-01-03
A
1.15
2020-03-01
2020-01-03
C
3.54
2020-05-01
2020-01-04
A
1.19
2020-03-01
2020-01-04
C
3.73
2020-05-01
How would you recommend going about it?
Generalising this example, suppose you had a list of tuples like below, where price_i is a target price and expiry_date_i is a target expiry date.
(date_1, price_1, expiry_date_1), (date_2, price_2, expiry_date_2),
(date_3, price_3, expiry_date_3),...
How would you load all of the corresponding widgets' time series in one go?
For the time being I am retrieving these widgets' IDs separately using a SQL query like this one (in this example date='2020-01-01', price=1, expiry date='2020-03-10'). Then collecting all of these retrieved IDs I load the full widget time series.
WITH sample AS
(SELECT *, ABS(DATEDIFF(day,widget_expiry_date, '2020-03-10')) AS date_diff, ABS(widget_price - 1) As price_diff
FROM data WHERE date='2020-01-01'
ORDER BY date_diff ASC, price_diff ASC)
SELECT TOP 1 widget_ID FROM sample
As you can imagine this is extremely inefficient. I wonder if there is a smarter way about it?
Thank you for your time and apologies in advance for the noobish question.
Retrieving all the series in a single query
with params (date_, price_, expiry_date_) AS (
select date '2020-01-01', 1, date '2020-03-10' union all
select date '2020-01-03', 3.5, date '2020-05-15'
)
select data.*
from params p
join data on data.widgetID = (
SELECT widgetID
FROM data d
WHERE d.date = p.date_
ORDER BY ABS(DATEDIFF(d.widget_expiry_date, p.expiry_date_)) ASC, ABS(d.widget_price - p.price_) ASC
LIMIT 1);
db<>fiddle
you also can use window functions:
SELECT indate , widgetID , price , expirydate FROM (
SELECT *
, ROW_NUMBER() OVER (PARTITION BY indate ORDER BY ABS(price - 1), ABS(DATEDIFF(expirydate, '2020-03-10')) ) rn1
, ROW_NUMBER() OVER (PARTITION BY indate ORDER BY ABS(price - 3.5), ABS(DATEDIFF(expirydate, '2020-05-15')) ) rn2
FROM widgets
) t
WHERE rn1 =1 OR rn2 = 1
ORDER BY indate , widgetID
db<>fiddle here

How to group rows into output columns, by date

I have data in a MariaDB table similar to the following. It is basically weather data at two locations, and I want to feed the data to a stats program. I want to output the data in a way that groups the rows by datetime, but puts the grouped row values into columns.
obsv_location obsv_value obsv_datetime
------------- ---------- -------------
airport1 35.0 2020-01-01 12:00
airport2 35.2 2020-01-01 12:00
airport1 36.5 2020-01-01 13:00
airport2 36.4 2020-01-01 13:00
Is it possible to create a query that outputs something like the following?
obsv_datetime airport1 airport2
------------- -------- -------------
2020-01-01 12:00 35.0 35.2
2020-01-01 13:00 36.5 36.4
One method uses join; another conditional aggregation. The second:
select obsv_datetime,
max(case when obsv_location = 'airport1' then obsv_value end) as airport1,
max(case when obsv_location = 'airport2' then obsv_value end) as airport2
from t
group by obsv_datetime;

How to create a SQL query that calculate monthly grow in population

I want to create a SQL query that count the number of babies born in month A, then it should count the babies born in month B but the second record should have the sum of month A plus B. For example;
Month | Number
--------|---------
Jan | 5
Feb | 7 <- Here were 2 babies born but it have the 5 of the previous month added
Mar | 13 <- Here were 6 babies born but it have the 7 of the two previous months added
Can somebody maybe please help me with this, is it possible to do something like this?
I have a straight forward table with babyID, BirthDate, etc.
Thank you very much
Consider using a subquery that calculates a running count. Both inner and outer query would be aggregate group by queries:
Using the following sample data:
babyID Birthdate
1 2015-01-01
2 2015-01-15
3 2015-01-20
4 2015-02-01
5 2015-02-03
6 2015-02-21
7 2015-03-11
8 2015-03-21
9 2015-03-27
10 2015-03-30
11 2015-03-31
SQL Query
SELECT MonthName(BirthDate) As BirthMonth, Count(*) As BabyCount,
(SELECT Count(*) FROM BabyTable t2
WHERE Month(t2.BirthDate) <= Month(BabyTable.BirthDate)) As RunningCount
FROM BabyTable
GROUP BY Month(BirthDate)
Output
BirthMonth BabyCount RunningCount
January 3 3
February 3 6
March 5 11

query to show items that are NOT in the list

This query returns me the list of room #077 that is occupied on specific day; how do I reverse this query and show only times that are NOT in the database between 07:00:00 and 22:00:00? (30 minutes intervals) and each class should take only 1 hour and 30 minutes
select
*
from
(select
rooms.id, rooms.number, rooms.building, rooms.capacity
from
rooms) R1,
(select
exam_schedules.room_id,
exam_schedules.day,
exam_schedules.start_time,
exam_schedules.end_time
from
exam_schedules) R2
where
R2.room_id = R1.id and R2.day = 'tuesday' and R1.number = '077'
This is the result:
ID number Bulding Capacity room_id day start_time end_time
1 077 ACT 12 1 tuesday 10:30:00 12:00:00
But I need the result that is shown below (which is pretty much shows the AVAILABLE times that are NOT occupied by exams AND could no be occupied by other exam since it might cause scheduling issues: for example if exam is already scheduled at 10.30, only 9.00 (not 9.30) should be shown since if 9.30 is shown - it will cause the conflict: 9.30+1.30 = 11.00 - but I already have the 10.30-12.00 scheduled for this room)
1 077 ACT 12 1 tuesday 07:00:00
1 077 ACT 12 1 tuesday 07:30:00
1 077 ACT 12 1 tuesday 08:00:00
1 077 ACT 12 1 tuesday 08:30:00
1 077 ACT 12 1 tuesday 09:00:00
//note that time frame from 9.30-10.30 is not available since there is a class at 10.30 scheduled already
//note that time period 10.30-<12.00 not shown since class is already scheduled for this timeframe 10.30-12.00
1 077 ACT 12 1 tuesday 12:00:00
1 077 ACT 12 1 tuesday 12:30:00
1 077 ACT 12 1 tuesday 13:00:00
1 077 ACT 12 1 tuesday 13:30:00
1 077 ACT 12 1 tuesday 14:00:00
1 077 ACT 12 1 tuesday 14:30:00
1 077 ACT 12 1 tuesday 15:00:00
1 077 ACT 12 1 tuesday 15:30:00
1 077 ACT 12 1 tuesday 16:00:00
1 077 ACT 12 1 tuesday 16:30:00
1 077 ACT 12 1 tuesday 17:00:00
1 077 ACT 12 1 tuesday 17:30:00
1 077 ACT 12 1 tuesday 18:00:00
1 077 ACT 12 1 tuesday 18:30:00
1 077 ACT 12 1 tuesday 19:00:00
1 077 ACT 12 1 tuesday 19:30:00
1 077 ACT 12 1 tuesday 20:00:00
1 077 ACT 12 1 tuesday 20:30:00
1 077 ACT 12 1 tuesday 21:00:00
1 077 ACT 12 1 tuesday 21:30:00
1 077 ACT 12 1 tuesday 22:00:00
The table with all possible timeframes does not exist in the database. Can I maybe hadcode it into the query?
You need to create the list of all possible values. You can do this with a query like:
select *
from (select '2012-01-01 7:00:00' from dual union all
select '2012-01-01 7:30:00' from dual union all . . .
) times
That is, you can use union all to put the data together. I find it easiest to use Excel to put together the SQL statements needed to make this work.
That said, a calendar/calendar-time table is something you should consider. Having a table with each possible time slot it in will probably help you in the future.
Take for example this query:
SELECT * FROM Persons WHERE Year='1965'
this sql query will return a data set containing all the rows from the 'Persons' table where all the 'Year' fields equal to '1965'. As you said, you wanted to reverse your sql query so it returns the opposite data set. This is made possible in the WHERE clause, where you can use the not equal to operator: <> or != in some versions of SQL
SELECT * FROM Persons WHERE Year<>'1965'
or
SELECT * FROM Persons WHERE Year!='1965'
will return a data set containing rows from the 'Persons' table where its field does not equal to '1965'.
Hope this helps in your sql query. you simply need to implement the <> or != operator where needed to return the opposite data set from your database this is assuming you wanted the opposite data from the data you're already having returned with your current query.
If this is not what you wanted please update your question so that you clearly explain what output you expect it to have - an example - so that a query can be made to retrieve the desired results.
Let me give you what I think is the answer first:
I think what you are looking for is Between ... And.... Or better to say Not(Between .. And..). Between ... And... is a range operator. So x Between 1 And 10 = 1<x<10 and Not x Between 1 And 10 = x<1 || x>10.
There are certain part of the query that are plain wrong to me:
Why do you have the select * anyway? You could just join the to tables like:
select
rooms.id, rooms.number, rooms.building, rooms.capacity,exam_schedules.room_id,
exam_schedules.day,
exam_schedules.start_time,
exam_schedules.end_time
from
rooms,exam_schedules
where
exam_schedules.room_id = rooms.id and exam_schedules.day = 'tuesday' and rooms.number = '077'
I generally do this with a left join and include only rows that are not in the right table.
SELECT
*
FROM
(select
rooms.id, rooms.number, rooms.building, rooms.capacity
from
rooms) R1
LEFT JOIN (select
exam_schedules.room_id,
exam_schedules.day,
exam_schedules.start_time,
exam_schedules.end_time
from
exam_schedules) R2
ON
R2.room_id = R1.id and R2.day = R1.day and (R2.start BETWEEN R1.start AND R1.end OR R2.end BETWEEN R1.start AND R1.end OR R1.start BETWEEN R2.start AND R2.end OR R1.end BETWEEN R2.start AND R2.end)
WHERE R2.room_id IS NULL;