MySQL COUNT rows in a subquery - mysql

I've got this MySQL statement that returns a list of all of our dealers, along with total amount of stores they have. I'm trying to make it so that I can add additional columns that are returned depending on the stores joineddate column.
This is the basic statement that returns the list of dealers (accountgroup) and the total / COUNT of stores that are assigned to accountgroup.
SELECT accountgroup, COUNT(storename) AS TotalAmountOfStores
FROM accounts
WHERE accountgroup <>''
AND othercode ='Site'
GROUP BY accountgroup
I thought using the below statement in a SELECT subquery would let me have these additional columns that I want. This statement works on it's own, but I haven't been able to encorporate it into the above statement as a subquery successfully.
SELECT COUNT( storename ) AS '2months'
FROM `accounts`
WHERE joineddate > date_sub(now(), interval 2 month)
AND accountgroup <> ''
AND othercode = 'Site'
GROUP BY accountgroup
This is the statement that I thought would work, but I get the error "#1242 - Subquery returns more than 1 row, which confuses me as it should be returning more than one row":
SELECT accountgroup, COUNT(storename) AS TotalAmountOfStores,
(SELECT COUNT(storename)
FROM `accounts`
WHERE joineddate > date_sub(now(), interval 2 month) AND accountgroup <>'' AND othercode='Site'
GROUP BY accountgroup ) AS '2months'
FROM accounts
WHERE accountgroup <>''
AND othercode ='Site'
GROUP BY accountgroup
I'm starting to feel that a subquery isn't the right approach, and am reading up on JOIN' s. Can anybody confirm this, and possibly point me in the right direction?
Any help is greatly appreciated. Thanks in advance.

You can SUM the times this condition is true using an if. Example:
SELECT
accountgroup,
COUNT(storename) AS TotalAmountOfStores,
SUM(
IF(joineddate > date_sub(now(), interval 2 month), 1, 0)
) AS '2months'
FROM accounts
WHERE accountgroup <>''
AND othercode ='Site'
GROUP BY accountgroup

Related

condition in SELECT in mysql

I have a query that looks like this
SELECT customer, totalvolume
FROM orders
WHERE deliverydate BETWEEN '2020-01-01' AND CURDATE()
Is there any way to select totalvolume for specific date range and make it a separate column?
So for example, I already have totalvolume. I'd like to also add totalvolume for the previous month as a separate column (totalvolume where deliverydate BETWEEN '2020-08-01' AND '2020-08-31'). Is there a function for that?
Simply use 2 table copies:
SELECT t1.customer, t1.totalvolume, t2.totalvolume previousvolume
FROM orders t1
LEFT JOIN orders t2 ON t1.customer = t2.customer
AND t1.deliverydate = t2.deliverydate + INTERVAL 1 MONTH
WHERE t1.deliverydate BETWEEN '2020-08-01' AND '2020-08-31';
You can do it with case/when construct in your columns and just expand your WHERE clause. Sometimes I would do it by having a secondary #variables to simplify my clauses. Something like
SELECT
o.customer,
sum( case when o.deliveryDate < #beginOfMonth
then o.TotalVolume else 0 end ) PriorMonthVolume,
sum( case when o.deliveryDate >= #beginOfMonth
then o.TotalVolume else 0 end ) ThisMonthVolume,
sum( o.totalvolume ) TwoMonthsVolume
FROM
( select #myToday := date(curdate()),
#beginOfMonth := date_sub( #myToday, interval dayOfMonth( #myToday ) -1 day ),
#beginLastMonth := date_sub( #beginOfMonth, interval 1 month ) ) SqlVars,
orders o
WHERE
o.deliverydate >= #beginLastMonth
group by
o.customer
To start, the "from" clause of the query alias "SqlVars" will dynamically create 3 variables and return a single row for that set. With no JOIN condition, is always a 1:1 ratio for everything in the orders table. Nice thing, you don't have to pre-declare variables and the #variables are available for the query.
By querying for all records on or after the beginning of the LAST month, you get all records for both months in question. The sum( case/when ) can now use those variables as the demarcation point for the respective volume totals.
I know you mentioned this was a simplified query, but masking that might not be a perfect answer to what you need, but may help you look at it from a different querying perspective.

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

Get percentage of total when using GROUP BY in SQL query

I have a SQL query that I'm using to return the number of training sessions recorded by a client on each day of the week (during the last year).
SELECT COUNT(*) total_sessions
, DAYNAME(log_date) day_name
FROM programmes_results
WHERE log_date >= DATE_SUB(CURDATE(), INTERVAL 1 YEAR)
AND log_date <= CURDATE()
AND client_id = 7171
GROUP
BY day_name
ORDER
BY FIELD(day_name, 'MONDAY', 'TUESDAY', 'WEDNESDAY', 'THURSDAY', 'FRIDAY', 'SATURDAY', 'SUNDAY')
I would like to then plot a table showing these values as a percentage of the total, as opposed to as a 'count' for each day. However I'm at a bit of a loss as to how to do that without another query (which I'd like to avoid).
Any thoughts?
Use a derived table
select day_name, total_sessions, total_sessions / sum(total_sessions) * 100 percentage
from (
query from your question goes here
) temp
group by day_name, total_sessions
You can add the number of trainings per day in your client application to get the total count. This way you definitely avoid having a 2nd query to get the total.
Use the with rollup modifier in the query to get the total returned in the last row:
...GROUP BY day_name WITH ROLLUP ORDER BY ...
Use a subquery to return the overall count within each row
SELECT ..., t.total_count
...FROM programmes_results INNER JOIN (SELECT COUNT(*) as total_count FROM programmes_results WHERE <same where criteria>) as t --NO join condition
...
This will have the largest performance impact on the database, however, it enables you to have the total number in each row.

MYSQL return 0 on hourly count

I am at a loss of how to accomplish this but have seen online ISNULL() and COALESCE() used to return a zero if the query is null. I am unsure though how to use it properly though I am intuitively thinking i need to have in a subquery then have ISNULL or COALESCE around that subquery?
The query goes:
SELECT HOUR( dateAdded ) AS HOUR , COUNT( DISTINCT remoteAddr, xForwardedFor) AS cnt
FROM Track
WHERE accessMask = '1iczo'
AND destination = 'lp_include.php'
AND dateAdded
BETWEEN '2014-05-01'
AND '2014-05-02'
GROUP BY HOUR
ORDER BY HOUR
Some help in the right direction would be greatly appreciated!
UPDATE
I used what #Barmar had suggested but it wasn't returning accurate results. I used what he provided and also another topic with a similar situation, Group by should return 0 when grouping by hours. How to do this? . I actually didn't find this topic till after posting this one, :( unfortunately. Here is the final code that appears to return accurate results, distinct across two columns with empty data being returned as 0.
SELECT a.hour, COALESCE(cnt, 0) AS cnt
FROM (SELECT 0 AS hour
UNION ALL
SELECT 1
UNION ALL
SELECT 2 .....
UNION ALL
SELECT 23) a
LEFT JOIN
(SELECT COUNT(DISTINCT remoteAddr, xForwardedFor) AS cnt, HOUR(dateAdded) AS hour
FROM Track
WHERE accessMask = '1iczo'
AND destination = 'lp_include.php'
AND dateAdded
BETWEEN '2014-05-01 00:00:00' AND '2014-05-01 23:59:59') AS totals
ON a.hour = totals.hour
Fiddle for better reference: http://sqlfiddle.com/#!2/9ab660/7
Thanks again to #Barmar, he really put me in the right direction to get to the solution!
You have to join with a table that contains all the hours. This must be a LEFT JOIN so that the results will include hours that have no matches in Track table.
SELECT allHours.hour, IFNULL(cnt, 0) AS cnt
FROM (SELECT 0 AS hour
UNION
SELECT 1
UNION
SELECT 2
UNION
SELECT 3
...
UNION
SELECT 23) AS allHours
LEFT JOIN
(SELECT HOUR(dateAdded) AS hour, COUNT(DISTINCT remoteAddr, xForwardedFor) AS cnt
FROM Track
WHERE accessMask = '1iczo'
AND destination = 'lp_include.php'
AND dateAdded
BETWEEN '2014-05-01' AND '2014-05-02') AS totals
ON allHours.hour = totals.hour
If you assume that you have some data for every hour, you can move the conditional part into the select:
SELECT HOUR(dateAdded) AS HOUR ,
COUNT(DISTINCT CASE WHEN accessMask = '1iczo' AND destination = 'lp_include.php'
THEN CONCAT(remoteAddr, ',', xForwardedFor)
END) AS cnt
FROM Track
WHERE dateAdded BETWEEN '2014-05-01' AND '2014-05-02'
GROUP BY HOUR
ORDER BY HOUR;

1242 - Subquery returns more than 1 row in subquery

I'm trying to get the total of pending amount of each staff. The below query works fine:
SELECT SUM(amount)
FROM pending
WHERE MONTH < DATE_SUB(curdate() , INTERVAL 1 MONTH)
GROUP BY class
but when I try to add it as a subquery it gives me the error below:
1242 - Subquery returns more than 1 row
SELECT
(period_diff(date_format(now(), '%Y%m'),
date_format(MONTH, '%Y%m'))) AS months,
pending.amount,
pending.admission_numb,
pending.month,
staff.name,
staff.class, (
SELECT SUM(amount)
FROM pending
WHERE MONTH < DATE_SUB(curdate(), INTERVAL 1 MONTH)
GROUP BY class
)
FROM
pending JOIN staff
ON pending.admission_numb = staff.admission
GROUP BY admission
ORDER BY CAST( staff.class AS UNSIGNED ) , staff.class
any help will be appreciated..
Since your subquery returns more than one row (i expect that it will return a row for each class), you need do join your subquery in the from clause:
SELECT
(period_diff(date_format(now(), '%Y%m'), date_format(MONTH, '%Y%m'))) AS months,
pending.amount,
pending.admission_numb,
pending.month,
staff.name,
staff.class,
sums.tot
FROM
pending JOIN staff ON pending.admission_numb = staff.admission
JOIN (
SELECT class, SUM(amount) as tot
FROM pending
WHERE MONTH < DATE_SUB(curdate(), INTERVAL 1 MONTH)
GROUP BY class
) sums on staff.class = sums.class
GROUP BY admission
ORDER BY CAST( staff.class AS UNSIGNED ) , staff.class
It's pretty simple, really. Subqueries can only return one row. That's what the error message tells you. The query you gave most likely returns more than one row (one for each class, in fact). Since you provide no output, I can only guess that this is true.
To fix it, you need to change that query to return one row. Perhaps you get rid of the GROUP BY, or perhaps you pick the largest (or smallest) sum and return that. It's up to you based on your business requirements.