Count left join group by excludes zero in result data - mysql

According to this How to include "zero" / "0" results in COUNT aggregate? I need to left join the tbl_postad table.
Following is my query
This queries shows count of ads in each category.
SELECT c.*, count(a.post_id) as item_count
FROM tbl_category c
LEFT JOIN tbl_postad a on a.post_maincategory = c.cat_id
WHERE c.cat_parent=0 AND c.cat_promote = 0
AND c.selActive = 'Y' AND a.post_delete_status!='Y' AND a.post_cat_status!='N'
AND a.post_status='Y' AND a.block=0 AND a.post_expiredate >= '".date("Y-m-d H:i:s")."'
GROUP BY c.cat_name
ORDER BY c.cat_sort asc
If remove query related to post it shows all the categories:
SELECT c.*, count(a.post_id) as item_count
FROM tbl_category c
LEFT JOIN tbl_postad a on a.post_maincategory=c.cat_id
WHERE c.cat_parent=0 AND c.cat_promote = 0 AND c.selActive = 'Y'
GROUP BY c.cat_name
ORDER BY c.cat_sort asc
But if I add query related to posts then it excludes all the categories with post count of zero.

In a left join, all the conditions on the second table should be in the on clause. Otherwise the non-matches turn the left join into an inner join.
So, try this:
select c.*, count(a.post_id) as item_count
from tbl_category c left join
tbl_postad a
on a.post_maincategory = c.cat_id and
a.post_delete_status <> 'Y' and
a.post_status = 'Y' and
a.post_cat_status <> 'N' and
a.block = 0 and
a.post_expiredate >= '".date("Y-m-d H:i:s")."'
where c.cat_parent = 0 and
c.cat_promote = 0 and
c.selActive = 'Y' ;
Note: You should learn how to use parameters to pass in values such as the ate, rather than munging the query string.

Related

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 Sub Select needed

I have three tables, libraryitems, copies and loans.
A libraryitem hasMany copies, and a copy hasMany loans.
I'm trying to get the latest loan entry for a copy only; The query below returns all loans for a given copy.
SELECT
libraryitems.title,
copies.id,
copies.qruuid,
loans.id AS loanid,
loans.status,
loans.byname,
loans.byemail,
loans.createdAt
FROM copies
INNER JOIN libraryitems ON copies.libraryitemid = libraryitems.id AND libraryitems.deletedAt IS NULL
LEFT OUTER JOIN loans ON copies.id = loans.copyid
WHERE copies.libraryitemid = 1
ORDER BY copies.id ASC, loans.createdAt DESC
I know there needs to be a sub select of some description in here, but struggling to get the correct syntax. How do I only return the latest, i.e MAX(loans.createdAt) row for each distinct copy? Just using group by copies.id returns the earliest, rather than latest entry.
Image example below:
in the subquery , getting maximum created time for a loan i.e. latest entry and joining back with loans to get other details.
SELECT
T.title,
T.id,
T.qruuid,
loans.id AS loanid,
loans.status,
loans.byname,
loans.byemail,
loans.createdAt
FROM
(
SELECT C.id, C.qruuid, L.title, MAX(LN.createdAt) as maxCreatedTime
FROM Copies C
INNER JOIN libraryitems L ON C.libraryitemid = L.id
AND L.deletedAt IS NULL
LEFT OUTER JOIN loans LN ON C.id = LN.copyid
GROUP BY C.id, C.qruuid, L.title) T
JOIN loans ON T.id = loans.copyid
AND T.maxCreatedTime = loans.createdAt
A self left join on loans table will give you latest loan of a copy, you may join the query to the other tables to fetch the desired output.
select * from loans A
left outer join loans B
on A.copyid = B.copyid and A.createdAt < B.createdAt
where B.createdAt is null;
This is your query with one simple modification -- table aliases to make it clearer.
SELECT li.title, c.id, c.qruuid,
l.id AS loanid, l.status, l.byname, l.byemail, l.createdAt
FROM copies c INNER JOIN
libraryitems li
ON c.libraryitemid = li.id AND
li.deletedAt IS NULL LEFT JOIN
loans l
ON c.id = l.copyid
WHERE c.libraryitemid = 1
ORDER BY c.id ASC, l.createdAt DESC ;
With this as a beginning let's think about what you need. You want the load with the latest createdAt date for each c.id. You can get this information with a subquery:
select l.copyid, max(createdAt)
from loans
group by l.copyId
Now, you just need to join this information back in:
SELECT li.title, c.id, c.qruuid,
l.id AS loanid, l.status, l.byname, l.byemail, l.createdAt
FROM copies c INNER JOIN
libraryitems li
ON c.libraryitemid = li.id AND
li.deletedAt IS NULL LEFT JOIN
loans l
ON c.id = l.copyid LEFT JOIN
(SELECT l.copyid, max(l.createdAt) as maxca
FROM loans
GROUP BY l.copyid
) lmax
ON l.copyId = lmax.copyId and l.createdAt = lmax.maxca
WHERE c.libraryitemid = 1
ORDER BY c.id ASC, l.createdAt DESC ;
This should give you the most recent record. And, the use of left join should keep all copies, even those that have never been leant.

MySQL using COUNT, LEFT JOIN and GROUP by returning undesired results

I need some help to get the desired results, which would in this case, be 7 (the number of rows in the products table that would match).
What I am instead getting is 7 rows with a count based on the the number of rows returned in the LEFT JOIN.
SELECT count(p.id) as theCount
FROM products p
left join tablea a on p.id = a.productId
left join tableb b on p.id = b.productId
WHERE (a.col = 'val' or b.col = 'val')
group by p.id
If I do not group by p.id, I get back 28 rows, which is all of the rows from the LEFT JOIN.
I know it's something simple, but I can't figure it out.
Thanks.
select count(distinct p.id), perhaps? Since you're pulling from two different tables, you're going to get a mismash of (p.id, a.col, b.col) being (xxx, null, yyy) and (xxx, yyy, null)
You shouldn't join the one-to-many relationships if all you want is the count of products.
Put your filter condition in the WHERE clause.
SELECT count(*) as theCount
FROM products p
WHERE p.id IN (
SELECT a.productId
FROM tablea a
WHERE a.productId = p.id AND a.col = 'val'
UNION
SELECT b.productId
FROM tableb b
WHERE b.productId = p.id AND b.col = 'val'
)

How to access outer table in a LEFT OUTER JOIN

In the query below, I am trying to use the first table in a left outer join. However I am getting an error.
SELECT
products.id,
products_cstm.oem_c,
products.mfr_part_num,
products.description,
products.cost,
products.assigned_user_id,
customfields_oo.ans
FROM products
LEFT OUTER JOIN (SELECT COUNT( q.id ) AS ans
, pq.product_id
FROM products_quotes pq
LEFT JOIN quotes q
ON pq.quote_id = q.id
WHERE q.deleted = 0
AND pq.deleted = 0
AND q.stage <> 4
AND (pq.qty_shipped < pq.product_qty)
AND pq.product_id = products.id
GROUP BY pq.product_id
) AS customfields_oo
ON customfields_oo.product_id = products.id
LEFT JOIN products_cstm
ON products.id = products_cstm.id_c
WHERE products.deleted = 0
ORDER BY ans DESC
When I run the query it gives me the following error:
Error Code : 1054
Unknown column 'products.id' in 'where clause'
It is not allowing first "products" table in left outer join query.
The issue is that customfields_oo is a derived table not a correlated subquery. Thus, you cannot reference the outer table from within the definition of the derived table. In this case, you cannot refer to the outer products table from within the customfields_oo definition. Instead, you must do that filter in the On clause outside the dervied table definition.
Select products.id,
products_cstm.oem_c,
products.mfr_part_num,
products.description,
products.cost,
products.assigned_user_id,
customfields_oo.ans
FROM products
Left Join (
Select pq1.product_id
, Count( q1.id ) As ans
From products_quotes As pq1
Left Join quotes As q1
On pq1.quote_id = q1.id
Where q1.deleted = 0
And pq1.deleted = 0
And q1.stage <> 4
And pq1.qty_shipped < pq1.product_qty
Group By pq1.product_id
) As customfields_oo
On customfields_oo.product_id = products.id
Left Join products_cstm
On products.id = products_cstm.id_c
Where products.deleted = 0
Order By customfields_oo.ans Desc
Now, you have stated in comments that this is too slow because, say products where deleted <> 0 might be evaluated in the derived table. If that is the case, then simply expand the derived table to include the filters on the outer products table.
Select products.id,
products_cstm.oem_c,
products.mfr_part_num,
products.description,
products.cost,
products.assigned_user_id,
customfields_oo.ans
FROM products
Left Join (
Select pq1.product_id
, Count( q1.id ) As ans
From products_quotes As pq1
Join products As p1
On p1.products.id = pq1.product_id
Left Join quotes As q1
On pq1.quote_id = q1.id
Where q1.deleted = 0
And pq1.deleted = 0
And q1.stage <> 4
And pq1.qty_shipped < pq1.product_qty
And p1.deleted = 0
Group By pq1.product_id
) As customfields_oo
On customfields_oo.product_id = products.id
Left Join products_cstm
On products.id = products_cstm.id_c
Where products.deleted = 0
Order By customfields_oo.ans Desc
You do not need to have AND pq.product_id = products.id in the where statement. Because you are LEFT JOINing on that. So I think something like this will work:
AND (pq.qty_shipped < pq.product_qty)
GROUP BY pq.product_id) AS customfields_oo
ON customfields_oo.product_id = products.id
LEFT JOIN products_cstm
ON products.id = products_cstm.id_c
WHERE products.deleted = 0
ORDER BY openorder DESC
EDIT
You do not need to LEFT JOIN on the table you are COUNTing on. You can also do ot like this:
SELECT
.....
(
SELECT
COUNT( q.id )
FROM products_quotes pq
LEFT JOIN quotes q
ON pq.quote_id = q.id
WHERE q.deleted = 0
AND pq.deleted = 0
AND q.stage <> 4
AND (pq.qty_shipped < pq.product_qty)
AND pq.product_id = products.id
) AS ans
FROM products
.....

MySQL - Counting rows and left join problem

I have 2 tables, campaigns and campaign_codes:
campaigns: id, partner_id, status
campaign_codes: id, code, status
I want to get a count of all campaign_codes for all campaigns WHERE campaign_codes.status equals 0 OR where there are no campaign_codes records for a campaign.
I have the following SQL, but of course the WHERE statement eliminates those campaigns which have no corresponding records in campaign_codes ( i want those campaigns with zero campaign_codes as well)
SELECT
c.id AS campaign_id,
COUNT(cc.id) AS code_count
FROM
campaigns c
LEFT JOIN campaign_codes cc on cc.campaign_id = c.id
WHERE c.partner_id = 4
AND cc.status = 0
GROUP BY c.id
I'd opt for something like:
SELECT
c.id AS campaign_id,
COUNT(cc.id) AS code_count
FROM
campaigns c
LEFT JOIN campaign_codes cc on cc.campaign_id = c.id
AND cc.status = 0 -- Having this clause in the WHERE, effectively makes this an INNER JOIN
WHERE c.partner_id = 4
GROUP BY c.id
Moving the AND to the join clause makes the join succeed or fail, crucially keeping resulting rows in where there is no matching row in the 'right' table.
If it were in the WHERE, the comparisons to NULL (where there is no campaign_code) would fail, and be eliminated from the results.
SELECT
c.id AS campaign_id,
COUNT(cc.id) AS code_count
FROM
campaigns c
LEFT JOIN campaign_codes cc on cc.campaign_id = c.id
AND c.partner_id = 4
AND cc.status = 0
GROUP BY c.id