MySQL get results for different timespans and group - mysql

I have a big table with user "session" data (login/logout time and the use_time). Now I would like to create a top list, with:
total time
time last 24 hours (day)
week time
month time
The table with the data looks like this:
id | user_id | login_time (datetime) | logout_time (datetime) | online_time (int in seconds)
Unfortunately I haven't found a way to get this done with one query.
SELECT sum(s.online_time) as ontime, u.name as name
FROM session_user s
INNER JOIN users u
ON u.id=s.user_id
WHERE login_time > NOW() - INTERVAL 1 DAY
GROUP BY u.id
This query does give me the daily time of each active user, but I also want the other 3 (total time, week time, month time).
Is it possible to get that done with one query and if possible within a reasonable time?

You can use conditional aggregation to get this information:
SELECT u.name, sum(s.online_time) as oneime,
sum(case when login_time > NOW() - INTERVAL 1 DAY then s.online_time end) as ontime_1day,
sum(case when login_time > NOW() - INTERVAL 7 DAY then s.online_time end) as ontime_1week,
sum(case when login_time > NOW() - INTERVAL 1 MONTH then s.online_time end) as ontime_1month
FROM session_user s INNER JOIN
users u
ON u.id=s.user_id
GROUP BY u.id;

Related

mySQLi --> Is it possible to create a query that would calculate the sum between dates

I have a mySQL table that contains at least the following field:
ID | user | date | balance
I would like to know if it is possible to sum the column balance group by user for dates that are between the following cases:
between TODAY and TODAY + 30days
between TODAY+31days and TODAY+60days
above TODAY+61days
Query:
SELECT user, SUM(balance) AS sumBalance
FROM table
WHERE ... CASE << this is where I think I need help...
GROUP BY user
It may not be possible to do all that once is the same query. If not I can execute three separate query and fill an array.
Thanks for your help!
It seems like you want conditional aggregation:
select
user,
sum(case when date between current_date and current_date + interval 30 day then balance end) balance1,
sum(case when date between current_date + interval 31 day and current_date + interval 60 day then balance end) balance2,
sum(case when date >= current_date + interval 61 day then balance end) balance3
from mytable
where date >= current_date
group by user
This gives you one record per user (which, by the way, is a MySQL keyword, hence not a good choice for a column name), with 3 additional columns that contain the sum of balance for the three distinct date ranges.

How to output the results of a SQL query into a report form table? [duplicate]

This question already has answers here:
multiple query same table but in different columns mysql
(4 answers)
Closed 4 years ago.
I have an the following table fields:
Invitations (user_id, type, created_at, completed_at)
I'm currently able to obtain last week's invitation conversation rate by running the following query and manually computing.
SELECT *
FROM invitations
WHERE created_at >= curdate() - INTERVAL DAYOFWEEK(curdate())+6 DAY
AND created_at < curdate() - INTERVAL DAYOFWEEK(curdate())-1 DAY
AND user_id != 1
AND type = 'email'
ORDER BY completed_at, created_at
Is it possible with SQL to output more of a report... Something that returns:
LAST WEEK | Total Invitations | Invitations Completed | % Conversion
| 100 | 50 | 50%
TWO WEEKS | Total Invitations | Invitations Completed | % Conversion
| 100 | 60 | 60%
Is something like this possible with SQL or do I need to create this with application logic?
Maybe you want to aggregate using count() and do a UNION ALL.
SELECT 'LAST WEEK' `Period`,
count(created_at) `Total Invitations`,
count(completed_at) `Invitations completed`,
concat(count(completed_at) / count(created_at) * 100, '%') `% Conversion`
FROM invitations
WHERE created_at >= curdate() - INTERVAL dayofweek(curdate()) + 6 DAY
AND created_at < curdate() - INTERVAL dayofweek(curdate()) - 1 DAY
AND user_id <> 1
AND type = 'email'
UNION ALL
SELECT 'TWO WEEKS' `Period`,
count(created_at) `Total Invitations`,
count(completed_at) `Invitations completed`,
concat(count(completed_at) / count(created_at) * 100, '%') `% Conversion`
FROM invitations
WHERE created_at >= curdate() - INTERVAL dayofweek(curdate()) + 13 DAY
AND created_at < curdate() - INTERVAL dayofweek(curdate()) - 1 DAY
AND user_id <> 1
AND type = 'email';
count(completed_at) does only count the rows, where completed_at isn't null. I assume that completed_at is null if and only if the invitation isn't completed. count(created_at) works analog. But I assume there are no null values in that column (and if they were, those rows won't match the conditions in the WHERE clause, so they aren't even candidates for counting). UNION ALL just unites the two result sets (without eliminating duplicates, which aren't in there anyway, as at least the Period differs).

MySQL Get Orders From Last 12 Weeks Monday to Sunday

I have a table that stores each order made by a user, recording the date it was made , the amount and the user id. I am trying to create a query that returns the weekly transactions from Monday to Sunday for the last 12 weeks for a particular user. I am using the following query:
SELECT COUNT(*) AS Orders,
SUM(amount) AS Total,
DATE_FORMAT(transaction_date,'%m/%Y') AS Week
FROM shop_orders
WHERE user_id = 123
AND transaction_date >= now()-interval 3 month
GROUP BY YEAR(transaction_date), WEEKOFYEAR(transaction_date)
ORDER BY DATE_FORMAT(transaction_date,'%m/%Y') ASC
This produces the following result:
This however does not return the weeks where the user has made 0 orders, does not sum the orders from Monday to Sunday and does not return the weeks ordered from 1 to 12. Is there a way to achieve these things?
One way to accomplish this is with an self outer join (in this case, I use a right outer join, but of course a left outer join would work as well).
To start your weeks on Monday, subtract the result of WEEKDAY from your column transaction_date with DATE_SUB, as proposed in the most upvoted answer here.
SELECT
COALESCE(t1.Orders, 0) AS `Orders`,
COALESCE(t1.Total, 0) AS `Total`,
t2.Week AS `Week`
FROM
(
SELECT
COUNT(*) AS `Orders`,
SUM(amount) AS `Total`,
DATE(DATE_SUB(transaction_date, INTERVAL(WEEKDAY(transaction_date)) DAY)) AS `Week`
FROM
shop_orders
WHERE 1=1
AND user_id = 123
AND transaction_date >= NOW() - INTERVAL 12 WEEK
GROUP BY
3
) t1 RIGHT JOIN (
SELECT
DATE(DATE_SUB(transaction_date, INTERVAL(WEEKDAY(transaction_date)) DAY)) AS `Week`
FROM
shop_orders
WHERE
transaction_date >= NOW() - INTERVAL 12 WEEK
GROUP BY
1
ORDER BY
1
) t2 USING (Week)
To return the weeks with no Orders you have to create a table with all the weeks.
For the order order by the same fields in the group by

mysql query on case when or data between specified date

for each relationship manager display all the customer and their total orders,who ordered more than 5 times in the last week or more than 10 times in the last 14 days,there are two tables
1.orders[date,rel. manager],
2.customer[cid,cname].
I am trying like this, thanks.
select o.RelationshipManager,c.Name,count(*) total_orders
case
when o.OrderedDate >curdate() -interval 7 day then count(*)
else
o.OrderedDate >curdate() -interval 14 day then count(*)
end
from customer c
join orders o on c.customerid=o.customerid;
SELECT o.RelationshipManager, c.Name
, COUNT(*) total_orders
, COUNT(CASE WHEN o.OrderedDate > curdate() - INTERVAL 7 DAY THEN 1 ELSE NULL END) AS oneWeekCount
, COUNT(CASE WHEN o.OrderedDate > curdate() - INTERVAL 14 DAY THEN 1 ELSE NULL END) AS twoWeekCount
FROM customer AS c
JOIN orders AS o ON c.customerid=o.customerid
-- WHERE o.OrderedDate > curdate() - INTERVAL 14 DAY
GROUP BY o.RelationshipManager, c.Name
HAVING oneWeekCount > 5 OR twoWeekCount > 10
;
COUNT only counts non-null values, the WHERE is optional but changes the results (it should reduce the number of records inspected, but makes total_orders the same as twoWeekCount); the HAVING filters the results after the aggregation/counting has been performed.
In my experience, it is very rare for an aggregate function to be appropriate inside a conditional; I'm not even 100% sure there is an appropriate scenario for such use.

Return a zero for a day with no results

I have a query which returns the total of users who registered for each day. Problem is if a day had no one register it doesn't return any value, it just skips it. I would rather it returned zero
this is my query so far
SELECT count(*) total FROM users WHERE created_at < NOW() AND created_at >
DATE_SUB(NOW(), INTERVAL 7 DAY) AND owner_id = ? GROUP BY DAY(created_at)
ORDER BY created_at DESC
Edit
i grouped the data so i would get a count for each day- As for the date range, i wanted the total users registered for the previous seven days
A variation on the theme "build your on 7 day calendar inline":
SELECT D, count(created_at) AS total FROM
(SELECT DATE_SUB(NOW(), INTERVAL D DAY) AS D
FROM
(SELECT 0 as D
UNION SELECT 1
UNION SELECT 2
UNION SELECT 3
UNION SELECT 4
UNION SELECT 5
UNION SELECT 6
) AS D
) AS D
LEFT JOIN users ON date(created_at) = date(D)
WHERE owner_id = ? or owner_id is null
GROUP BY D
ORDER BY D DESC
I don't have your table structure at hand, so that would need adjustment probably. In the same order of idea, you will see I use NOW() as a reference date. But that's easily adjustable. Anyway that's the spirit...
See for a live demo http://sqlfiddle.com/#!2/ab5cf/11
If you had a table that held all of your days you could do a left join from there to your users table.
SELECT SUM(CASE WHEN U.Id IS NOT NULL THEN 1 ELSE 0 END)
FROM DimDate D
LEFT JOIN Users U ON CONVERT(DATE,U.Created_at) = D.DateValue
WHERE YourCriteria
GROUP BY YourGroupBy
The tricky bit is that you group by the date field in your data, which might have 'holes' in it, and thus miss records for that date.
A way to solve it is by filling a table with all dates for the past 10 and next 100 years or so, and to (outer)join that to your data. Then you will have one record for each day (or week or whatever) for sure.
I had to do this only for MS SqlServer, so how to fill a date table (or perhaps you can do it dynamically) is for someone else to answer.
A bit long winded, but I think this will work...
SELECT count(users.created_at) total FROM
(SELECT DATE_SUB(CURDATE(),INTERVAL 6 DAY) as cdate UNION ALL
SELECT DATE_SUB(CURDATE(),INTERVAL 5 DAY) UNION ALL
SELECT DATE_SUB(CURDATE(),INTERVAL 4 DAY) UNION ALL
SELECT DATE_SUB(CURDATE(),INTERVAL 3 DAY) UNION ALL
SELECT DATE_SUB(CURDATE(),INTERVAL 2 DAY) UNION ALL
SELECT DATE_SUB(CURDATE(),INTERVAL 1 DAY) UNION ALL
SELECT CURDATE()) t1 left join users
ON date(created_at)=t1.cdate
WHERE owner_id = ? or owner_id is null
GROUP BY t1.cdate
ORDER BY t1.cdate DESC
It differs from your query slightly in that it works on dates rather than date times which your query is doing. From your description I have assumed you mean to use whole days and therefore have used dates.