Mysql COUNT sub query - mysql

I have a fairly complex query for myself across a few tables that I have nearly finished. The final aspect involves just aggregating how many bids an item has received, I am doing this for all items in a particular section though. The query works fine except it only returns 1 row when I try and add in aggregation (/* */) to the query. Reading around it would seem i need a sub query, except I'm not entirely sure how to go about this. This is the query to date:
SELECT s.id as section_id, s.name as section, i.id as item_id,
i.title as item_title, item.location_id as item_location_id,
i.slug as item_slug, i.description as item_description,
i.price as item_price, UNIX_TIMESTAMP(i.date_added) as item_date_added,
c.id, c.city, c.particular_id, p.id, p.particular /*,COUNT(o.i_id) as interest*/
FROM section as s
INNER JOIN item as i
ON s.id = i.section_id
INNER JOIN city as c
ON i.location_id = c.id
INNER JOIN particular as p
ON c.particular_id = p.id
/*LEFT JOIN offer as o
ON o.job_id = i.id*/
WHERE s.id = 2
The code works in that it returns all the rows I expected until the introduction of the /* */ code, where it now only returns 1 row.
any help you could give me would be appreciated
Thanks
Jonny

You have to have group bys if you are doing aggregates, e.g.:
SELECT s.name, COUNT(i.id)
FROM section as s
INNER JOIN item as i
GROUP BY s.name
would return the section name and the number of items found in it.

It should solve your problem:
SELECT s.id as section_id, s.name as section, i.id as item_id,
i.title as item_title, item.location_id as item_location_id,
i.slug as item_slug, i.description as item_description,
i.price as item_price, UNIX_TIMESTAMP(i.date_added) as item_date_added,
c.id, c.city, c.particular_id, p.id, p.particular,
(SELECT COUNT(o.i_id) FROM offer AS o ON o.job_id = i.id) AS interest
FROM section as s
INNER JOIN item as i
ON s.id = i.section_id
INNER JOIN city as c
ON i.location_id = c.id
INNER JOIN particular as p
ON c.particular_id = p.id
WHERE s.id = 2

Related

Multiple aggregate functions in SQL query

For this example I got 3 simple tables (Page, Subs and Followers):
For each page I need to know how many subs and followers it has.
My result is supposed to look like this:
I tried using the COUNT function in combination with a GROUP BY like this:
SELECT p.ID, COUNT(s.UID) AS SubCount, COUNT(f.UID) AS FollowCount
FROM page p, subs s, followers f
WHERE p.ID = s.ID AND p.ID = f.ID AND s.ID = f.ID
GROUP BY p.ID
Obviously this statement returns a wrong result.
My other attempt was using two different SELECT statements and then combining the two subresults into one table.
SELECT p.ID, COUNT(s.UID) AS SubCount FROM page p, subs s WHERE p.ID = s.ID GROUP BY p.ID
and
SELECT p.ID, COUNT(f.UID) AS FollowCount FROM page p, follow f WHERE p.ID = f.ID GROUP BY p.ID
I feel like there has to be a simpler / shorter way of doing it but I'm too unexperienced to find it.
Never use commas in the FROM clause. Always use proper, explicit, standard JOIN syntax.
Next, learn what COUNT() does. It counts the number of non-NULL values. So, your expressions are going to return the same value -- because f.UID and s.UID are never NULL (due to the JOIN conditions).
The issue is that the different dimensions are multiplying the amounts. A simple fix is to use COUNT(DISTINCT):
SELECT p.ID, COUNT(DISTINCT s.UID) AS SubCount, COUNT(DISTINCT f.UID) AS FollowCount
FROM page p JOIN
subs s
ON p.ID = s.ID JOIN
followers f
ON s.ID = f.ID
GROUP BY p.ID;
The inner joins are equivalent to the original query. You probably want left joins so you can get counts of zero:
SELECT p.ID, COUNT(DISTINCT s.UID) AS SubCount, COUNT(DISTINCT f.UID) AS FollowCount
FROM page p LEFT JOIN
subs s
ON p.ID = s.ID LEFT JOIN
followers f
ON p.ID = f.ID
GROUP BY p.ID;
Scalar subquery should work in this case.
SELECT p.id,
(SELECT Count(s_uid)
FROM subs s1
WHERE s1.s_id = p.id) AS cnt_subs,
(SELECT Count(f_uid)
FROM followers f1
WHERE f1.f_id = p.id) AS cnt_fol
FROM page p
GROUP BY p.id;

MySQL: Ranking from multiple tables, sub queries?

This is a MySQL question. I have three tables with the following columns:
transactions (table): transact_id, customer_id, transact_amt, product_id,
products (table): product_id, product_cost, product_name, product_category
customers (table): customer_id, joined_at, last_login_at, state, name, email
I'd like a query that finds out the most popular item in every state and the state. One of the tricky parts is that some product_name have multiple product_id. Therefore I though joining the three tables that generate an output with two columns: state and product_name. Until here that worked fine doing this:
SELECT p.product_name, c.state
FROM products p
INNER JOIN transactions t
ON p.product_id = t.product_id
INNER JOIN customers c
ON c.customer_id = t.customer_id
This selects all the products, and the states from where the customer is. The problem is that I can't find the way to rank the mos popular product per state. I tried different group by, order by and using subqueries without success. I suspect I need to do subqueries, but I can't find the way to resolve it. The expected outcome should look like this:
most_popular_product | state
Bamboo | WA
Walnut | MO
Any help will be greatly appreciated.
Thank you!
You need a subquery that gets the count of transactions for each product in each state.
SELECT p.product_name, c.state, COUNT(*) AS count
FROM products p
INNER JOIN transactions t
ON p.product_id = t.product_id
INNER JOIN customers c
ON c.customer_id = t.customer_id
GROUP BY p.product_name, c.state
Then write another query that has this as a subquery, and gets the highest count for each state.
SELECT state, MAX(count) AS maxcount
FROM (
SELECT p.product_name, c.state, COUNT(*) AS count
FROM products p
INNER JOIN transactions t
ON p.product_id = t.product_id
INNER JOIN customers c
ON c.customer_id = t.customer_id
GROUP BY p.product_name, c.state
) AS t
GROUP BY state
Finally, join them together:
SELECT t1.product_name AS most_popular_product, t1.state
FROM (
SELECT p.product_name, c.state, COUNT(*) AS count
FROM products p
INNER JOIN transactions t
ON p.product_id = t.product_id
INNER JOIN customers c
ON c.customer_id = t.customer_id
GROUP BY p.product_name, c.state
) AS t1
JOIN (
SELECT state, MAX(count) AS maxcount
FROM (
SELECT p.product_name, c.state, COUNT(*) AS count
FROM products p
INNER JOIN transactions t
ON p.product_id = t.product_id
INNER JOIN customers c
ON c.customer_id = t.customer_id
GROUP BY p.product_name, c.state
) AS t
GROUP BY state
) AS t2 ON t1.state = t2.state AND t1.count = t2.maxcount
This is basically the same pattern as SQL select only rows with max value on a column, just using the first grouped query as the table you're trying to group.

Returning all results of an outer query and getting a count of attached items

So I'm struggling to write a query that returns me all categories regardless of what filter I have applied but the count changes based on how many returned recipes there will be in this filter.
This query works nice if I don't apply any filters to it. The count's seem right, but as soon as I add something like this: where c.parent_id is not null and r.time_cook_minutes > 60 I am filtering out most of the categories instead of just getting a count of zero.
here's an example query that I came up with that does not work the way I want it to:
select t.id, t.name, t.parent_id, a.cntr from categories as t,
(select c.id, count(*) as cntr from categories as c
inner join recipe_categories as rc on rc.category_id = c.id
inner join recipes as r on r.id = rc.recipe_id
where c.parent_id is not null and r.time_cook_minutes > 60
group by c.id) as a
where a.id = t.id
group by t.id
so this currently, as you might imagine, returns only the counts of recipes that exist in this filter subset... what I'd like is to get all of them regardless of the filter with a count of 0 if they don't have any recipes under that filter.
any help with this would be greatly appreciated. If this question is not super clear let me know, and I can elaborate.
No need for nested join if you move the condition into a regular outer join:
select t.id, t.name, t.parent_id, count(r.id)
from categories as t
left join recipe_categories as rc on rc.category_id = c.id
left join recipes as r on r.id = rc.recipe_id
and r.time_cook_minutes > 60
where c.parent_id is not null
group by 1, 2, 3
Notes:
Use left joins so you always get every category
Put r.time_cook_minutes > 60 on the left join condition. Leaving it on the where clause cancels the effect of left
Simply use conditional aggregation, moving the WHERE clause into a CASE (or IF() for MySQL) statement wrapped in a SUM() of 1's and 0's (i.e., counts). Also, be sure to consistently use the explicit join, the current industry practice in SQL. While your derived table uses this form of join, the outer query uses implicit join matching IDs in WHERE clause.
select t.id, t.name, t.parent_id, a.cntr
from categories as t
inner join
(select c.id, sum(case when c.parent_id is not null and r.time_cook_minutes > 60
then 1
else 0
end) as cntr
from categories as c
inner join recipe_categories as rc on rc.category_id = c.id
inner join recipes as r on r.id = rc.recipe_id
group by c.id) as a
on a.id = t.id
group by t.id
I believe you want:
select c.id, c.name, c.parent_id, count(r.id)
from categories c left join
recipe_categories rc
on rc.category_id = c.id left join
recipes r
on r.id = rc.recipe_id and r.time_cook_minutes > 60
where c.parent_id is not null and
group by c.id, c.name, c.parent_id;
Notes:
This uses left joins for all the joins.
It aggregates by all the non-aggregated columns.
It counts matching recipes rather than all rows.
The condition on recipes is moved to the on clause from the where clause.

MySQL One-To-Many SUM of COUNTs

After 2 days of searching and trying similar questions, it's got to the point where I need to ask the question!
I have the following database structure (simplified)..
mt_product | mt_sku | mt_price
========== | ====== | ========
id | brand_id | mpn | id | product_id | retailer_id | sku | id | sku_id | price | date
For instance...
* A can of Coca-Cola is ONE product.
* It can be sold in many different retailers, who will all have a SKU for it.
* This SKU will have a price, which can change day-by-day.
I want to list the total number of prices for the product.
To list this I currently have the following query which nearly works...
SELECT
p.id AS pid, p.title AS p_title, p.cat, p.mpn,
b.id AS bid, b.name AS brand,
(SELECT COUNT(s.id) FROM mt_sku AS s WHERE s.pid = p.id) AS num_sku,
(SELECT COUNT(gbp.id) FROM mt_price AS gbp INNER JOIN mt_sku ON mt_sku.id = gbp.sid ) AS num_price
FROM mt_product AS p
INNER JOIN mt_brand b ON p.bid = b.id
INNER JOIN mt_sku s ON p.id = s.pid
num_sku returns as expected, however when I introduce the second sub query for num_price (and I have revised this many times) I either get...
* no duplications of the pid but the num_price is the total number of prices to SKUs, not the amount of prices for this product_id (as query above) eg1_img
* the correct number of num_price, but instead of totalling up the total num_price, the pid is duplicated in the table (as query below) - therefore as the pid is duplicated, this does not give me the result I want. I added DISTINCT as it helped an earlier version of the query, it now makes no difference. eg2_img
SELECT
DISTINCT(p.id) AS pid, p.title AS p_title, p.cat, p.mpn,
b.id AS bid, b.name AS brand,
(SELECT COUNT(s.id) FROM mt_sku AS s WHERE s.pid = p.id) AS num_sku,
(SELECT COUNT(gbp.id) FROM mt_price AS gbp WHERE s.id = gbp.sid) AS num_price
FROM mt_product AS p
INNER JOIN mt_brand b ON p.bid = b.id
INNER JOIN mt_sku s ON p.id = s.pid
I'm pretty sure the key to this is that
product can have multiple SKUs, of which a SKU has multiple price history.
Any help or ideas of the schema would be superb.
Try this:
SELECT
p.id AS pid, p.title AS p_title, p.cat, p.mpn,
b.id AS bid, b.name AS brand,
COUNT(DISTINCT s.id) AS num_sku,
COUNT(gbp.id) AS num_price
FROM mt_product AS p
INNER JOIN mt_brand b ON p.brand_id = b.id
INNER JOIN mt_sku s ON p.id = s.product_id
INNER JOIN mt_price gbp ON s.id = gbp.sku_id
GROUP BY b.id, p.id
The products that don't have SKUs defined will not appear in the result set. Use LEFT JOIN mt_sku to make them appear in the result set (having 0 for num_sku and num_price):
LEFT JOIN mt_sku s ON p.id = s.product_id
In both variants of the query, the products that do not have prices defined will not appear in the result set. Use LEFT JOIN mt_price to include them into the result set (having 0 for num_price):
LEFT JOIN mt_price gbp ON s.id = gbp.sku_id
Take a look at the MySQL documentation for JOINs, GROUP BY and GROUP BY aggregate functions.
If you want to list the total prices then you need correlations.
Your first count is fine, because it is correlated to the outer query. The second has no correlation, so that seems strange. The following fixes the num_price subquery:
SELECT p.id AS pid, p.title AS p_title, p.cat, p.mpn,
b.id AS bid, b.name AS brand,
(SELECT COUNT(s2.id) FROM mt_sku s2 WHERE s2.pid = p.id) AS num_sku,
(SELECT COUNT(gbp.id) FROM mt_price gbp WHERE s.id = gbp.sid ) AS num_price
FROM mt_product p INNER JOIN
mt_brand b
ON p.bid = b.id INNER JOIN
mt_sku s
ON p.id = s.pid;
I'm also not sure why you have all the joins in the outer query. I assume that a given product is going to have multiple rows, and you want the multiple rows to have the same num_sku and num_price values.

GROUP BY and ORDER BY issues

I have the following query:
SELECT DISTINCT (
s.styleTitle
), COUNT(p.id) AS `PictureCount`
FROM `style` s
LEFT JOIN `instagram_picture_style` ps ON s.id = ps.style_id
LEFT JOIN `instagram_shop_picture` p ON ps.picture_id = p.id
LEFT JOIN `instagram_picture_category` c ON c.picture_id = p.id
LEFT JOIN `instagram_second_level_category` sl ON c.second_level_category_id = sl.id
WHERE sl.id =25
GROUP BY p.id
ORDER BY PictureCount
however this query gives me:
I basically wanted the list to be ordered by the style that has the most pictures in it. What did I do wrong? Why is it giving me 1 on all of the styles, I am pretty sure it has more pictures for that style
ORDER BY doesn't have underscores. But equally important, you are using DISTINCT in a way where you seem to think that it is a function. It is not. It is a modifies on the SELECT and it applies to all columns.
You should group by the same column you have in the distinct. Something like this:
SELECT s.styleTitle, COUNT(p.id) AS `PictureCount`
FROM `style` s
LEFT JOIN `instagram_picture_style` ps ON s.id = ps.style_id
LEFT JOIN `instagram_shop_picture` p ON ps.picture_id = p.id
LEFT JOIN `instagram_picture_category` c ON c.picture_id = p.id
LEFT JOIN `instagram_second_level_category` sl ON c.second_level_category_id = sl.id
WHERE sl.id = 25
GROUP BY s.styleTitle
ORDER BY PictureCount DESC;
In fact, you almost never need distinct with group by. If you are using, you need to think why it would be necessary.