SELECT statement subquery - mysql

EDIT: I need the overall total in there subtracting both direct and indirect minutes.
I'm trying to SUM M. Minutes as an alias "dminutes". Then, take the SUM of M.minutes again and subtract M.minutes that have "indirect" column value (and give it "inminutes" alias). However, it's showing me null, so the syntax is wrong. Suggestions?
table = tasks
column = task_type
Example:
M.minutes total = 60 minutes
M. minutes (with "direct" task_type column value) = 50 minutes (AS dminutes)
M. minutes (with "indirect" task_type column value) = 10 minutes (AS inminutes)
SQL statement:
SELECT
U.user_name,
SUM(M.minutes) as dminutes,
ROUND(SUM(M.minutes))-(SELECT (SUM(M.minutes)) from summary s WHERE ta.task_type='indirect') as inminutes
FROM summary S
JOIN users U ON U.user_id = S.user_id
JOIN tasks TA ON TA.task_id = S.task_id
JOIN minutes M ON M.minutes_id = S.minutes_id
WHERE DATE(submit_date) = curdate()
AND TIME(submit_date) BETWEEN '00:00:01' and '23:59:59'
GROUP BY U.user_name
LIMIT 0 , 30

I think something like this should work.
You might have to tweak it a little.
SELECT direct.duser_id, indirect.iminutes, direct.dminutes,
direct.dminutes - indirect.iminutes FROM
(SELECT U.user_id AS iuser_id, SUM(M.minutes) AS iminutes
FROM summary S
JOIN users U
ON U.user_id = S.user_id
JOIN minutes M
ON M.minutes_id = S.minutes_id
JOIN tasks TA
ON TA.task_id = S.task_id
WHERE TA.task_type='indirect'
AND DATE(submit_date) = curdate()
AND TIME(submit_date) BETWEEN '00:00:01' and '23:59:59'
GROUP BY U.user_id) AS indirect
JOIN
(SELECT U.user_id AS duser_id, SUM(M.minutes) AS dminutes
FROM summary S
JOIN users U
ON U.user_id = S.user_id
JOIN minutes M
ON M.minutes_id = S.minutes_id
JOIN tasks TA
ON TA.task_id = S.task_id
WHERE TA.task_type='direct'
AND DATE(submit_date) = curdate()
AND TIME(submit_date) BETWEEN '00:00:01' and '23:59:59'
GROUP BY U.user_id) AS direct
WHERE indirect.iuser_id = direct.duser_id

SUM is a nasty little function:
http://dev.mysql.com/doc/refman/5.0/en/group-by-functions.html#function_sum
Returns the sum of expr. If the return set has no rows, SUM() returns
NULL. The DISTINCT keyword can be used to sum only the distinct values
of expr.
SUM() returns NULL if there were no matching rows.
Try wrapping SUM to a COALESCE or an IFNULL:
... COALESCE( SUM(whatever), 0) ...

Related

Percentage above a number in Mysql

I have a column in my table that shows how many seconds the customer was in line to be served, in seconds.
I need to put together a query that shows how many percent of chats were served within 30 seconds.
I was only able to count the number of calls over the ideal period, but I couldn't get the percentage of chats that were within that ideal period.
SELECT
COUNT(c.id) AS 'Total'
FROM lh_chat as c
LEFT JOIN lh_departament as d ON c.dep_id = d.id
INNER JOIN lh_users u ON c.user_id = u.id
WHERE FROM_UNIXTIME(c.time-10800, '%Y-%m-%d') BETWEEN '2023-01-01' AND '2023-01-31'
AND u.id != 178
AND c.`status` <> 0
AND c.dep_id IN (7,19,23)
AND c.wait_time > 30
I was only able to add the number of chats above the ideal time, but not to make the percentage of chats that are within the expected range
Could you try this:
SELECT SUM(IF(c.wait_time > 30, 0, 1) * 1.0 / COUNT(c.id)
FROM lh_chat as c
LEFT JOIN lh_departament as d ON c.dep_id = d.id
INNER JOIN lh_users u ON c.user_id = u.id
WHERE FROM_UNIXTIME(c.time-10800, '%Y-%m-%d') BETWEEN '2023-01-01' AND '2023-01-31'
AND u.id != 178
AND c.`status` <> 0
The idea is to count only wait_times less then 30 seconds using SUM and then divide to the total count.
AND c.dep_id IN (7,19,23)

SQL query not returning the things I want

I am confused about why my query is not returning the things I want. Can someone please give me a hand on this?
Tables:
Query(CTE):
WITH cancel AS(
SELECT t.Request_at AS day, IFNULL(COUNT(t.Status),0) AS cancelled
FROM Trips t
LEFT JOIN Users u
ON t.Client_Id = u.Users_Id
WHERE (t.Status = "cancelled_by_driver" or t.Status = "cancelled_by_client")
AND t.Request_at BETWEEN "2013-10-01" AND "2013-10-03"
AND u.Banned = "No"
GROUP BY t.Request_at)
So what I want here is to make the cte I have above to return the number of trips that is canceled by the unbanned users or the driver between Oct 1, 2013 and Oct 3, 2013. My query is returning the correct number for the one that got canceled but it is not returning "0" for the date that has no cancellation. I can't figure out why the result is like this as I am using IFNULL and along with left join already.
The where clause evicts dates that have no cancelled drive before you get a chance to include them in the resultset.
If you have all dates available in the table (regardless of the status), you can just move the conditions within the aggregate:
select t.request_at,
sum(t.status in ('cancelled_by_driver', 'cancelled_by_client')) as cnt_cancelled
from trips t
inner join users u on u.user_id = t.client_id
where u.banned = 'No' and t.request_at between '2013-10-01' and '2013-10-03'
group by t.request_at
A more generic approach uses a calendar table to handle the dates, then brings the table with a left join. If you don't have such table, you can generate it on the fly with a recursive query (available in MySQL 8.0):
with recursive cal as (
select '2013-10-01' as dt
union all
select dt + interval 1 day from cal where dt < '2013-10-03'
)
select c.dt, count(t.id) as cnt_cancelled
from cal c
left join trips t on t.request_at = c.dt and t.status in ('cancelled_by_driver', 'cancelled_by_client')
left join users u on u.user_id = t.client_id and u.banned = 'No'
group by c.dt

MySQL: "Subquery returns more than 1 row" when selecting results of multiple subqueries

I'm getting a "Subquery returns more than 1 row" error while running a query that's meant to return results of two subqueries. Why is returning more than one row a problem here, and how can I get around this problem?
Data tables and relevant fields look like this:
Accounts
id
Meetings
account_id
assigned_user_id
start_date
Users
id
last_name
A meeting is assigned to an account and to a user. I'm trying to create a table that will display the quantities of meetings per assigned user per account where the meeting start date is within different date ranges. The date ranges should be arranged in the same row, as a table with these headings:
Account | User's Last Name | Meetings 1-31 days in the future | Meetings 31-60 days in the future
as shown in this image:
.
This is my query:
SELECT
(SELECT
a.name
FROM
accounts AS a
JOIN
meetings AS m ON a.id = m.account_id
AND date_start BETWEEN CURDATE() AND DATE_ADD(CURDATE(),INTERVAL 60 DAY)
JOIN
users AS u ON m.assigned_user_id = u.id
WHERE
m.status = 'Planned'
AND m.deleted = 0
GROUP BY a.id, u.id) AS 'Account',
(SELECT
u.last_name
FROM
accounts AS a
JOIN
meetings AS m ON a.id = m.account_id
AND date_start BETWEEN CURDATE() AND DATE_ADD(CURDATE(),INTERVAL 60 DAY)
JOIN
users AS u ON m.assigned_user_id = u.id
WHERE
m.status = 'Planned'
AND m.deleted = 0
GROUP BY a.id, u.id) AS 'Name',
(SELECT
COUNT(m.id)
FROM
accounts AS a
JOIN
meetings AS m ON a.id = m.account_id
AND date_start BETWEEN CURDATE() AND DATE_ADD(CURDATE(),INTERVAL 30 DAY)
JOIN
users AS u ON m.assigned_user_id = u.id
WHERE
m.status = 'Planned'
AND m.deleted = 0
GROUP BY a.id, u.id) AS 'Meetings 1-30 days',
(SELECT
COUNT(m.id)
FROM
accounts AS a2
JOIN
meetings AS m ON a.id = m.account_id
AND m.date_start BETWEEN DATE_ADD(CURDATE(),INTERVAL 31 DAY) AND DATE_ADD(CURDATE(),INTERVAL 60 DAY)
JOIN
users AS u ON m.assigned_user_id = u.id
WHERE
m.status = 'Planned'
AND m.deleted = 0
GROUP BY a.id, u.id) AS 'Meetings 31-60 days'
Columns containing the names of accounts and names of users had to be added as subqueries in order to avoid "Operand should contain 1 column(s)" errors. Columns corresponding to the counts of meetings had to be subqueries because no single row of the joined table can fit both date ranges at the same time. Each subquery returns the expected results when run individually. But I get "Subquery returns more than 1 row" when the subqueries are put together as shown. I tried assigning different aliases to each subquery, but that did not help.
SQL queries do not return nested result sets; so an expression (such as a subquery) used in a SELECT clause cannot have multiple values, as that would "nest" it's values. You more likely just need to use conditional aggregation, like so:
SELECT a.id, u.id, a.name, u.last_name
, COUNT(CASE WHEN m.date_start BETWEEN CURDATE() AND DATE_ADD(CURDATE(),INTERVAL 30 DAY) THEN 1 ELSE NULL END) AS `Meetings 1-30 days`
, COUNT(CASE WHEN m.date_start BETWEEN DATE_ADD(CURDATE(),INTERVAL 31 DAY) AND DATE_ADD(CURDATE(),INTERVAL 60 DAY) THEN 1 ELSE NULL END) AS `Meetings 31-60 days`
, COUNT(CASE WHEN THEN 1 ELSE NULL END) AS
FROM accounts AS a
JOIN meetings AS m ON a.id = m.account_id
JOIN users AS u ON m.assigned_user_id = u.id
WHERE m.status = 'Planned' AND m.deleted = 0
AND m.date_start BETWEEN CURDATE() AND DATE_ADD(CURDATE(),INTERVAL 60 DAY)
GROUP BY a.id, u.id, a.name, u.last_name
;
Notes: ELSE NULL is technically automatic, and can be omitted; it is just there for clarity. Aggregate functions, such as COUNT, ignore NULL values; the only time null values affect such functions is when they encounter only null values (in which case their results are null).
Sidenote: You could have continued with your query in a form similar to what you originally had; if you included the grouping fields in the subqueries' results, the subqueries could have been joined together (but that would have been a lot of redundant joining of accounts, meetings, and users).

finding closest date from multiple tables mysql

I have many tables that log the users action on some forum, each log event has it's date.
I need a query that gives me all the users that wasn't active in during the last year.
I have the following query (working query):
SELECT *
FROM (questions AS q
INNER JOIN Answers AS a
INNER JOIN bestAnswerByPoll AS p
INNER JOIN answerThumbRank AS t
INNER JOIN notes AS n
INNER JOIN interestingQuestion AS i ON q.user_id = a.user_id
AND a.user_id = p.user_id
AND p.user_id = t.user_id
AND t.user_id = n.user_id
AND n.user_id = i.user_id)
WHERE DATEDIFF(CURDATE(),q.date)>365
AND DATEDIFF(CURDATE(),a.date)>365
AND DATEDIFF(CURDATE(),p.date)>365
AND DATEDIFF(CURDATE(),t.date)>365
AND DATEDIFF(CURDATE(),n.date)>365
AND DATEDIFF(CURDATE(),i.date)>365
what i'm doing in that query - joining all the tables according to the userId, and then checking each
date column individually to see if it's been more then a year
I was wondering if there is a way to make it simpler, something like finding the max between all dates (the latest date) and compering just this one to the current date
If you want to get best performance, you cannot use greatest(). Instead do something like this:
SELECT *
FROM questions q
JOIN Answers a ON q.user_id = a.user_id
JOIN bestAnswerByPoll p ON a.user_id = p.user_id
JOIN answerThumbRank t ON p.user_id = t.user_id
JOIN notes n ON t.user_id = n.user_id
JOIN interestingQuestion i ON n.user_id = i.user_id
WHERE q.date > curdate() - interval 1 year
AND a.date > curdate() - interval 1 year
AND p.date > curdate() - interval 1 year
AND t.date > curdate() - interval 1 year
AND n.date > curdate() - interval 1 year
AND i.date > curdate() - interval 1 year
You want to avoid datediff() such that MySQL can do index lookup on date column comparisons. Now, to make sure that index lookup works, you should create compound (multi-column) index on (user_id, date) for each one of your tables.
In this compound index, first part (user_id) will be user for faster joins, and second part (date) will be used for faster date comparisons. If you replace * in your SELECT * with only columns mentioned above (like user_id only), you might be able to get index-only scans, which will be super-fast.
UPDATE Unfortunately, MySQL does not support WITH clause for common table expressions like PostgreSQL and some other databases. But, you can still factor out common expression as follows:
SELECT *
FROM questions q
JOIN Answers a ON q.user_id = a.user_id
JOIN bestAnswerByPoll p ON a.user_id = p.user_id
JOIN answerThumbRank t ON p.user_id = t.user_id
JOIN notes n ON t.user_id = n.user_id
JOIN interestingQuestion i ON n.user_id = i.user_id,
(SELECT curdate() - interval 1 year AS year_ago) x
WHERE q.date > x.year_ago
AND a.date > x.year_ago
AND p.date > x.year_ago
AND t.date > x.year_ago
AND n.date > x.year_ago
AND i.date > x.year_ago
In MySQL, you can use the greatest() function:
WHERE DATEDIFF(CURDATE(), greatest(q.date, a.date, p.date, t.date, n.date, i.date)) > 365
This will help with readability. It would not affect performance.

SQL SELECT statement filtering with 3 subquery joins

I realize this might not be an efficient select, but it works for now...mostly. The problem I'm having is that if values aren't submitted for both "iminutes" and "dminutes"... I get no data. I think if they both returned a value of "0" it would resolve this problem, but not sure how to get them to do that. Suggestions?
SELECT user,total.tuser_id, total.tminutes,
total.tminutes-indirect.iminutes AS itminutes, total.tminutes-direct.dminutes AS dtminutes
FROM
(SELECT U.user_name AS user,U.user_id AS tuser_id, SUM(M.minutes) AS tminutes
From summary S
JOIN users U ON U.user_id = S.user_id
JOIN tasks TA ON TA.task_id = S.task_id
JOIN tcompleted TC ON TC.tcompleted_id = S.tcompleted_id
JOIN minutes M ON M.minutes_id = S.minutes_id
JOIN hour_interval H ON H.hourinterval_id = S.hourinterval_id
WHERE DATE(submit_date) = curdate()
AND TIME(submit_date) BETWEEN '00:00:01' and '23:59:59'
GROUP BY U.user_id) total
JOIN
(SELECT U.user_id AS iuser_id, SUM(M.minutes) AS iminutes
FROM summary S
JOIN users U
ON U.user_id = S.user_id
JOIN minutes M
ON M.minutes_id = S.minutes_id
JOIN tasks TA
ON TA.task_id = S.task_id
WHERE TA.task_type='indirect'
AND DATE(submit_date) = curdate()
AND TIME(submit_date) BETWEEN '00:00:01' and '23:59:59'
GROUP BY U.user_id) AS indirect
ON total.tuser_id = indirect.iuser_id
JOIN
(SELECT U.user_id AS duser_id, SUM(M.minutes) AS dminutes
FROM summary S
JOIN users U
ON U.user_id = S.user_id
JOIN minutes M
ON M.minutes_id = S.minutes_id
JOIN tasks TA
ON TA.task_id = S.task_id
WHERE TA.task_type='direct'
AND DATE(submit_date) = curdate()
AND TIME(submit_date) BETWEEN '00:00:01' and '23:59:59'
GROUP BY U.user_id) AS direct
ON total.tuser_id = direct.duser_id
ORDER BY total.tuser_id
COALESCE will return the first non-null value provided.
total.tminutes-COALESCE(indirect.iminutes, 0)
COALESCE here will return 0 when iminutes is NULL. You can do the same for dminutes.
You may check the MySQL Reference Manual http://dev.mysql.com/doc/refman/5.0/en/comparison-operators.html#function_coalesce