Very complex structuration of a MySQL query - mysql

please take a look of this query:
SELECT DATE(datetime), COUNT(1) as numVisits
FROM ".table_stats."
WHERE type='profile_visit'
AND user_url = '".$_GET['ref']."'
AND id_user='".$_SESSION['user_code']."'
GROUP BY DATE(DATE_SUB(datetime, INTERVAL 1 DAY))
This query counts the number of times that type is equal to 'profile_visit' by each date, as a result it gives me two rows (DATE(datetime), numVisits). This is a screen capture of the table table_stats:
Table_Stats
Ok, until now you can understand that every time a user comes to the site a new element is inserted on the table with type=profile_visit and the datetime field with the date and time of the visit, thats why i use a GROUP BY DATE(datetime) to count the total number of visits by day.
Here comes the complex part, when the type field is equal to 'click' and the origin is 'imp' that means that a user hits a particular button on the page, i will like to know how many times that button was clicked (no matter the ip) by day, just like i did with the profile visits.
I can make two querys, one to know the total visits (like the one before) and another similar just by grouping by datetime when type is 'click' and origin is 'imp'.
The problem is that i will like to make this just in one call in order to count the total visits by date in the row NumVisits like i did before and a new row call NumClick with the total of clicks made. This is why i dont want more calculations on my php server, if its possible will be great to make all the calculation on the sql server.
So finally, if you call this query to the table:
SELECT DATE( DATETIME ) , COUNT( 1 ) AS numVisits
FROM stats_ram
WHERE TYPE = 'profile_visit'
AND user_url = 'xxx'
AND id_user = '88e91'
GROUP BY DATE( DATE_SUB( DATETIME, INTERVAL 1
DAY ) )
LIMIT 0 , 30
You will get:
DATE(datetime) numVisits
2011-11-16 7
How can i add another row with the total type=click AND origin=imp made by DATE(datetime)???
Thanks for any help!!!

SELECT
DATE(DATETIME),
SUM(CASE WHEN type = 'profile_visit' THEN 1 ELSE 0 END) AS numVisits,
SUM(CASE WHEN type = 'click' AND origin = 'imp' THEN 1 ELSE 0 END) numClicks
FROM stats_ram
WHERE user_url = 'xxx'
AND id_user = '88e91'
GROUP BY DATE(DATE_SUB(DATETIME, INTERVAL 1 DAY))
LIMIT 0, 30

Related

In mysql how to get users that have no event X in past 14 days and neither in the future

Consider I have the following table and current date is 2022-09-01:
Requirement: I want to get all users that have no event_name like cbt care in the past 14 days and onwards into the future.
I have this query:
SELECT * FROM test_table
WHERE event_name LIKE "%cbt care%"
AND start_date <= DATE_SUB(NOW(), INTERVAL 14 DAY)
;
Which returns:
The issue is that user_id = x does have a cbt care event in 2022-09-10 which is 9 days ahead of current date (2022-09-01).
How to return only users satisfy requirement posted above?
SELECT user_id,
COUNT(CASE WHEN event_name LIKE '%cbt care%' AND start_date
> CURDATE() - INTERVAL 14 day THEN 1 END) AS count_recent
FROM test_table
GROUP BY user_id
HAVING count_recent = 0;
https://www.db-fiddle.com/f/64j7L1VZsVdLYqmcQ2NrvV/0
The CASE expression returns 1 for each row with the conditions you described (a specific event name and a start date after the date 14 days ago, which includes all of the future dates too). For rows that don't match that condition, the CASE returns NULL. There's an implicit ELSE NULL in any CASE expression.
COUNT(<expr>), like many set functions, ignores NULLs. It will only count the occurrences of non-NULL values. So if the count returns 0, then the CASE returned only NULLs, which means there are no recent or future 'cbt care' events for that user.
select id
,user_id
,event_name
,start_date
from (
select *
,count(case when abs(datediff(curdate(),start_date)) <= 14 and event_name like "%cbt care%" then 1 end) over (partition by user_id) as cw
from t
) t
where cw = 0
id
user_id
event_name
start_date
0
a
cbt care
2022-06-01 20:00:00
Fiddle

Calculate time difference based on status change and day change

I am trying to get the online time of riders based on below data but I am unable to cater to the need of changing days. The status = 1 means rider is online and 0 means offline. Sometimes, the riders forget to mark their offline status i.e. 0 on their shift end but do it the next morning when they have to start a new shift.
I am using the below query to get the time differences but this query also calculates the time difference between the changing days. I want it to calculate such that if the last status of the day of any rider is not 0, it should automatically take the last login time as logout time.
SELECT
fleet_id,
login_time,
logout_time,
timediff(logout_time, login_time) AS logged_in_time,
(unix_timestamp(logout_time) - unix_timestamp(login_time))/60 AS minutes_logged_in_time
FROM
(SELECT
fleet_id,
creation_datetime AS login_time,
coalesce(
(SELECT creation_datetime
FROM tb_fleet_duty_logs t_out
WHERE t_out.fleet_id = t_in.fleet_id
AND t_out.creation_datetime >= t_in.creation_datetime
AND t_out.status = 0
ORDER BY creation_datetime
LIMIT 1
),
creation_datetime
) AS logout_time
FROM
tb_fleet_duty_logs t_in
WHERE
status = 1
) AS q1
ORDER BY
fleet_id, login_time
I'll assume that you have some analytic functions available as well as CTEs. I have the impression that status = 0 is a login. If not then reverse the test in the case expression.
with data as (
select
fleet_id,
cast(creation_datetime as date) as dt,
count(case when status = 0 then 1 end) over (
partition by fleet_id, cast(creation_datetime as date)
order by creation_datetime asc) as login_count
from tb_fleet_duty_logs
)
select fleet_id,
min(creation_datetime) as login_time,
max(creation_time) as logout_time,
/* ... other calculations ... */
from data
group by fleet_id, dt, login_count
order by fleet_id, login_time;
The trick is to count off the number of logins per day per rider using the order of the timestamps. After that you only need to use simple grouping to collapse the pairs of rows (or single rows) into the login/logout times.

MySQL combine 2 different counts in one query

I have a table, that pretty much looks like this:
users (id INT, masterId INT, date DATETIME)
Every user has exactly one master. But masters can have n users.
Now I want to find out how many users each master has. I'm doing that this way:
SELECT `masterId`, COUNT(`id`) AS `total` FROM `users` GROUP BY `masterId` ORDER BY `total` DESC
But now I also want to know how many new users a master has since the last 14 days. I could do it with this query:
SELECT `masterId`, COUNT(`id`) AS `last14days` FROM `users` WHERE `date` > DATE_SUB(NOW(), INTERVAL 14 DAY) GROUP BY `masterId` ORDER BY `total` DESC
Now the question: Could I somehow get this information with one query, instead of using 2 queries?
You can use conditional aggregation to do this by only counting rows for with the condition is true. In standard SQL this would be done using a case expression inside the aggregate function:
SELECT
masterId,
COUNT(id) AS total,
SUM(CASE WHEN date > DATE_SUB(NOW(), INTERVAL 14 DAY) THEN 1 ELSE 0 END) AS last14days
FROM users
GROUP BY masterId
ORDER BY total DESC
Sample SQL Fiddle

Calculate average from data only stored in MySQL on change

We have a database where we only store data values and timestamp (as a row in MySQL) when the data value changes. There are therefore no fixed interval between the timestamps in the database. The table looks something like this:
MySQLTimestamp Data
2014-10-01 18:01 1
2014-10-03 16:13 2
2014-10-07 15:45 1
2014-10-09 10:08 3
THE PROBLEM: We want to calculate the average over time assuming that the data value continues to be i.e. 2 until the value changes on the next row in the database.
A simple AVG won't do the trick because it will only calculate the average between the number of rows. This would not take in to count that a value can continue to be the same value for a long period of time before the next row states a change in data value.
Would really appreciate your help!
Self join and calculate the duration of date or time as weight to Data.
select
sum(data*duration_of_date)/sum(duration_of_date) as avg_over_date,
sum(data*duration_of_hour)/sum(duration_of_hour) as avg_over_hour,
sum(data*duration_of_sec)/sum(duration_of_sec) as avg_over_sec
from (
select
t1.MySQLTimestamp,
t1.data,
min(case when t1.MySQLTimestamp<t2.MySQLTimestamp
then t2.MySQLTimestamp else null end) as next_tm,
datediff(
min(case when t1.MySQLTimestamp<t2.MySQLTimestamp
then t2.MySQLTimestamp else null end) ,
t1.MySQLTimestamp) as duration_of_date,
TIME_TO_SEC(timediff(
min(case when t1.MySQLTimestamp<t2.MySQLTimestamp
then t2.MySQLTimestamp else null end) ,
t1.MySQLTimestamp))/60/60 as duration_of_hour,
TIME_TO_SEC(timediff(
min(case when t1.MySQLTimestamp<t2.MySQLTimestamp
then t2.MySQLTimestamp else null end) ,
t1.MySQLTimestamp)) as duration_of_sec
from
your_table t1
cross join
your_table t2
group by
t1.MySQLTimestamp,
t1.data
) as t
Use datediff to calculate days interval as weight. If you want hours or minutes as interval, you could use timediff and transfer the result to hours, minutes or seconds.
Here is the sql fiddle demo and the results:
AVG_OVER_DATE AVG_OVER_HOUR AVG_OVER_SEC
1.5 1.51887 1.5189
Another version by left join:
select
sum(data*duration_of_date)/sum(duration_of_date) as avg_over_date,
sum(data*duration_of_hour)/sum(duration_of_hour) as avg_over_hour,
sum(data*duration_of_sec)/sum(duration_of_sec) as avg_over_sec
from (
select
t1.MySQLTimestamp,
t1.data,
min(t2.MySQLTimestamp) as next_tm,
datediff(min(t2.MySQLTimestamp), t1.MySQLTimestamp) as duration_of_date,
TIME_TO_SEC(timediff(min(t2.MySQLTimestamp), t1.MySQLTimestamp))/60/60 as duration_of_hour,
TIME_TO_SEC(timediff(min(t2.MySQLTimestamp), t1.MySQLTimestamp)) as duration_of_sec
from
your_table t1
left join
your_table t2
on
t1.MySQLTimestamp<t2.MySQLTimestamp
group by
t1.MySQLTimestamp,
t1.data
) as t

MySql -- Determine periods of missing data with query

I have a database that's set up like this:
(Schema Name)
Historical
-CID int UQ AI NN
-ID Int PK
-Location Varchar(255)
-Status Varchar(255)
-Time datetime
So an entry might look like this
433275 | 97 | MyLocation | OK | 2013-08-20 13:05:54
My question is, if I'm expecting 5 minute interval data from each of my sites, how can I determine how long a site has been down?
Example, if MyLocation didn't send in the 5 minute interval data from 13:05:54 until 14:05:54 it would've missed 60 minutes worth of intervals, how could I find this downtime and report on it easily?
Thanks,
*Disclaimer: I'm assuming that your time column determines the order of the entries in your table and that you can't easily (and without heavy performance loss) self-join the table on auto_increment column since it can contain gaps.*
Either you create a table containing simply datetime values and do a
FROM datetime_table d
LEFT JOIN your_table y ON DATE_FORMAT(d.datetimevalue, '%Y-%m-%d %H:%i:00') = DATE_FORMAT(y.`time`, '%Y-%m-%d %H:%i:00')
WHERE y.some_column IS NULL
(date_format() function is used here to get rid of the seconds part in the datetime values).
Or you use user defined variables.
SELECT * FROM (
SELECT
y.*,
TIMESTAMPDIFF(MINUTE, #prevDT, `Time`) AS timedifference
#prevDT := `Time`
FROM your_table y ,
(SELECT #prevDT:=(SELECT MIN(`Time`) FROM your_table)) vars
ORDER BY `Time`
) sq
WHERE timedifference > 5
EDIT: I thought you wanted to scan the whole table (or parts of it) for rows where the timedifference to the previous row is greater than 5 minutes. To check for a specific ID (and still having same assumptions as in the disclaimer) you'd have to do a different approach:
SELECT
TIMESTAMPDIFF(MINUTE, (SELECT `Time` FROM your_table sy WHERE sy.ID < y.ID ORDER BY ID DESC LIMIT 1), `Time`) AS timedifference
FROM your_table y
WHERE ID = whatever
EDIT 2:
When you say "if the ID is currently down" is there already an entry in your table or not? If not, you can simply check this via
SELECT TIMESTAMPDIFF(MINUTE, NOW(), (SELECT MAX(`Time`) FROM your_table WHERE ID = whatever));
So I assume you are going to have some sort of cron job running to check this table. If that is the case you can simply check for the highest time value for each id/location and compare it against current time to flag any id's that have a most recent time that is older than the specified threshold. You can do that like this:
SELECT id, location, MAX(time) as most_recent_time
FROM Historical
GROUP BY id
HAVING most_recent_time < DATE_SUB(NOW(), INTERVAL 5 minutes)
Something like this:
SELECT h1.ID, h1.location, h1.time, min(h2.time)
FROM Historical h1 LEFT JOIN Historical h2
ON (h1.ID = h2.ID AND h2.CID > h1.CID)
WHERE now() > h1.time + INTERVAL 301 SECOND
GROUP BY h1.ID, h1.location, h1.time
HAVING min(h2.time) IS NULL
OR min(h2.time) > h1.time + INTERVAL 301 SECOND