I have a somewhat far-fetched question I want to know if there is any way to solve it:
I have a table called PRODUCTS with these attributes:
ID, NAME, DATE
I have a table called PRODUCTS_STATUS with these attributes:
ID, PRODUCT_ID, STATUS_ID, DATE
I have a table called STATES with these attributes:
ID, STATUS_NAME
Now, as you have seen, it will be about:
STATES_PRODUCTS will have many STATES of PRODUCTS and STATUS will be many times in PRODUCT_STATUS
This means that PRODUCT_STATUS has a many-to-many relationship between states and products, since a PRODUCT will go through different state transitions.
Perfect up to there, it turns out that I have this case suppose:
PRODUCTS_STATES
ID, PRODUCT_ID, STATUS_ID, DATE
1 1 1 00:00:00
2 2 1 00:00:00
3 3 1 00:00:00
4 1 5 00:00:00
I am doing a query at the moment between PRODUCTS and PRODUCT_STATUS, at first I made a query where I told him to show me all the PRODUCTS that have in PRODUCT_STATUS the STATUS_ID= 1
But if you realize for example the PRODUCT_ID is twice in the table PRODUCT_STATUS with the STATUS_ID = 1 and 5
Ask:
Is it possible to make a query where it shows me all PRODUCTS with PRODUCT_STATUS equal to 1 but where PRODUCTS_ID that already has another state are excluded?
that is, for this example, I want to know if we could see PRODUCTS 2 and 3 because if they see 1 it already has another state.
Notice I have this query made but it is not useful for what I want:
SELECT
*
FROM
products
inner join product_status in products.id = product_status.product_id
WHERE
product_status.status_id not IN (
SELECT
product_status.id_status
FROM
product_status
WHERE
product_status.id_status = 5
)
Obviously they will realize that the query returns all the products with STATUS_ID = 1 but I no longer want to see PRODUCT 1 because I am no longer interested.
I would like to exclude all those PRODUCTS that have 1 and 5.
I hope you can give me an idea of how it is done. Thanks.
I recommend NOT EXISTS instead of NOT IN, because NOT IN has strange behavior when working with NULL values (and EXISTS often optimizes better than IN).
But your version is quite close:
SELECT p.*
FROM products p JOIN
product_status ps
ON p.id = ps.product_id
WHERE ps.status_id = 1 AND
NOT EXISTS (SELECT 1
FROM product_status ps2
WHERE ps2.product_id = ps.product_id AND
ps2.status_id <> 1
);
Note that this introduces tables aliases (abbreviations for the tables). This makes the query easier to write and to read.
Related
Background
Products can be sold as bundles. Following tables are present: products, bundles, bundles_products, orders, orders_products.
An order would be said to "contain" a bundle if it contains all the bundle's products.
Problem
How would one go about counting orders for bundles?
Example
products table
id name
1 broom
2 mug
3 spoon
4 candle
bundles table
id name
1 dining
2 witchcraft
bundles_products table
bundle_id product_id
1 2
1 3
2 1
2 4
orders_products table
order_id product_id
1000 1
1000 3
1001 1
1001 2
1001 3
The query would return the following table:
bundle orders
dining 1
witchcraft 0
Notes
The example intentionally misses the orders table as it is not relevant what it contains.
Of course, this could be approached imperatively, by writing some code and gathering the data, but I was hoping there is a declarative, SQL way of querying for this kind of things?
One idea I had was to use a GROUP_CONCAT to concatenate all the products of a bundle and somehow compare that with products of each order. Still, a long way from clear.
One way is to use two Derived Tables (subqueries). In first subquery, we will fetch the total number of unique products for every bundle. In the second subquery, we will fetch the total products in an order, for a combination of order and bundle.
We will LEFT JOIN them on bundle_id as well as matching the total count of products per bundle in them. Eventually, we will do a grouping on bundle, and count the number of orders matching successfully.
SELECT dt1.id AS bundle_id,
dt1.name AS bundle,
Count(dt2.order_id) AS orders
FROM (SELECT b.id,
b.name,
Count(DISTINCT bp.product_id) AS total_bundle_products
FROM bundles AS b
JOIN bundles_products AS bp
ON bp.bundle_id = b.id
GROUP BY b.id,
b.name) AS dt1
LEFT JOIN (SELECT op.order_id,
bp.bundle_id,
Count(DISTINCT op.product_id) AS order_bundle_products
FROM orders_products AS op
JOIN bundles_products AS bp
ON bp.product_id = op.product_id
GROUP BY bp.bundle_id,
op.order_id) AS dt2
ON dt2.bundle_id = dt1.id
AND dt2.order_bundle_products = dt1.total_bundle_products
GROUP BY dt1.id,
dt1.name
SQL Fiddle DEMO
Here's the brief example, which lacks some parts, I omitted because I don't know precise database structure. Logic is such:
Temp table is generated, which consists of 3 rows - order, count of
products related to bundle, count of products in bundle
Then we select only orders from this table in which we have those last two
variables equal
select count(order_id) from orders
left join(
select count(*) from bundles_products as bundle_amount,
sum(case when orders_products in (
select names from bundles_products where bundle_id='1') then 1 else 0) as order_total,
orders.order_id
left join product on bundle_products.product_id = products.product_id
left join orders on products.product_id = orders_products.product_id
where bundle_products.bundle_id ='1'
) as my_table
on orders.order_name = my_table.orders
where my_table.bundle_amount = my_table.order_total
Edit: I posted this as a response to previous version of the question, without detailed explanation.
Edit2: fixed query a bit. It can be starting point. Logic is still the same, you can get amount of orders for each bundle_id using it
i have two tables :
Product :
id name category
1 AAA BBB
2 CCC DDD
3 EEE FFF
Ordre:
id id_product date
1 2 10/11/16
2 2 06/16/16
3 3 12/09/16
4 1 02/06/16
5 3 15/10/16
in order to know if a product has an order i create this select query :
SELECT id,name ,category
CASE WHEN id IN (select id_product from Ordre) then 'Y'
ELSE 'N' END AS has_ordre
FROM product;
but this is not working for me, in the fact i want to create a View that contains the info about the products and also has the column "has_ordre" to check if a product has an ordre or not.
do you have any suggestion?
i'm new in Mysql
thanks in advance.
You can do what you need without a sub-query by doing a join on the two tables and a COUNT() on order.id_product, which will count the number of orders for each product. NOTE: this query will completely (as designed) exclude products which have no orders. Also, the result for this particular query will show the number of orders for each product:
SELECT p.id, p.name, p.category, COUNT(o.id_product) AS num_orders
FROM product p
RIGHT JOIN order o
ON p.id = o.id_product
GROUP BY p.id
ORDER BY num_orders
You can change the RIGHT JOIN to a LEFT JOIN if you would like to display all orders regardless of whether or not they have an active order:
Here is a fiddle
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! :)
It may be difficult to explain what I am after, apologies if the question is vague.
I have a table which associates products with keywords using IDs
So I may have product IDs, 2,3,4,5 associated with Keyword id 14
and product IDs 3,6,9 associated with Keyword id 15
My question is How do I count and store the total for those IDs associated with Keyword 14 and for those IDs associated with Keyword 15 and so on (New Keywords added all the time)?
MY SQL so far:
select products_keyword_categories.key_cat_name
from products_keyword_to_product
inner join products_keyword_categories
on products_keyword_categories.key_cat_id = products_keyword_to_product.key_cat_id
group by products_keyword_categories.key_cat_name
Many thanks in advance for any advice. Also, if there is any terminology that will aid me in further research via a Google search that would also be most welcome.
Edit to add: In the example above the table containing the associations is products_keyword_to_product - I inner join the other table to return the Keyword name.
Edit to add (2): Sorry I was afraid my question would be vague.
If I wanted to just count all the products using keyword ID 14 I would use COUNT() AS - As mentioned in the answers but I also need to count the number of products using Keyword ID 15 and Keyword ID 16 etc. - Hope that makes more sense.
select key_cat_name ,count(*)
from products_keyword_categories pkc
inner join products_keyword_to_product ptk on pkc.id=ptk.key_id
group by id;
select cat.key_cat_name, count(*) from
products_keyword_categories cat inner join products_keyword_to_product prod
on prod.key_cat_id=cat.key_cat_id
group by cat.key_cat_name
Edit:
select cat.key_cat_name, prod_assoc.product_id, count(*) from
products_keyword_categories cat inner join products_keyword_to_product prod_assoc
on prod_assoc.key_cat_id=cat.key_cat_id
group by cat.key_cat_name,prod_assoc.product_id
Assuming your tables structure is like this:
products_keyword_categories
key_cat_id key_cat_name
1 Electronics
2 Toys
3 Software
products_keyword_to_product
key_cat_id product_id
1 1
2 1
3 2
1 2
products
product_id name
1 Product A
2 Robot
Edit 2:
Try this
SELECT key_cat_name, product_id, COUNT(*)
FROM
(select cat.key_cat_name, prod_assoc.product_id from
products_keyword_categories cat inner join products_keyword_to_product prod_assoc
on prod_assoc.key_cat_id=cat.key_cat_id) as tbl
GROUP BY key_cat_name, product_id
Edit 3:
The query above is made of 2 parts:
The inner part:
(select cat.key_cat_name, prod_assoc.product_id from
products_keyword_categories cat inner join products_keyword_to_product prod_assoc
on prod_assoc.key_cat_id=cat.key_cat_id)
Which gives 1 row per combination of product_id and key_cat_name.
The outer part:
SELECT key_cat_name, product_id, COUNT(*)
FROM (...) as tbl
GROUP BY key_cat_name, product_id
Which operates on the results of the inner part (as tbl), counting how many times a combination of key_cat_name and product_id appears on the inner part.
Check this: Subqueries in MySQL, Part 1
You are almost there, you just need to add the following:
select count(products_keyword_to_product.id), products_keyword_categories.key_cat_name
...
the rest is correct
Updated Answer:
SELECT COUNT(*), reference_field FROM table WHERE...
HAVING field=value
GROUP BY field
For aggregate conditions you must use HAVING
I have a MYSQL table called 'categories' from a project I inherited from someone else.
id parent_id name
1 NULL Travel
2 NULL Sleep
3 NULL Eat
4 NULL Bath
5 1 Prams
6 1 Travel Systems
7 2 Cots
8 3 High Chairs
The table is obviously a lot bigger than that, but you get the general idea. I have a MYSQL statement which brings together this table with other category, brand and product tables, but basically I want to list the parent category name from the above table with the sub-category in the statement. How do I do this?
My current statement is something like:
SELECT brands.name, products.name, categories.id, categories.name, brands.id,
FROM `products` , `brands` , `categories`
WHERE products.brand_id = brands.id
AND products.category_id = categories.id
AND brands.name = '$brand'
ORDER BY categories.name, products.name
How do I retrieve the parent category names in the results?
For example if the product is a Pram, how can I output "Travel". I could do seperate MYSQL statements in the loop but I want to avoid this. This is either a stupidly simple question (in which case I apologise for being brain dead) or a little more complicated! Thanks.
First you need to know the parent id of the current category and then get the name for that id, you could use a subquery in this way:
SELECT name FROM categories WHERE id = (SELECT pid FROM categories WHERE name = $brand)
EDIT: Since you need to get the category and subcategory names in the same row for a given subcategory id, try this:
SELECT sc.name AS subcategory, c.name AS category
FROM categories sc
LEFT JOIN categories c ON c.id = sc.parent
WHERE sc.id = $subcategory_id