Insert certain weekday within a week if it doesn't exist - mysql

I have a simple table as follows:
DROP TABLE IF EXISTS table_name;
CREATE TABLE table_name
(symbol CHAR(4)
,time DATE
,bid DECIMAL (7,2)
,ask DECIMAL (7,2)
,PRIMARY KEY(symbol,time)
);
insert into table_name
(symbol,`time`,bid,ask)
values
('CN50','2020-09-25',15077.5, 15087.5),
('CN50','2020-09-28',15255 , 15265 ),
('CN50','2020-09-29',15257 , 15267 ),
('CN50','2020-09-30',15258 , 15268 ),
('CN50','2020-10-01',15259 , 15269 );
SELECT *, DATE_FORMAT(time,'%a') dow FROM table_name;
+--------+------------+----------+----------+------+
| symbol | time | bid | ask | dow |
+--------+------------+----------+----------+------+
| CN50 | 2020-09-25 | 15077.50 | 15087.50 | Fri |
| CN50 | 2020-09-28 | 15255.00 | 15265.00 | Mon |
| CN50 | 2020-09-29 | 15257.00 | 15267.00 | Tue |
| CN50 | 2020-09-30 | 15258.00 | 15268.00 | Wed |
| CN50 | 2020-10-01 | 15259.00 | 15269.00 | Thu |
+--------+------------+----------+----------+------+
What I wish to achieve is that if there is no value for Sunday within a week, insert a new row between this Friday and next Monday with Sunday date in time column and copy the bid/ask values from Friday.
In this example, 2020-09-25 is Friday and 2020-09-28 is Monday, I would expect to insert a row with 2020-09-27 as time and keep the bid and ask value from last Friday which is 2020-09-25.
Before:
Symbol
time
bid
ask
CN50
2020-09-25
15077.5
15087.5
CN50
2020-09-28
15255
15265
After:
Symbol
time
bid
ask
CN50
2020-09-25
15077.5
15087.5
CN50
2020-09-27
15077.5
15087.5
CN50
2020-09-28
15255
15265

As mentioned, I would handle this kind of thing in application code, but if you know that Friday's going to be there (and, crucially, that Sunday isn't!) , then something like this would suffice...
SELECT *
FROM table_name
UNION
SELECT symbol
, time + INTERVAL 2 DAY
, bid
, ask
FROM table_name
WHERE DATE_FORMAT(time + INTERVAL 2 DAY,'%a') = 'sun'
ORDER
BY symbol
, time;
+--------+------------+----------+----------+
| symbol | time | bid | ask |
+--------+------------+----------+----------+
| CN50 | 2020-09-25 | 15077.50 | 15087.50 |
| CN50 | 2020-09-27 | 15077.50 | 15087.50 |
| CN50 | 2020-09-28 | 15255.00 | 15265.00 |
| CN50 | 2020-09-29 | 15257.00 | 15267.00 |
| CN50 | 2020-09-30 | 15258.00 | 15268.00 |
| CN50 | 2020-10-01 | 15259.00 | 15269.00 |
+--------+------------+----------+----------+
If there's a chance that Sunday could be there already, then you could left join this result onto the original data set, and use COALESCE to filter the correct value - but by this point you'd probably want switch back to application code or employ the CTE/Windowing tools available in MySQL 8.0+

Related

Find the name, start date and duration in months of projects that have the earliest end date - mysql

I was studying subqueries in sql and couldn't figure out the answer to this question. Following are the tables.
Project(p͟r͟o͟j͟n͟o͟, projname, prestdate, prendate)
+--------+----------------------+------------+------------+
| projno | projname | prstdate | prendate |
+--------+----------------------+------------+------------+
| AD3100 | Admin Services | 2014-01-01 | 2015-02-01 |
| AD3110 | General AD Systems | 2014-01-01 | 2015-02-01 |
| MA2113 | W L Prod Cont Progs | 2014-02-15 | 2014-12-01 |
| PL2100 | Weld Line Planning | 2014-01-01 | 2014-09-15 |
I came up with this but I think its wrong:
select projname
, prstdate
, MONTH(prendate - prstdate) as duration
from Proj
where prendate - prstdate IN (select MIN(prendate - prstdate) from Proj);
You are on the right track. But your query is looking at the duration of the projects, not the end date.
Second, the calculation of the duration is off. The best function to use is TIMESTAMPDIFF().
So, just modify the code to look at the end date instead of the duration:
select projnamek, prstdate
timestampdiff(month, prstdate, prendate) as duration
from Proj
where prendate = (select min(prendate) from Proj);

MySQL monthly diffs

i spent lot of time to search a solution to my problem. i think i'm near the solution but my final request doesn't work...
first of all, i have a table that represent water index based on 10 minutes sampling.
----------------------------------
| DateTime | Counter |
----------------------------------
| 2020-05-13 15:00:03 | 38450 |
| 2020-05-13 15:10:03 | 38454 |
| 2020-05-15 15:00:03 | 38500 |
| 2020-06-02 12:10:03 | 38510 |
| 2020-06-15 12:10:03 | 38600 |
----------------------------------
Some samples could be not present in the table
so i would like to extract a table to see my consumptions by days, week, month, year
i have found many examples, but none works as i expect...
for the example table above, i expect to get:
------------------------------------------------------------------------------
| fromDateTime | toDateTime | fromCounter | toCounter | diff |
------------------------------------------------------------------------------
| 2020-05-13 15:00:03 | 2020-06-02 12:10:03 | 38450 | 38510 | 60 |
| 2020-06-02 12:10:03 | 2020-06-15 12:10:03 | 38510 | 38600 | 90 |
------------------------------------------------------------------------------
i have writen a query
select mt1.DateTime as fromDateTime,
mt2.DateTime as toDateTime,
mt1.Counter as fromCounter,
mt2.Counter as toCounter,
(mt2.Counter - mt1.Counter) as diff
from WaterTest as mt1
left join WaterTest as mt2
on mt2.DateTime=(
select max(dd.datetime) as DateTime from (SELECT MIN(DateTime) as DateTime
FROM WaterTest as mt3
WHERE month(mt3.DateTime) = month(mt1.DateTime + INTERVAL 1 month)
union ALL
select max(DateTime) as DateTime
from WaterTest as mt4
where month(mt4.DateTime) = month(mt1.DateTime)
) as dd
)
But MySql results with an error saying "Field 'mt1.DateTime' unknown in where clause"
is someone can help to find where i'm wrong ?
Am I on the good way to achieve this ?
(and for sure, if there is a more powerfull request.... :) )

How can I select all rows which have been inserted in the last day?

I have a table like this:
// reset_password_emails
+----+----------+--------------------+-------------+
| id | id_user | token | unix_time |
+----+----------+--------------------+-------------+
| 1 | 2353 | 0c274nhdc62b9dc... | 1339412843 |
| 2 | 2353 | 0934jkf34098joi... | 1339412864 |
| 3 | 5462 | 3408ujf34o9gfvr... | 1339412894 |
| 4 | 3422 | 2309jrgv0435gff... | 1339412899 |
| 5 | 3422 | 34oihfc3lpot4gv... | 1339412906 |
| 6 | 2353 | 3498hfjp34gv4r3... | 1339412906 |
| 16 | 2353 | asdf3rf3409kv39... | 1466272801 |
| 7 | 7785 | 123dcoj34f43kie... | 1339412951 |
| 9 | 5462 | 3fcewloui493e4r... | 1339413621 |
| 13 | 8007 | 56gvb45cf3454g3... | 1339424860 |
| 14 | 7785 | vg4er5y2f4f45v4... | 1339424822 |
+----+----------+--------------------+-------------+
Each row is an email. Now I'm trying to implement a limitation for sending-reset-password email. I mean an user can achieve 3 emails per day (not more).
So I need an query to check user's history for the number of emails:
SELECT count(1) FROM reset_password_emails WHERE token = :token AND {from not until last day}
How can I implement this:
. . . {from now until last day}
Actually I can do that like: NOW() <= (unix_time + 86400) .. But I guess there is a better approach by using interval. Can anybody tell me what's that?
Your expression will work, but has 3 problems:
the way you've coded it means the subtraction must be performed for every row (performance hit)
because you're not using the raw column value, you couldn't use an index on the time column (if one existed)
it isn't clear to read
Try this:
unix_time > unix_timestamp(subdate(now(), interval '1' day))
here the threshold datetime is calculated once per query, so all of the problems above have been addressed.
See SQLFiddle demo
You can convert your unix_time using from_unixtime function
select r.*
from reset_password_emails r
where now() <= from_unixtime(r.unix_time) - interval '1' day
Just add the extra filters you want.
See it here: http://sqlfiddle.com/#!9/4a7a9/3
It evaluates to no rows because your given data for unix_time field is all from 2011
Edited with a sqlfiddle that show the conversion:
http://sqlfiddle.com/#!9/4a7a9/4

MySQL complex date calculation in inner join

I am reading the MySql tutorial in the docs and have the following tables and SQL statements:
Event table:
+----------+------------+----------+------------------------------+
| name | date | type | remark |
+----------+------------+----------+------------------------------+
| Fluffy | 1995-05-15 | litter | 4 kittens, 3 females, 1 male |
| Buffy | 1993-06-23 | litter | 5 puppies, 2 female, 3 male |
| Buffy | 1994-06-19 | litter | 3 puppies, 3 female |
| Chirpy | 1999-03-21 | vet | needed beak streightened |
| Slim | 1997-08-03 | vet | broken rib |
| Bowser | 1991-10-12 | kennel | NULL |
| Fang | 1991-10-12 | kennel | NULL |
| Fang | 1998-08-28 | birthday | Gave him new chew toy |
| Claws | 1998-03-17 | birthday | Gave him a flea collar |
| Whistler | 1998-12-09 | birthday | First birthday |
+----------+------------+----------+------------------------------+
Pet table:
+----------+--------+---------+------+------------+------------+
| name | owner | species | sex | birth | death |
+----------+--------+---------+------+------------+------------+
| Fluffy | Harold | cat | f | 1993-02-04 | NULL |
| Claws | Gwen | cat | m | 1994-03-17 | NULL |
| Buffy | Harold | dog | f | 1989-05-13 | NULL |
| Fang | Benny | dog | m | 1990-08-27 | NULL |
| Bowser | Diane | dog | m | 1989-03-31 | 1995-07-29 |
| Chirpy | Gwen | bird | f | 1998-09-11 | NULL |
| Whistler | Gwen | bird | NULL | 1997-12-09 | NULL |
| Slim | Benny | snake | m | 1996-04-29 | NULL |
| Puffball | Diane | hamster | f | 1999-03-30 | NULL |
| Jenny | Robert | dog | f | 2004-01-01 | 2014-05-04 |
+----------+--------+---------+------+------------+------------+
SQL:
select pet.name,
( YEAR(date) - YEAR(birth) ) - ( RIGHT(date,5) < RIGHT(birth,5) ) AS age, remark
from pet inner join event
on pet.name = event.name
where event.type = 'litter';
I understand the SQL statement except for this one:
( YEAR(date) - YEAR(birth) ) - ( RIGHT(date,5) < RIGHT(birth,5) )
A step by step explanation would greatly help. I know that the YEAR() function is used to extract the year from a date.
As you've mentioned YEAR() gets the year from the date.
We will use Fluffy as an example with birth = 1993-02-04 and date = 1995-05-15
Step 1:
Subtract the extracted year from both dates ( YEAR(date) - YEAR(birth) )
You now have 1995 - 1993 which is equal to 2
Step 2:
( RIGHT(date,5) < RIGHT(birth,5) ) this will actually read the date and birth string 5 paces from right to left, so if you perform RIGHT(date,5) you will get the value 0, and if you perform RIGHT(birth,5) you will also get a value of 0.
Step 3:
Now we get on to the < operator, this returns a boolean value of 1 or 0 if it satisfies the condition. Since 0 = 0, the statement is false, so it will return to 0.
The whole function actually checks if the day part of your date is less than the day on your birthdate which will determine if you have a sort of remaining days before a whole year. And if you do, it will return 1 which will be subtracted from the current year - year operation you performed earlier.
But in our case, since the < will return 0, we can definitely say that Fluffy's age is 2 - 0, which is 2.
If, however, Fluffy's birthdate is say, 1993-12-04, this will yield a 1 value for the < operation meaning that the year is not yet complete which will bring a result of 2 - 1, which is 1.
Sorry if its a bit messy.
If you were born in 1980 and I know that right now it's 2015, then I I can almost compute your age by computing YEAR(date) - YEAR(birth) = 2015 - 1980 = 35. The sticking point is that your birthday might not have happened yet. So how can I tell if your birthday has happened? Lets say you were born on July 20, 1980. Then in MySQL date format your birthday would look like 2015-06-20. If I look at the last 5 characters of your birthday, the right-most 5 characters, or RIGHT(birth, 5) I'd get 06-20. If the the last 5 characters of today's date, 06-07 is less than the last 5 characters of your birthday then your birthday hasn't happened yet. Here, by "less than" we mean only that it would "sort before" in normal string order. So, if RIGHT(date, 5) < RIGHT(birth, 5) then your birthday hasn't happened yet. And, luckily, MySQL treats a "true" as the number 1 and a "false" as the number 0. So, the above will subtract 1 from our date computation only if your birthday hasn't happened yet.
It's a clever(?) way of checking if the month and day of the date of birth happened before or after the date from the event (litter) in order to properly calculate the number of years between the events (as the year part in itself isn't enough).
In this part:
( YEAR(date) - YEAR(birth) ) - ( RIGHT(date,5) < RIGHT(birth,5) )
the RIGHT(date,5) returns the month and day part like (03-30) and does a boolean less than comparison which returns either 0 or 1 depending on the result. This is then subtracted from the YEAR(date) - YEAR(birth) calculation so that the years between the events get adjusted correctly.

Combining multiple rows to make up a date range

I'm not sure if this is possible with MySQL and its a little complicated, but I'll try and explain as best as possible.
I amending out accommodation availability & price checking system based on an existing database. The visitor will be able to select an arrival and a departure date and I want to be able to show all the rates/offers that are available for those dates. Easy. However, an offer may be set up to be available for only the first part of the given date range and a second rate may be set up for that room type covering the later part of the date range.
e.g. I select September 29th until October 3rd for my stay. A rate has been set up for room 1 to cover September 1st - 30th, and a second rate for October 1st - 31st.
Can I query the table to check if besides rates that span the entire range, there are pairs (or maybe even more) rates for the same room id that together cover the date range, and retrieve the rates & descriptions for those?
The query below checks for basic rates that are valid between the given dates (where roomtype is the id of the room):
SELECT id, caption, price, startdate, enddate, nights, roomtype,
(#arrival := DATE('2011-09-29')) AS arrivaldate,
(#departure := DATE('2011-10-05')) AS departuredate
FROM bnbrates
WHERE
bnb_ref = 16639
AND active = 'TRUE'
AND rooms != 0
AND #arrival < enddate
AND #departure > startdate
AND roomtype != ''
ORDER BY roomtype, enddate, startdate, price;
and this is a sample of the data stored:
id | caption | price | startdate | enddate | nights | roomtype | arrivaldate | departuredate
-------------------------------------------------------------------------------------------------------------------
23553 | Single | 50 | 2011-10-01 | 2011-10-31 | 1 | 1064 | 2011-09-29 | 2011-10-05
23544 | Double | 55 | 2011-09-01 | 2011-09-30 | 1 | 1647 | 2011-09-29 | 2011-10-05
23545 | Double | 80 | 2011-10-01 | 2011-10-31 | 1 | 1647 | 2011-09-29 | 2011-10-05
30312 | Triple | 109 | 2011-09-01 | 2011-09-30 | 1 | 1649 | 2011-09-29 | 2011-10-05
34234 | Executive | 109 | 2011-09-01 | 2011-09-30 | 1 | 1653 | 2011-09-29 | 2011-10-05
23569 | Executive | 99 | 2011-10-01 | 2011-10-31 | 1 | 1653 | 2011-09-29 | 2011-10-05
The desired result would for example combine 23544 with 23545 and 34234 with 23569
Thank you in advance for your help, and apologies if this doesn't make sense...