Change DATE_ADD's unit based on column's value - mysql

I have a table which stores the datetime an operation was last_run, I also have a frequency column, which is the frequency, in months, that the operation should run.
+----+---------------------+-----------+
| id | last_run | frequency |
+----+---------------------+-----------+
| 1 | 2014-05-22 00:00:00 | 12 |
|----|---------------------|-----------|
| 2 | 2015-05-15 00:00:00 | 0.25 |
+----+---------------------+-----------+
I'm using the following query to obtain the date that the operation should next be run:
SELECT DATE_ADD(last_run, INTERVAL frequency MONTH) AS next_run FROM table;
This fails to return the correct next_run date on the second record because of the frequency value.
Is it possible to add a condition so that when frequency = 0.25
frequency becomes 7
MONTH becomes DAY
This is part of an old system which I am currently refactoring. Would it just be better to store the interval type (DAY/MONTH/YEAR), and pass that to the query, or is there a way of adding the above conditions to the query?

I think you have to use a big case statement. Something like this:
select (case when frequency >= 1
then DATE_ADD(last_run, INTERVAL frequency MONTH)
when frequency < 1
then DATE_ADD(last_run, INTERVAL frequency*28 DAY)
ELSE DATE_ADD(last_run, INTERVAL 1 MONTH) --Adding for may be null values
end)

Related

Mysql Subtract 'N' days to date value and compare to current date. N and Date are two column values

My table contains remind_me_befor_days column and expiry_date column.
I have to select records with condition (expiry_date - remind_me_befor_days) = current_date.
How this possible by query. Anyone can help me ?
Thanks in advance
You can do date arithemtics:
select t.*
from mytable t
where t.expiry_date - interval t.remind_me_befor_days day = current_date
Demo on DB Fiddle:
-- sample data
select * from mytable;
id | expiry_date | remind_me_befor_days
-: | :---------- | -------------------:
1 | 2019-12-25 | 10
2 | 2019-12-25 | 5
-- query
select t.*, current_date
from mytable t
where t.expiry_date - interval t.remind_me_befor_days day = current_date
id | expiry_date | remind_me_befor_days | current_date
-: | :---------- | -------------------: | :-----------
1 | 2019-12-25 | 10 | 2019-12-15
To split this into a few subproblems, we have:
Find the current date.
Subtract your remind_me_befor_days column value from the expiry_date column.
Use that as a filter for the records you want from the table.
The first can be done using the MySQL CURDATE() function. Read more here. Combining that with the second and third steps, you get something that looks like
SELECT *
FROM my_table
WHERE expiry_date - remind_me_befor_days = CURDATE()
I would strongly recommend that you write the condition as:
where expiry_date = curdate() + interval remind_me_before_days day
This is index friendly, so it can make use of an index on expiry_date.

Combine 2 count queries into 1

Unable to get total per accountcode, it gives me a sum of all, here is the code
SELECT cdr.accountcode,
(SELECT COUNT(id) FROM cdr WHERE cdr.calldate > now() - INTERVAL 30 MINUTE and cdr.dstchannel LIKE '%neural%') AS callcount ,
(SELECT COUNT(id) FROM cdr WHERE cdr.calldate > now() - INTERVAL 3 MINUTE and cdr.dstchannel LIKE '%neotel%') AS localcallcount
FROM cdr
Table Structure
id | calldate | clid | src | dst | dstchannel | accountcode | note
---+------------+-------+-----+-----------------+------------+-------------+------
12 | 2018-12-05 | "105" | 105 | SIP/neotel/1234 | 1234user | testuser | null
Expected output
accountcode callcount localcallcount
5000user 1 2
5236user 1 3
Use SUM() instead of COUNT(). COUNT() counts every line, no matter what. The boolean expression in the SUM() function returns either 1 or 0, depending on if it's true or false.
SELECT cdr.accountcode,
SUM(cdr.calldate > now() - INTERVAL 30 MINUTE and cdr.dstchannel LIKE '%neural%') AS callcount ,
SUM(cdr.calldate > now() - INTERVAL 3 MINUTE and cdr.dstchannel LIKE '%neotel%') AS localcallcount
FROM cdr

Get the hourly average amount based on rows with minutely timestamp

I have a table structure that looks like this:
+---------+------------+----------+---------+-----------+------------+
| id | account_id | hashrate | workers | sharerate | timestamp |
+---------+------------+----------+---------+-----------+------------+
| 1227368 | 42 | 405211 | 1 | 6183 | 1534264380 |
| 1227367 | 12 | 450077 | 1 | 6868 | 1534264380 |
+---------+------------+----------+---------+-----------+------------+
A row is created every minute for a user with an active worker.
I'm attempting to display a chart that will show the average hashrate and sharerate for each hour over a 24 hour period.
My sql query so far is:
SELECT
timestamp,
SUM(hashrate) as hashes,
SUM(sharerate) AS shares
FROM statistics_users
WHERE FROM_UNIXTIME(timestamp) >= DATE_SUB(NOW(), INTERVAL 1 DAY)
GROUP BY timestamp;
which returns:
+------------+------------+----------+
| timestamp | hashes | shares |
+------------+------------+----------+
| 1534177980 | 2282744244 | 34831913 |
Which is returning 1440 objects (one for each minute of day). I want to group the items by hour and average all sums returned from my query of minutes into an hour. Not sure where to go from here and I'm fairly new to writing sql queries, so any help would be much appreciated.
We can use an expression that returns a representation of hour, basically trimming off minutes and seconds, and then GROUP BY that expression. One possibility it to use the MySQL DATE_FORMAT function.
SELECT DATE_FORMAT(FROM_UNIXTIME(t.timestamp),'%Y-%m-%d %H') AS ts_hr
, AVG(t.hashes)
, AVG(t.shares)
FROM (
-- original query goes here as inline view
SELECT timestamp
, SUM(hashrate) as hashes
, SUM(sharerate) AS shares
FROM statistics_users
WHERE FROM_UNIXTIME(timestamp) >= NOW() + INTERVAL -1 DAY
GROUP BY timestamp
) t
GROUP
BY DATE_FORMAT(FROM_UNIXTIME(t.timestamp),'%Y-%m-%d %H')
-- ^^^^^^^^^^^^ ^^^^^^^^^^^^^^^
Note that the condition in the WHERE clause doesn't respect hour boundaries, so we're going to get a partial result for first hour, and a partial last hour.
The specification isn't clear, as to whether we want the average of the sums for each timestamp, or more simply
SELECT DATE_FORMAT(FROM_UNIXTIME(t.timestamp),'%Y-%m-%d %H') AS ts_hr
, AVG(t.hashrate)
, AVG(t.sharerate)
FROM statistics_users t
WHERE FROM_UNIXTIME(t.timestamp) >= NOW() + INTERVAL -1 DAY
GROUP
BY DATE_FORMAT(FROM_UNIXTIME(t.timestamp),'%Y-%m-%d %H')
-- ^^^^^^^^^^^^ ^^^^^^^^^^^^^^^
https://dev.mysql.com/doc/refman/8.0/en/date-and-time-functions.html#function_date-format

MySQL AND condition parenthesis not working

The following select statement does not return the correct results, despite furiously moving parenthesis around:
SELECT * FROM Table A where (refId != <some refId> and date <= (now() - INTERVAL 10 DAY))
It seems that the AND condition is short circuiting for some reason, meaning that if the refId does in fact equal the refId in question, the date doesn't matter. This statement needs to return rows without the refId in question but only if the date condition matches as well.
For instance, I have many refIds, but I want to filter out one of them, but only when the date is also outside the 10 day window.
Sample Data: Expected Results:
----------- -----------------
|id|refId| date | refId != 5 AND date <= (now() - INTERVAL 10 DAY)
|1 | 1 | 2018-2-10| Only row 1 should be returned since the
|2 | 5 | 2018-2-14| refId !=5 AND the date is <= 10 days ago.
|3 | 2 | 2018-2-20|
|4 | 5 | 2018-2-22|
|5 | 5 | 2018-2-12|
I think you want = and or:
SELECT *
FROM Table A
WHERE (refId = <some refId> OR date <= (now() - INTERVAL 10 DAY))

How can I return new IDs in accordance with a date?

I have a table with the following data (merely an example, actual table has 600,000 rows) (aid = access id [primary key] and id = user id [foreign key]):
aid | id | date
332 | 1 | 2016-12-15
331 | 4 | 2016-12-15
330 | 3 | 2016-12-15
329 | 1 | 2016-12-14
328 | 1 | 2016-12-14
327 | 2 | 2016-12-14
326 | 3 | 2016-12-13
325 | 2 | 2016-12-13
324 | 1 | 2016-12-13
323 | 1 | 2016-12-12
322 | 3 | 2016-12-12
321 | 1 | 2016-12-12
Each id is a users primary key, and every time they access something in my system I log them in this table (with the date in the format as shown, and their id). A user can be logged multiple times a day.
I'm looking to: return the total number of times the thing has been accessed in a day and return the total number of NEW users who have accessed the thing in a day, for the last 8 days (something will always be logged each day, so using "LIMIT 8" is fine for getting only the last 8 days).
My SQL currently looks like:
SELECT COUNT(id), COUNT(distinct id), date
FROM table
GROUP BY date
ORDER BY date DESC
LIMIT 8;
That SQL does the first part correctly, but I can't figure out how to get it to return the number of users who have never accessed the thing until that day.
Desired results would be, the one "newuser" represents the user with id "4" as they have never accessed the thing before:
COUNT(id) | newusers | date
3 | 1 | 2016-12-15
3 | 0 | 2016-12-14
3 | 0 | 2016-12-13
3 | 0 | 2016-12-12
Sorry if I didn't explain this clear enough.
To get new users you want the first day an id appeared:
select id, min(date)
from t
group by id;
The rest is just a join and group by:
select d.date, cnt, count(dd.id) as newusers
from (select date, count(*) as cnt
from t
group by date
) d left join
(select id, min(date) as mindate
from t
group by id
) dd
on d.date = dd.mindate
group by d.date, d.cnt
limit 8;
To get the number of new users you need to compare them to a set of ids over the past 8 days
My MySQL is a bit rusty, so you might have to correct the syntax.
SELECT COUNT(id)
FROM table
WHERE id NOT IN (
SELECT DISTINCT id
FROM table
WHERE date BETWEEN DATE(DATE_SUB(NOW(), INTERVAL 8 DAY)) AND DATE(DATE_SUB(NOW(), INTERVAL 1 DAY))
)
I'll leave it as a task for you to combine it with your other query ;)
Hi if your date column in database is datetime/date or other date representing format you can do something like this:
for getting all users who accessed something in 8 days:
Select id, date from table
where date BETWEEN DATE_ADD(NOW(), INTERVAL -9 DAY) AND NOW()
I think, you can do whatever grouping you want on that.
To get new users, you can either go with self join or with sub select
selfjoin:
select t.id, t.date from table as t
LEFT join table as t2
ON t.id = t2.id
AND t.date BETWEEN DATE_ADD(NOW(), INTERVAL -1 DAY) AND NOW()
AND t2.date NOT BETWEEN DATE_ADD(NOW(), INTERVAL -9 DAY) AND NOW()
WHERE t2.id IS NULL
i used left join to match all access from users and then in where excluded those rows. However self joins are slow, and even slower with LEFT join
subselect:
select id, date from table
where date BETWEEN DATE_ADD(NOW(), INTERVAL -1 DAY) AND NOW()
AND id NOT IN (
SELECT id FROM table
WHERE date BETWEEN DATE_ADD(NOW(), INTERVAL -2 DAY) AND DATE_ADD(NOW(), INTERVAL -1 DAY)
)
I know those betweens with date_adds are not exactly nice looking, but i hope it will help you more than grouping dates
I would suggest using date with time for more information, but its entirely up to meaning of yours data