how to get orphans from a join table in MySQL - mysql

Imagine 2 tables, the first one is a list of products (products), the second one a join table between products and another table (categories), called products-categories
products:
id | name
------------
1 Lorem
2 Ipsum
3 Dolor
4 Sit
products-categories
product_id | categories_id
---------------------------
1 3
1 6
4 1
2 2
How to get the orphan elements, I mean the elements in no category, so in this case: 3, in a efficient way (+30k records) using MyISAM?
This is somehow like showing all rows that are not joinable, but this syntax seams weird to me...

select * from products p
left join product_categories pc on p.id=pc.product_id
where pc.product_id is null
will return all products in table products that are not found in product_Category. LEft join and where is very fast. 30k records is also very little, so don't worry there.

Using SubQuery:
SELECT name FROM products
WHERE id NOT IN (SELECT product_id FROM products-categories);
Using JOIN
SELECT name FROM products
LEFT JOIN products_categories ON (id=product_id)
WHERE product_id IS NULL;
Better to go with join
sqlfiddle demo : http://sqlfiddle.com/#!2/684c1/8

select p.id from products p left join product-categories c on p.id=c.product_id
where c.id is NULL

I had a similar problem once i used something like following
select p.id from products p left join productscategories pc where pc.categories_id is null
-hj

Related

Filtering on a LEFT JOINED column

Is there a more efficient way to filter on a joined table as in the following example? Or is this a fine approach? This query returns the desired results, but I am an amateur at MySQL.
I have indexes on products.id, product_details.product_id and product_details.value
SELECT p.id
FROM products p
LEFT
JOIN product_details d
ON d.product_id = p.id
WHERE d.value = 1
OR p.id = 4
Simplified structure as follows:
products table
product_id (PRIMARY KEY) | name
--------------------------------
1 | Shirt
2 | Shoes
3 | Dress
4 | A product with no corresponding details row
product_details table
product_id (PRIMARY KEY) | value
---------------------------------
1 | 1
2 | 23
3 | 32
This is your query:
SELECT products.id
FROM products LEFT JOIN
product_details
ON product_details.product_id = products.id
WHERE product_details.value = 1 OR products.id = 4;
This is not a bad practice. I do think the query is easier to follow using EXISTS:
SELECT p.id
FROM products p
WHERE p.id = 4 OR
EXISTS (SELECT 1
FROM product_details pd
WHERE pd.product_id = p.id AND pd.value = 1
);
In addition EXISTS makes it clear that you don't want to return duplicates if there are duplicate matching rows in product_details.
If performance is you main consideration, then EXISTS is probably your best choice, with an index on product_details(product_id, value).
Couple of notes:
As a rule of thumb, a UNION ALL statement performs better than an OR operator. Also, this helps clear up the query.
Using both an implicit JOIN and a predicate in the WHERE clause on the same table can get you into trouble - especially if you're using a LEFT OUTER JOIN (the predicate in the WHERE clause has precedence over the LEFT OUTER JOIN).
Seems like you always want to pull back any records that has a products.id = 4, and also any products that have a product_details.value = 1. This seems like two separate queries to me, and splitting it would probably make it easier to maintain in the future.
SELECT
p.id
FROM
products p
WHERE
p.id = 4
UNION ALL
SELECT
p.id
FROM
product_details pd
JOIN
products p
ON
p.id = pd.product_id
WHERE
pd.value = 1
Source: https://bertwagner.com/posts/or-vs-union-all-is-one-better-for-performance/

MYSQL Query search in relationship

For the sake of clarity and this question i will rename the tables so it is a bit clearer for everybody and explain what i want to achieve:
There is an input form with options that return categories ID's. If a 'Product' has 'Category', i want to return/find the 'Product' which lets say has multiple categories(or just 1) and all of its categories are inside the array that is passed from the form.
Products table
ID Title
1 Pizza
2 Ice Cream
Categories table
ID Title
1 Baked food
2 Hot food
ProductsCategories table
ID ProductId CategoryId
1 1 1
2 1 2
So if i pass [1,2] the query should return Product with id 1 since all ProductsCategories are inside the requested array, but if i pass only 1 or 2, the query should return no results.
Currently i have the following query which works, but for some reason if i create a second Product and create a ProductCategory that has a CategoryId same as the first product, the query returns nulll...
SELECT products.*
FROM products
JOIN products_categories
ON products_categories.product_id= products.id
WHERE products_categories.category_id IN (1, 2)
HAVING COUNT(*) = (select count(*) from products_categories pc
WHERE pc .product_id = products.id)
All help is deeply appretiated! Cheers!
In order to match all values in IN clause, you just need to know in addition the number of passed categories which you must use it in HAVING clause:
SELECT
p.*,
GROUP_CONCAT(c.title) AS categories
FROM
Products p
INNER JOIN ProductsCategories pc ON pc.productId = p.ID
INNER JOIN Categories c ON c.ID = pc.categoryId
WHERE
pc.categoryId IN (1,2)
GROUP BY
p.id
HAVING
COUNT(DISTINCT pc.categoryId) = 2 -- this is # of unique categories in IN clause
So in case IN (1,2) result is:
+----+-------+---------------------+
| id | title | categories |
+----+-------+---------------------+
| 1 | Pizza | Baked Food,Hot Food |
+----+-------+---------------------+
1 row in set
In case IN (1,3) result is Empty set (no results).
#mitkosoft, thanks for your answer, but sadly the query is not producing the needed results. If the product's categories are partially in the passed categories the product is still returned. Additionally i might not know how many parameters are sent by the form.
Luckily I managed to create the query that does the trick and works perfectly fine (at least so far)
SELECT products.*,
COUNT(*) as resultsCount,
(SELECT COUNT(*) FROM products_categories pc WHERE pc.product_id = products.id) as categoriesCount
FROM products
JOIN products_categories AS productsCategories
ON productsCategories.product_id= products.id
WHERE productsCategories.category_id IN (7, 15, 8, 1, 50)
GROUP BY products.id
HAVING resultsCount = categoriesCount
ORDER BY amount DESC #optional
That way the query is flexible and gives me exactly what I needed! - Only those products that have all their categories inside the search parameters(not partially).
Cheers! :)

MySQL Queries - Selecting from 2 tables and if exists in the other check field

I am trying to pull products from a table called products, I also have a table called product_ranges.
products
--------
id
name
model
product_ranges
--------------
id
product_id
other_id
SELECT p.id
FROM products As p
LEFT JOIN product_ranges As pr ON (pr.product_id = p.id AND pr.other_id = 16)
This will select all products and include the product_ranges table columns too if the product exists in it but if it does exist in this table and the other_id does not equal 16 I don't want the product to be in the returned results but if the product doesn't exist at all in the other table I want it in the results still.
I am sure I have done this years ago but can't think of the SQL for it - if anyone knows the right query I would be grateful, thanks.
Updated:
SELECT p.id
FROM products
LEFT JOIN product_ranges pr ON pr.product_id = p.id
WHERE (pr.product_id IS NULL OR pr.other_id = 16)

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

Multiple Attributes from one Query

I am having a problem getting multiple attributes for one item. Below is my Table:
Table: product_attrib
id | product_id | name | value
------------------------------
0 | 33 | age | 25
1 | 33 | size | 25
My problem is when I join the query, I only get one of the attributes with such a query:
Query:
SELECT
p.*
,pa.name
,pa.value
FROM product AS p
LEFT OUTER JOIN product_attrib AS pa ON (
p.id = pa.product_id
)
My Results
"products_id":"0",
"products_price":"0.0000",
"products_name":null,
"products_description":null,
"attrib_name":"color",
"attrib_value":"red"
Do you see how I only get one attribute set?
Is there a way I can get all the attributes for a product?
Most likely, your original query is right as it is. You probably want the product, no matter if attributes can be found.
You can reverse the order of the tables in the JOIN to prevent losing rows from product_attrib like this (if product with product_id 33 does not exist):
SELECT
p.*
,pa.name
,pa.value
FROM product_attrib AS pa
LEFT JOIN product AS p ON p.id = pa.product_id
But that's probably not what you want.
A LEFT [OUTER] JOIN includes all rows from the left hand table and adds values from the right table where the JOIN condition can be fulfilled (potentially creating multiple rows if multiple matches are found in the right hand table.) If no matching row can be found in the right hand table NULL values are substituted for all columns of the right hand table.
Start by reading the manual here.
If you want "all attributes" per product in the same row, you need to aggregate values. Something like this:
SELECT p.*
,group_concat(pa.name) AS att_names
,group_concat(pa.value) AS att_values
FROM product AS p
LEFT JOIN product_attrib AS pa ON p.id = pa.product_id
WHERE p.product_id = 33
GROUP BY p.*;
i see so many people writing full joins when they aren't necessary. please correct me if i'm wrong.
SELECT
p.*
,pa.name
,pa.value
FROM product AS p, product_attrib AS pa
WHERE p.id = pa.product_id