Count() and left join problem - sql

I'm having a problem with the a query which displays a list of shops with the number of products associated with it. I've been playing around with left joins etc for quite a while now but to no avail. The tables have the following structures:
Shops table containing columns: id, name
Products table containing columns: id, name, status, shop
The query is as follows:
select s.name
, p.name
, count(p.id)
from Product as p
left join Shop as s on p.shop=s.id
where p.status <> '8796107276379'
group by
s.id
I'm not getting the shops which have 0 products. How can I achieve this please?
The underlying database is MySQL.
Thanks!
Krt_Malta

You need SHOP on the LEFT side, since the right side is the one that may not have data, in this case PRODUCT.
Not only that, you need the WHERE condition as a LEFT-JOIN ON condition, so that it joins to products on the status condition and just discounts the product (while keeping shop) even if the status is not desired.
select s.name
, p.name
, count(p.id)
from Shop as s
left join Product as p on p.shop=s.id AND p.status <> '8796107276379'
group by
s.id, p.name

select s.name
, p.name
, count(p.id)
from Shop as s
left join Product as p on s.id=p.shop
where p.status <> '8796107276379'
group by
s.id

You need to add OR p.status IS NULL to your where clause.
select s.name, p.name, count(p.id)
from Shop s
left join Product p on p.shop = s.id
where (p.status <> '8796107276379' OR p.status IS NULL)
group by s.name, p.name

I suffered this gotcha too and though I am not entirely sure why, placing the predicate on the jojn itself rather than the actual main query is how to solve it.
I actually documented the whole thing, before reading this. I used a simple example with two two small tables, it explains I hope the difference, maybe it will help
http://simpleritsolutions.com/sql/left/join/problems

Related

how to count two values from three dataset

I have 3 datasets: company, post, postedited,
I want to count the numbers of companies' post and postedited. some companies post but did not edited.
here is my query :
SELECT company.name, company.id, count(*),
( select count(*)
from post, postedited
where post.id=postedited.post_id)
from company, post as p
where company.id=p.company_id
group by company_id
the outcome of post is right, but the column of postedited is the same. what's wrong with my query?
Your subquery is completely unrelated to the main query. It selects post and postedited and counts. You are showing this result for every row of the main query.
You want the subquery relate to the main query's post. So remove the post table from the subquery's from clause:
(select count(*) from postedited where postedited.post_id = p.id)
Now this subquery selects a count for the post_id of the main query's records. At last you must get the sum of the counts:
select
c.name, c.id, count(*) as posts,
sum(select count(*) from postedited pe where pe.post_id = p.id) as edits
from company c
join post p on p.company_id = c.id
group by c.id;
You can achieve the same thus:
select
c.name, c.id, count(distinct p.id) as posts, count(pe.post_id) as edits
from company c
join post p on p.company_id = c.id
left join postedited pe on pe.post_id = p.id
group by c.id;
SELECT c.name AS companyName
, c.id AS companyID
, COUNT(DISTINCT p.id) AS postCount
, COUNT(DISTINCT pe.post_id) AS postEditCount
FROM company c
LEFT OUTER JOIN post p ON p.Company_ID = c.ID
LEFT OUTER JOIN postEdited pe ON pe.Company_ID = c.ID
GROUP BY c.id, c.name
That will give you a list of all companies in your company table with a count of each of their posts and edited posts. If you need to further query against that dataset, you can. Or you can add a WHERE clause to the above query to filter it.
And I agree, please don't use comma syntax. It's very easy to produce unintended results, and it doesn't give a good representation of what you're actually querying against. Plus, it's no longer standard and being deprecated in many flavors of SQL. Good JOIN syntax will make your life much easier.

SQL Group by not returning accurate answer

it is current result
select cat.sms_schoolcategoryid as categoryId,
cat.sms_name as category ,
count(sch.sms_name) as schoolname,
count(stu.accountnumber)NoofStudent
from Filteredsms_schoolcategory cat
inner join Filteredsms_school sch
on cat.sms_schoolcategoryid=sch.sms_schoolcategoryid
inner join FilteredAccount stu
on sch.sms_schoolid=stu.sms_schoolid
group by cat.sms_schoolcategoryid,
cat.sms_name
;
I have three tables one is Category and 2nd is Schools and 3rd is students. i just want to count the schools on behalf of category when I join tables category and school it returns me accurate result and when i join students table with schools table it returns me wrong result. Please Guide me how it is possible.
There is guesswork involved unless you provide more information on you data model. However, it appears that your issue is rooted in the following:
When you join the student table, for each combination of school category and school (*) you generate additional records. I.e., your sql no longer counts schools by school category but students.
For a concrete solution and a good advice see #Nick.McDermaid's comment.
Try this one
select count(stu.accountnumber) as NoofStudent
, catsch.categoryId
, catsch.category
, catsch.schoolname
from FilteredAccount stu
inner join (
select cat.sms_schoolcategoryid as categoryId
, cat.sms_name as category
, count(sch.sms_name) as schoolname
, sch.sms_schoolid
from Filteredsms_schoolcategory cat
inner join Filteredsms_school sch
on cat.sms_schoolcategoryid = sch.sms_schoolcategoryid
group by cat.sms_schoolcategoryid, cat.sms_name, sch.sms_schoolid) catsch
on catsch.sms_schoolid = stu.sms_schoolid
group catsch.categoryId
, catsch.category
, catsch.schoolname
count() returns the number of non-NULL values. So, your two count() will return the same values. You can quickly fix the query using count(distinct):
select cat.sms_schoolcategoryid as categoryId,
cat.sms_name as category ,
count(distinct sch.sms_name) as schoolname,
count(distinct stu.accountnumber) as NoofStudent
from Filteredsms_schoolcategory cat inner join
Filteredsms_school sch
on cat.sms_schoolcategoryid = sch.sms_schoolcategoryid inner join
FilteredAccount stu
on sch.sms_schoolid = stu.sms_schoolid
group by cat.sms_schoolcategoryid, cat.sms_name ;
Actually, you probably don't need the second count distinct. Just count(stu.accountnumber) should count the students.

Basic SQL Joining Tables

Having a bit of trouble with a basic SQL problem.
The question is that I have to find the salespersons first and last name, then their Social Insurance Number, the product description, the product price, and quantity sold where the total quantity sold is greater than 5.
I'll attach the database information below as a photo.
Product quantity sold greater than 5
SELECT ProductId
FROM ProductsSales
HAVING SUM(QuantitySold) > 5
Use that to get the rest:
SELECT s.FirstName, s.LastName, s.SIN, p.ProductDescription, ps.UnitSalesPrice, ps.QuantitySold
FROM ProductsSales ps
LEFT JOIN Products p on p.ProductID = ps.ProductID
LEFT JOIN Salesmen s on s.SalesmaneID = ps.SellerID
WHERE ps.ProductID IN
(
SELECT ProductId
FROM ProductsSales
GROUP BY ProductId
HAVING SUM(QuantitySold) > 5
)
SELECT a.FirstName, a.LastName, a.SIN, c.ProductDescription, b.UnitSalesPrice, b.QuantitySold
FROM Salesmen a
LEFT JOIN ProductsSales b
ON a.SalesmanId = b.SellerId
LEFT JOIN Products c
ON b.ProductId = c.ProductId
WHERE b.QuantitySold > 5
Select a.FirstName, a.LastName, a.SIN From Salesmen as a,
c.ProductDescriptio, c.Price, b.sum(QunatitySold)
inner join ProductSales as b on a.Salesmanid = b.sellerid
inner join Products as c on c.ProductId = b.ProductId
having b.sum(QunatitySold)> 5
group by a.FirstName, b.ProductDescription
Brad,
Welcome to SQL. Joining for me was a terrifying experience when I first started but its really easy. The general concept is this:
Pick a Join
If you want to see all records that would be common between the two table, you would use and JOIN. If you wanted to combine the two tables but still show all records you use LEFT JOIN
The basic syntax is
SELECT fieldnames FROM tablename alias
JOIN othertable alias ON firstalias.field = secondalias.field
--Example
SELECT animal, food, idtag from animals a
JOIN food f on a.animalid = f.animalid
This assumes you have a common field animalid in both the animals table and the food table. you should also ideally preface the field names with the alias to make it easier to understand like this: a.animal, f.food
And you keep going until you have joined all the tables you need.
Make sure you only request field names you want
Hope that helps

SQL query to get default values if translations are not found

These are my tables:
Product:
ID PRICE NAME DESCRIPTION
1 100 laptop laptop_desc
2 200 mouse mouse_desc
Product_Translations:
PID LANG NAME DESCRIPTION
1 ch 伊吾 伊吾伊吾
Please don't worry about name and description in the Product table. We are keeping it to avoid join if default language is selected by the user.
Now I need to write a query to get all the products from Product according to the users's language with fallback that if no translations for name and description are found in Product_Translations get them from Product table. I tried couple of different ways, but couldn't make it work.
Update:
I require all the columns from Product table(In this example, I only gave 2, but my actual table has more columns). And one more restriction is that I need to generate this query using JPA Criteria API, so any SQL keywords not supported by JPA may not work for me.
Thanks for your help.
SELECT p.ID, p.Price,
COALESCE(pt.Name, p.Name) Name,
COALESCE(pt.Description, p.Description) Description
FROM Product p
INNER JOIN User u on u.ID = #MyUserID
LEFT JOIN Product_Translations pt ON pt.PID = p.ID AND pt.LANG = u.LANG
In your case, you should use LEFT JOIN
SELECT p.ID, p.PRICE, ISNULL(pt.NAME,p.NAME) AS NAME, p.DESCRIPTION
FROM Product AS p
LEFT JOIN Product_Translations AS pt ON p.ID = pt.PID
WHERE pt.LANG = #UserLang
ISNULL in hsqldb
http://hsqldb.org/doc/guide/builtinfunctions-chapt.html#bfc_general_functions
Use LEFT OUTER JOIN:
SELECT
p.id
,p.price
,ISNULL(t.name, p.name) AS translation
,ISNULL(t.description, p.description) AS description
FROM Product p
LEFT JOIN Translations t
ON p.id = t.pid
AND t.lang = ?
This will work on SQL Server if you use other DB change to COALESCE().

SQL: COUNT() with items appearing multiple times in one-to-many relationship? Alternative to GROUP BY?

I have a query listing product categories, by supercategory > category. The query counts the number of products in each category (COUNT(ptc.catid)).
Howewer: the query doesn't let a category appear twice (a category can be linked to multiple supercategories). How can this be re-written so categories (c.title) can be listed under multiple supercategories?
SELECT
s.supercategory_id,
s.title,
c.title,
c.id,
COUNT(ptc.catid)
FROM supercategories s,
supercategories_to_categories stc,
categories c,
products p,
products_categories ptc
WHERE c.viewable='1'
AND c.id=stc.category_id
AND stc.supercategory_id=s.supercategory_id
AND ptc.catid=c.id
AND ptc.productid = p.id
AND p.viewable='y'
GROUP BY ptc.catid
ORDER BY s.title, c.title
Thanks!
The join syntax you're using is a bit "old school", so if you don't mind I'll rewrite it here:
select
s.supercategory_id,
max(s.title) as supercategory_title,
max(c.title) as category_title,
c.id,
count(ptc.catid) as product_count
from supercategories s
join supercategories_to_categories stc on stc.supercategory_id = s.supercategory_id
join categories c on c.id = stc.category_id
join products_to_categories ptc on ptc.catid = c.id
join products p on p.id = ptc.productid
where p.viewable = 'y'
group by s.supercategory_id, c.id
As shown, I've changed the group by expression to include supercategory_id, making it possible for a category to show up once per super category that it's a member of. I also added aggregation functions around the non-group-by columns, as I'm not sure how that would have executed before.
Try changing your GROUP BY to:
GROUP BY ptc.catid, stc.supercategory_id