SQL - Counting how many associated records another table has - mysql

As I'm SQL beginner, I can't describe a problem in a simple way, so let me show you an example:
3 Tables:
PRODUCT
id
group_id
person_id
GROUP
id
name
PERSON
id
group_id
As you see, GROUP can have multiple PERSONs and PRODUCT can be connected with GROUP and PERSON.
From this point, I would like to count number of PERSONs having a PRODUCT within a GROUP
I don't really understand the background of IN or using another SELECT within FROM, so if that's the point, then I'm happy that I was one step before it lol.
SELECT
group.name as GROUP_name,
COUNT(DISTINCT person_id) AS PERSON_having_min_one_PRODUCT
FROM products
LEFT JOIN groups ON groups.id = products.group_id
LEFT JOIN persons ON persons.id = products.person_id;
With this data:
GROUP
ExampleGroupName1 has 3 PERSONs, but 2 of them has >0 PRODUCTS
ExampleGroupName2 has 3 PERSONs and all of them has >0 PRODUCTS
ExampleGroupName3 has 2 PERSONs, but none of them has the PRODUCT
ExampleGroupName4 has 2 PERSONs, but only 1 has >0 PRODUCT
I would like to have an output like this:
GROUP_name | PERSON_having_min_one_PRODUCT
ExampleGroupName1 | 2
ExampleGroupName2 | 3
ExampleGroupName4 | 1

I would like to count number of PERSONs having a PRODUCT within a GROUP
Note: I will assume the table product does not have the column group_id, since it is redundant and can lead to a lot of errors.
The following query will show you the result you want by joining the tables person and product:
select
count(distinct x.id)
from person x
join product p on p.person_id = x.id
where x.group_id = 123 -- choosing a specific group
and p.id = 456 -- choosing a specific product

This would rather be simple like below meaning all the groups with some group_id with count(persons) and those count who has some product via id used in having clause
Select group_id,
count( distinct id ) AS "PERSON_WITH_PRODUCT"
from
person group by group_id having id
in (Select id from product);

Related

SQL Query to Return Top Field for Each Member

Let's say I have a table with the following fields...
MEMBER_ID (text)
CATEGORY1 (int)
CATEGORY2 (int)
CATEGORY3 (int)
CATEGORY4 (int)
...and let's say I have, like, 30+ more CATEGORY fields, all numbered accordingly. And in each category field, there is a numerical score.
Is there a query that could be used to populate a new table that looks like so...
MEMBER_ID
TOP_CATEGORY (the category name from the previous table with the
highest score for this MEMBER_ID)
SECOND_CATEGORY (the category name from the previous table with the
second-highest score for this MEMBER_ID)
THIRD_CATEGORY (the category name from the previous table with the
third-highest score for this MEMBER_ID)
...I know I could use CASE, but if I have a ton of CATEGORY fields, I assume that would get unwieldy. Do I have any other options?
Your best option in my opinion would be to normalise your database structure. Create a table of members, a table of categories and a table of scores by member & category. Having normalised, the problem you are trying to solve becomes a "top n per group" problem, followed by conditional aggregation. For example, if you follow the template for normalising that I have made in this demo, your query would look something like this:
select member_id,
max(case when `rank` = 1 then name end) as top_category,
max(case when `rank` = 2 then name end) as second_category,
max(case when `rank` = 3 then name end) as third_category
from (select n.member_id, c.name, s1.score, count(s2.score) + 1 as rank
from score s1
left join score s2 on s2.member = s1.member and s1.score < s2.score
join new_members n on n.id = s1.member
join category c on c.id = s1.category
group by n.member_id, c.name, s1.score
having count(s2.score) < 3) s
group by member_id
Once your database is normalised, adding new members, categories and scores becomes a lot easier, and queries to get the data out such as the above don't have to change at all.

SQL inner join wrong output from two tables

Students Table:
ID | Family_ID | Student_name | F_name
Families Table:
ID | F_name | Contact_No
i want to get all records from students where family_id is repeating.(basically i want to know students Brothers/Sisters records if there is any in student table.
i tried it this way but got wrong output;
SELECT students.*
FROM students
INNER JOIN families
ON families.id = students.family_id
ORDER BY families.id ASC
my query result in image: as you can see some ids are showing once others are more then once, but i think all should appear more then once.
If you want to see only relevant people you don't need to link it to the families table. You can group the student with family_id. Here is your query :
SELECT *
FROM Student
WHERE family_id IN (SELECT family_id
FROM students
GROUP BY family_id
HAVING COUNT(1)>1)
ORDER BY family_id
You could try using a join on subquery for the family_id that have more that one rows in students
SELECT students.*
FROM students
inner join (
select students.family_id
FROM students
group by students.family_id
having count(*)>1
) t on t. family_id = students.family_id

MySQL Query - Get Customers who have not bought all parts of a single product ID

Okay so I have 2 tables. One table for Product List and one table for Orders. There will be several of the same ProductID in my Table1 since each ProductID has several parts to it (IE: Part 1 of 7.)
The PartNumber will be a number. How do I design my query to find me all the customers who have purchased one of the part numbers, but not all the part numbers for a single product ID?
I'm just learning the basics of MySQL so any help would be much appreciated!
Table1 - Product List
UniqueIDKey
Product ID
PartNumber
Table2 - Orders
UniqueIDKey
Product ID Ordered
PartNumber Ordered
Customer ID
So an order might look like this:
UniqueIDKey: 77
Product ID Ordered: 1001
PartNumber Ordered: 3
Customer ID: 2000001
And, several rows of my Table1 - Product List might look like this:
UniqueIDKey Product ID PartNumber
77 1001 1
78 1001 2
79 1001 3
You need to know the total number of parts under each product prior
to knowing which customers bought some parts of a product but not the
whole.
The query enclosed by table alias B provides count of parts for
each product.
The query enclosed by table alias A provides for each
<customer,product> pair the total number of bought parts.
Now the rest is to match whether the total number of bought parts is
less than the total number of parts of a product.
In this approach the query would look like below:
SELECT
A.customer_id,
A.product_id,
A.total_parts_of_product_customer_purchased AS total_purchased,
B.total_parts,
B.total_parts - A.total_parts_of_product_customer_purchased AS didnot_purchase
FROM (
SELECT
customer_id,
product_id,
count(part_number) AS total_parts_of_product_customer_purchased
FROM Orders AS ordr
GROUP BY
customer_id, product_id
) AS A
INNER JOIN (
SELECT
product_id,
count(part_number) AS total_parts
FROM product_list AS pl
GROUP BY product_id
) AS B
ON A.product_id = B.product_id
WHERE A.total_parts_of_product_customer_purchased < B.total_parts
ORDER BY A.customer_id;
Use a cross join to get all combinations of customers,product_id's and part_numbers. left join orders table on to this result to get customers who haven't ordered all the parts in a product.
select c.customer_id,p.product_id
from (select product_id,part_number from product_list) p
cross join (select distinct customer_id from orders) c
left join orders o on p.product_id=o.product_id and p.part_number=o.part_number and c.customer_id=o.customer_id
group by c.customer_id,p.product_id
having count(o.part_number) < count(p.part_number)

Matching items in one table that don't match in a subset of a second table

Suppose I have a Product table, and a
id product
1 Apple
2 Bag
3 Cat
4 Ducati
and a Cart table
id user_id product_id
1 1 2
2 1 3
3 2 1
4 3 1
So, I want to look at a particular user and see what he/she does NOT have in their Cart.
In other words, in the above example
SELECT ...... WHERE user_id=1 .....
would return Apple and Ducati because User 1 already has Bag and Cat.
(This may well duplicate another question but there are so many variations I couldn't find the exact match and put in these simple terms may help)
Perform a left join from product to all products purchased by user1, which can be retrieved with a subselect in the join. This will cause all product id's that are not in user1's care to have null product ids. The where clause will select all null product id's meaning they will not have been in a users cart, essentially filtering purchased items.
select p.name
from product p
left join (select product_id, user_id
from cart where user_id = 1)
c
on p.id = c.product_id
where c.product_id is null;
SQL Fiddle: http://sqlfiddle.com/#!2/5318eb/17
Select
*
From Product p
Where p.id Not In
(
Select c.product_id
From Cart c
Where User ID = ____
)
SELECT product FROM product_table
WHERE product NOT IN
(SELECT product_id FROM cart_table WHERE user_id = 1);
This will give you all product for all users which are not in there cart.
select c.user_id,a.Product
from cart c Cross Join product a
left Join
cart b on b.product_id=a.id and c.user_id=b.user_Id
where b.product_id is null
group by c.user_id,a.Product
Sql Fiddle Demo

Count and group by join

I have a table for products and a table for users who have bought products. On the products table A there is site_name which determines where the products were bought.
Table B shows the users and what they have bought.
I am using the following to show a list of products bought, by site_name, grouping the product name together.
SELECT product FROM A JOIN B ON A.prod_id = B.prod_id WHERE A.site_name = 'ebay' group by A.product
Table A for products is:
prod_id
site_name
product
Table B for users is:
user_id
prod_id
What i can't figure out is how to get the number of products bought per line.
e.g. in table A there is
prod_id site_name product
------- --------- -------
1 ebay chair
2 amazon desk
3 ebay lamp
and on table b
user_id prod_id
------- -------
1000 1
1001 2
1002 1
1003 3
So I want to show each line where site_name is ebay and how many products were bought, order by most first like:
chair 2
lamp 1
I would use a LEFT JOIN, so that if there is a product that has never been purchased, it will show up with a count of 0.
Also, I would use COUNT(users.prod_id), instead of COUNT(*), so that it will only count rows which have satisfied the LEFT JOIN condition:
SELECT
products.product,
COUNT(users.prod_id) AS productsBought
FROM
A AS products
LEFT JOIN B AS users
ON products.prod_id = users.prod_id
WHERE products.site_name = 'ebay'
GROUP BY products.product
A minor change to Michael's query.
SELECT
products.product,
COUNT(users.prod_id) AS productsBought
FROM
A AS products
INNER JOIN B AS users
ON products.prod_id = users.prod_id
WHERE products.site_name = 'ebay'
GROUP BY products.product
The above query will not return products that haven't been bought.