mysql string to date conversion - mysql

Need help to suggest sql query where I have a varchar column called Timing like below
+---------------------+
| Timing | MO |
+---------------------+
|7/11/2016 20:45 |ABC |
|7/12/2016 20:45 |ABC |
|2016-07-11 00:00|ABC |
|2016-07-12 00:00|ABC |
+---------------------+
I need to extract date from the Timing column but tricky part is the date is not always in same format. I need to output timing column as below
+-------------+
| Timing | MO|
+-------------+
|7/11/2016|ABC|
|7/12/2016|ABC|
|7/11/2016|ABC|
|7/12/2016|ABC|
+-------------+

It's simple enough using substring and replace functions to cast varchar as data and recast them to you required output.
for example
DROP TABLE T;
CREATE TABLE T (TIMING VARCHAR(20),MO VARCHAR(2));
INSERT INTO T VALUES
('7/11/2016 20:45' ,'AB'),
('7/12/2016 20:45' ,'AB'),
('2016-07-11 00:00','AB'),
('2016-07-12 00:00','AB'),
('27/06/2016 20:45' ,'AB'),
('29/05/2016 20:45' ,'AB')
*/
SELECT *,
CASE
WHEN INSTR(TIMING,'/') = 0 THEN
CASE WHEN INSTR(TIMING,'-') = 5 THEN CAST(TIMING AS DATE)
end
WHEN INSTR(TIMING,'/') = 3 THEN
CAST(
CONCAT(
SUBSTRING(CONCAT('0',TIMING),8,4)
,'-'
,SUBSTRING(CONCAT('0',TIMING),5,2)
,'-'
,SUBSTRING(CONCAT('0',TIMING),1,2)
)
AS DATE
)
WHEN INSTR(TIMING,'/') = 2 THEN
CAST(
CONCAT(
SUBSTRING(CONCAT('0',TIMING),7,4)
,'-'
,SUBSTRING(CONCAT('0',TIMING),4,2)
,'-'
,SUBSTRING(CONCAT('0',TIMING),1,2)
)
AS DATE
)
END
FROM T
results in
-----------------------------------------------------------------------------+
| 7/11/2016 20:45 | AB | 2016-11-07 |
| 7/12/2016 20:45 | AB | 2016-12-07 |
| 2016-07-11 00:00 | AB | 2016-07-11 |
| 2016-07-12 00:00 | AB | 2016-07-12 |
| 27/06/2016 20:45 | AB | 2016-06-02 |
| 29/05/2016 20:45 | AB | 2016-05-02 |
+------------------+------+-----------------------------------------------------
You do have to code for every known variant - in this case I have identified that 7/11/2016 needs to left padded with zero and slashes replaced with dashs before date conversion. Hopefully you have a limited number of date variations in your source to code for and you will end up with a large query - but that's life. God luck,

Related

MySQL Generate random timestamps from range, order it by date asc, then use it to update missing values from another table

I have a table "ips", where I store my download logs. Accidentally, I forgot to add timestamp for it (yea, stupid mistake)... Now, I have fixed it, but there are already 65.5k entries without timestamp.. Is there a way, how to add random timestamp from date range to fill NULL timestamps?
I was able to generate timestamps list using this queries:
SET #MIN = '2020-04-05 18:30:00';
SET #MAX = NOW();
SELECT TIMESTAMPADD(SECOND, FLOOR(RAND() * TIMESTAMPDIFF(SECOND, #MIN, #MAX)), #MIN) as dldate FROM ips WHERE name="filename1" ORDER BY dldate ASC;
It generated the exact count of entries I need for specific filename, but I have absolutely no idea, how to use this list to update already existing entries in my "ips" table and KEEP IT ORDERED by "dldate"...
When I was testing it, I was close, when I used this query (I was afraid to use UPDATE to not mess my data up, so I used just SELECT):
SELECT ips.id, ips.name, t1.dldate FROM (SELECT id, name FROM ips WHERE name="filename1") ips INNER JOIN (SELECT ips.id as id, TIMESTAMPADD(SECOND, FLOOR(RAND() * TIMESTAMPDIFF(SECOND, #MIN, #MAX)), #MIN) as dldate FROM ips WHERE name="filename1" ORDER BY dldate ASC) t1 ON (ips.id=t1.id) ORDER BY ips.id ASC;
That worked, but timestaps are purely random (obviously :D), and I need them to "respect" id from "ips" table (starting with lower timestamp for lowest id, and then continuously higher timestamps for higher ids).
I'm getting this:
+------+-----------+---------------------+
| id | name | dldate |
+------+-----------+---------------------+
| 15 | filename1 | 2020-12-18 21:35:03 |
| 1118 | filename1 | 2020-12-18 13:34:47 |
| 1141 | filename1 | 2020-08-07 12:49:46 |
| 1142 | filename1 | 2020-11-29 00:43:31 |
| 1143 | filename1 | 2020-05-13 03:00:16 |
| 1286 | filename1 | 2020-12-14 09:58:50 |
| 1393 | filename1 | 2021-04-14 06:45:23 |
| 1394 | filename1 | 2021-03-03 17:42:25 |
| 1395 | filename1 | 2020-09-03 05:56:56 |
| .... |
|62801 | filename1 | 2021-01-05 21:21:29 |
+------+-----------+---------------------+
And I would like to get this:
+------+-----------+---------------------+
| id | name | dldate |
+------+-----------+---------------------+
| 15 | filename1 | 2020-04-05 21:35:03 |
| 1118 | filename1 | 2020-04-18 13:34:47 |
| 1141 | filename1 | 2020-05-07 12:49:46 |
| 1142 | filename1 | 2020-06-29 00:43:31 |
| 1143 | filename1 | 2020-08-13 03:00:16 |
| 1286 | filename1 | 2020-10-14 09:58:50 |
| 1393 | filename1 | 2020-12-14 06:45:23 |
| 1394 | filename1 | 2021-01-03 17:42:25 |
| 1395 | filename1 | 2021-03-03 05:56:56 |
| .... |
|62801 | filename1 | 2021-04-29 14:21:29 |
+------+-----------+---------------------+
Is there any way, how to achieve this output and how to use it with UPDATE statement instead of SELECT with INNER JOIN?
Thank you for help!
How about just starting with a date and adding a time unit?
update ips
set timestamp = '2000-01-01' + interval id second
where timestamp is null;
I'm not sure if second is the right unit. Or if '2000-01-01' is a good base time. But this gives you an approach for doing what you want.
You can, of course, test this using a select first.
If you do want randomness, you can do something like this:
select ips.*,
'2021-04-01' - interval running second
from (select ips.*,
sum(rnd) over (order by id desc) as running
from (select ips.*,
rand() * 1000 as rnd
from ips
where timestamp is null
) ips
) ips;
This calculates a random number of seconds. Then it does a revenue cumulative sum . . . and subtracts those seconds from a base date.

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.... :) )

is it possible to use case statement + round() in database

i have table called 'test' and want to calculate based on different codes, most of them should be saved in 5 decimals except certain code, like containing jpy in 3 decimals, and xua in 2 decimals
create table test(
id int, ymd date,
code varchar(10),
price int
)
insert into test(id, ymd, code, price) values
(1, '2019-01-01', 'auus', 75125),
(2, '2019-01-02', 'nzus', 68541),
(3, '2019-01-03', 'xuaus', 131485),
(4, '2019-01-04', 'aujp', 77852),
(5, '2019-01-05', 'usjp', 110852),
(6, '2019-01-06', 'xuaus', 131091)
So my execute code is:
select id, ymd, code, price,
case
when code like '%xua%' then round(price/100,2)
when code like '%jp%' then round(price/1000,3)
else round(price/100000,5)
end as t
from test
ideal result:
id ymd code price t
1 2019-01-01 auus 75125 0.75125
2 2019-01-02 nzus 68541 0.68541
3 2019-01-03 xuaus 131485 1314.85
4 2019-01-04 aujp 77852 77.852
5 2019-01-05 usjp 110852 110.852
6 2019-01-06 xuaus 131091 1310.91
intersting, above sql works well with Mysql, but i am using mariadb, and just can't get results as same as mysql, spent 2 days to fix problem, but still don't know, please help
mysql> select id, ymd, code, price,
-> case
-> when code like '%xua%' then round(price/100,2)
-> when code like '%jp%' then round(price/1000,3)
-> else round(price/100000,5)
-> end as t
-> from test ;
+------+------------+-------+--------+------------+
| id | ymd | code | price | t |
+------+------------+-------+--------+------------+
| 1 | 2019-01-01 | auus | 75125 | 0.75125 |
| 2 | 2019-01-02 | nzus | 68541 | 0.68541 |
| 3 | 2019-01-03 | xuaus | 131485 | 1314.85000 |
| 4 | 2019-01-04 | aujp | 77852 | 77.85200 |
| 5 | 2019-01-05 | usjp | 110852 | 110.85200 |
| 6 | 2019-01-06 | xuaus | 131091 | 1310.91000 |
+------+------------+-------+--------+------------+
6 rows in set (0.04 sec)
Using FORMAT instead of ROUND:
mysql> select id, ymd, code, price,
case when code like '%xua%' then format(price/100,2)
when code like '%jp%' then format(price/1000,3)
else format(price/100000,5) end as t from test;
+------+------------+-------+--------+----------+
| id | ymd | code | price | t |
+------+------------+-------+--------+----------+
| 1 | 2019-01-01 | auus | 75125 | 0.75125 |
| 2 | 2019-01-02 | nzus | 68541 | 0.68541 |
| 3 | 2019-01-03 | xuaus | 131485 | 1,314.85 |
| 4 | 2019-01-04 | aujp | 77852 | 77.852 |
| 5 | 2019-01-05 | usjp | 110852 | 110.852 |
| 6 | 2019-01-06 | xuaus | 131091 | 1,310.91 |
+------+------------+-------+--------+----------+
6 rows in set (0.00 sec)
mysql> select ##version;
+----------------------------------------+
| ##version |
+----------------------------------------+
| 10.3.11-MariaDB-1:10.3.11+maria~bionic |
+----------------------------------------+
1 row in set (0.00 sec)
Note that it includes a "thousands-separator" when appropriate. See the 3rd argument to FORMAT() or the Locale setting to change that.
I suspect it is the display process that is formatting the output differently. By switching from ROUND to FORMAT, I managed to get MariaDB's output to be nearly the same as MySQL's. The remaining difference is the added commas ("thousands separators"), which may show as '.' for some Locales.
In contrast, for MySQL 5.6.22:
+------+------------+-------+--------+---------+
| id | ymd | code | price | t |
+------+------------+-------+--------+---------+
| 1 | 2019-01-01 | auus | 75125 | 0.75125 |
| 2 | 2019-01-02 | nzus | 68541 | 0.68541 |
| 3 | 2019-01-03 | xuaus | 131485 | 1314.85 |
| 4 | 2019-01-04 | aujp | 77852 | 77.852 |
| 5 | 2019-01-05 | usjp | 110852 | 110.852 |
| 6 | 2019-01-06 | xuaus | 131091 | 1310.91 |
+------+------------+-------+--------+---------+
The numeric values are the same, but the display is different. The difference seems to come from the commandline tool mysql, not from ROUND, itself. Note that t is right-justified, implying that the values are seen as numeric.
If this offends someone enough, file a bug report with MariaDB.
77.75200000000001 -- This is representative of some intermediate computation using DOUBLE instead of all DECIMAL. MySQL (and MariaDB) do a reasonably good job of second-guessing where the number are headed. And usually they get away with whatever is done.
In DOUBLE, 77.75200000000001 is not exactly equal to the DECIMAL 77.752 because one is binary, one is decimal. For this reason, I often recommend not using FLOAT or DOUBLE for "money".
Assuming your real goal is to represent a monetary value as 77.7520000000000000000000000..., that is exactly '77.752', and, assuming you need at most 5 decimal places for the various values, I recommend you do this:
t DECIMAL(m, 5)
where m is a suitably large number for any values you may eventually have. For the numbers given, (9,5) will suffice, but I suspect you should do more like DECIMAL(14,5) to allow for a billion dollars/euros/yen/etc.
What I don't know is where in the processing DOUBLE crept in.
Latest 'advice'
Use DECIMAL(14,5) for all monetary values in your system, not INT.
14,5 lets you get up to a billion 'dollars'; change that as needed for your expected max value.
Ignore my comments about FORMAT(); it seems to be too confusing.
Get rid of the CASE clause, at least for that particular usage.
Most arithmetic among DECIMAL values will be exact, and not encounter 77.75200000000001. If it crops up again, start a new Question and include all the steps, datatypes, etc, involved in the computation.
The above notes refer to storing and computing. For displaying, please specify the requirements:
Plan A: 5 decimal places is OK.
Plan B: need to round to 3 or 2 decimals for some values.
Plan C: You have application code, not in SQL, that can deal with the issue.
Plan D:...

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: Proper date range between start and end date as two fields

I have the following table called seasons
+----+--------+------------+------------+
| id | cost | start | end |
+----+--------+------------+------------+
| 33 | 255 | 2014-01-05 | 2014-04-16 |
| 17 | 357 | 2014-04-17 | 2014-04-19 |
| 65 | 191.25 | 2014-04-20 | 2014-07-10 |
| 49 | 255 | 2014-07-11 | 2014-08-23 |
| 81 | 191.25 | 2014-08-24 | 2014-12-18 |
+----+--------+------------+------------+
I am trying to get a date range between start and end using the following query.
SELECT
*
FROM
seasons
WHERE
(start BETWEEN '2014-01-05' AND '2014-01-05' OR end BETWEEN '2014-01-05' AND '2014-01-05');
I was able to get a result set if the start date started exactly on the value on the field.
+----+------+------------+------------+
| id | cost | start | end |
+----+------+------------+------------+
| 33 | 255 | 2014-01-05 | 2014-04-16 |
+----+------+------------+------------+
Now the problem is when I advance the date to
2014-01-06
SELECT
*
FROM
seasons
WHERE
(start BETWEEN '2014-01-06' AND '2014-01-06' OR end BETWEEN '2014-01-06' AND '2014-01-06');
Empty set (0.00 sec)
There are NO RESULT. How can I get the date range in between to different fields on SQL?
Any help is appreciated.
You have your logic backward. If you want seasons where a given date is in that season then your where clause should look like:
WHERE '2014-01-06' BETWEEN start AND end;
Since you are not really looking for a range and only needing a specific date, you could also just use (SQL Fiddle):
SELECT *
FROM seasons
WHERE start = '2014-01-05' OR end = '2014-01-05'
When querying if a certain date is in a given range, the WHERE clause typically is:
WHERE 'specified_date' BETWEEN 'start_date' AND 'end_date'
Otherwise it is an illogical date span to MySQL and will return an empty result set.
The BETWEEN Comparison Operator is equivalent to:
min <= expr AND expr <= max
Therefore, it's meant to be used like this:
expr BETWEEN min AND max
Here is documentation from MySQL on the BETWEEN Comparison Operator.