SQL many-to-many, how to check criteria on multiple rows - sql

In many-to-many table, how to find ID where all criteria are matched, but maybe one row matches one criterion and another row matches another criterion?
For example, let's say I have a table that maps shopping carts to products, and another table where the products are defined.
How can I find a shopping cart that has at least one one match for every criterion?
Criteria could be, for example, product.category like '%fruit%', product.category like '%vegetable%', etc.
Ultimately I want to get back a shopping cart ID (could be all of them, but in my specific case I am happy to get any matching ID) that has at least one of each match in it.

I am assuming a table named cart_per_product with fields cart,product, and a table named product with fields product,category.
select cart from cart_per_product c
where exists
(
select 1 from product p1 where p1.product=c.product and p1.category like N'%fruit%'
)
and exists
(
select 1 from product p2 where p2.product=c.product and p2.category like N'%vegetable%'
)

You can use ANY and ALL operators combined with outer joins. A simple sample on a M:N relation:
select p.name
from products p
where id_product = ALL -- all operator
( select pc.id_product
from categories c
left outer join product_category pc on pc.id_product = p.id_product and
pc.id_category = c.id_category
)

I think you can figure out the column names
select c.id
from cart c
join product p
on c.pID = p.ID
group by c.id
having count(distinct p.catID) = (select count(distinct p.catID) from product)

Generic approach that possibly isn't the most efficient:
with data as (
select *,
count(case when <match condition> then 1 end)
over (partition by cartid) as matches
from <cart inner join products ...>
)
select * from data
where matches > 0;

Related

Deleting data from one table if the reference doesn't exist in two other tables

I managed to import too much data into one of my database tables. I want to delete most of this data, but I need to ensure that the reference doesn't exist in either of two other tables before I delete it.
I figured this query would be the solution. It give me the right result on a test database, but in the production environment it returns no hits.
select product
from products
where 1=1
and product not in (select product from location)
and product not in (select product from lines)
You are getting no results/hits it means that you table location and/or lines having the null values in the product column. in clause failed if column having null value.
try below query just added the null condition on the top of your shared query.
select product from products
where 1=1
and product not in ( select product from location where product is not null)
and product not in ( select product from lines where product is not null)
Use EXISTS instead of IN which is more efficient
DELETE FROM products WHERE
NOT EXISTS
(
SELECT
1
FROM [Location]
WHERE Product = Products.Product
) AND
NOT EXISTS
(
SELECT
1
FROM lines
WHERE Product = Products.Product
)
Try this..
DELETE FROM Products where not exists
(select 1 from Location
join lines on lines.Product = Location.Product
and Location.Product = Products.Product
);
It's difficult to tell from your post why the query would return results in the test database but not production other than there is different data or different structures. You might try including the DDL for the participating tables in your post so that we know what the table structures are. For example, is the "product" column a PK or a text name?
One thing that does jump out is that your query will probably perform poorly. Try something like this instead: (Assuming the "product" column is a PK in Products and FK in the other tables.)
Select product
From Products As p
Left Outer Join Location As l
On p.product = l.product
And l.product is null
Left Outer Join Lines as li
On p.product = li.product
And li.product is null;
This simple set based approach may help ...
DELETE p
FROM products p
LEFT JOIN location lo ON p.product = lo.product
LEFT JOIN lines li ON p.product = li.product
WHERE lo.product IS NULL AND li.product IS NULL

SQL Beginner: Getting items from 2 tables (+grouping+ordering)

I have an e-commerce website (using VirtueMart) and I sell products that consist child products. When a product is a parent, it doesn't have ParentID, while it's children refer to it. I know, not the best logic but I didn't create it.
My SQL is very basic and I believe I ask for something quite easy to achieve
Select products that have children.
Sort results by prices (ASC/DSC).
SELECT * FROM Products INNER JOIN Prices ON Products.ProductID = Prices.ProductID ORDER BY Products.Price [ASC/DSC]
Explanation:
SELECT - Select (Get/Retrieve)
* - ALL
FROM Products - Get them from a DB Table named "Products".
INNER JOIN Prices - Selects all rows from both tables as long as there is a match between the columns in both tables. Rather, JOIN DB Table "Products" with DB Table "Prices".
ON - Like WHERE, this defines which rows will be checked for matches.
Products.ProductID = Prices.ProductID - Your match criteria. Get the rows where "ProductID" exists in both DB Tables "Products" and "Prices".
ORDER BY Products.Price [ASC/DSC] - Sorting. Use ASC for Ascending, DSC for Descending.
This table design is subpar for a number of reasons. First, it appears that the value 0 is being used to indicate lack of a parent (as there's no 0 ID for products). Typically this will be a NULL value instead.
If it were a NULL value, the SQL statement to get everything without a parent would be as simple as this:
SELECT * FROM Products WHERE ParentID IS NULL
However, we can't do that. If we make the assumption that 0 = no parent, we can do this:
SELECT * FROM Products WHERE ParentID = 0
However, that's a dangerous assumption to make. Thus, the correct way to do this (given your schema above), would be to compare the two tables and ensure that the parentID exists as a ProductID:
SELECT a.*
FROM Products AS a
WHERE EXISTS (SELECT * FROM Products AS b WHERE a.ID = b.ParentID)
Next, to get the pricing, we have to join those two tables together on a common ID. As the Prices table seems to reference a ProductID, we can use that like so:
SELECT p.ProductID, p.ProductName, pr.Price
FROM Products AS p INNER JOIN Prices AS pr ON p.ProductID = pr.ProductID
WHERE EXISTS (SELECT * FROM Products AS b WHERE p.ID = b.ParentID)
ORDER BY pr.Price
That might be sufficient per the data you've shown, but usually that type of table structure indicates that it's possible to have more than one price associated with a product (we're unable to tell whether this is true based on the quick snapshot).
That should get you close... if you need something more, we'll need more detail.
use the below script if you are using ssms.
SELECT pd.ProductId,ProductName,Price
FROM product pd
LEFT JOIN price pr ON pd.ProductId=pr.ProductID
WHERE EXISTS (SELECT 1 FROM product pd1 WHERE pd.productID=pd1.ParentID)
ORDER BY pr.Price ASC
Note :neither of your parent product have price in price table. If you want the sum of price of their child product use the below script.
SELECT pd.ProductId,pd.ProductName,SUM(ISNULL(pr.Price,0)) SUM_ChildPrice
FROM product pd
LEFT JOIN product pd1 ON pd.productID=pd1.ParentID
LEFT JOIN price pr ON pd1.ProductId=pr.ProductID
GROUP BY pd.ProductId,pd.ProductName
ORDER BY pr.Price ASC
You will have to use self-join:
For example:
SELECT * FROM products parent
JOIN products children ON parent.id = children.parent_id
JOIN prices ON prices.product_id = children.id
ORDER BY prices.price
Because we are using JOIN it will filter out all entries that don't have any children.
I haven't tested it, I hope it would work.

Oracle single query with multiple lookups to same table

I have a reference table containing a list of account numbers. For each account in that table, I need to query a table containing a list of activities; each activity can have zero to many associated notes. Each activity also has a product, call type, reason and outcome, which are stored as references to a category table containing these in a single field. And just for fun, I have to pull all of this data in a single query.
Here's where I am so far:
SELECT
ACTIVITY.ACTIVITYID,
ACTIVITY.ACCOUNTNO,
ACTIVITY.CREATEDATETIME,
C1.CATDESC AS PRODUCT,
C2.CATDESC AS CALLDRIVER,
C3.CATDESC AS REASON,
C4.CATDESC AS OUTCOME,
NOTE.NOTEID,
NOTE.NOTEDESC
FROM NOTE
RIGHT JOIN ACTIVITY
ON NOTE.ACTIVITYID = ACTIVITY.ACTIVITYID
RIGHT JOIN REFERENCE
ON ACTIVITY.ACCOUNTNO = REFERENCE.ACCOUNTNO
INNER JOIN CATEGORY C1
ON ACTIVITY.PRODUCTCODE = C1.CATCODE
INNER JOIN CATEGORY C2
ON ACTIVITY.CALLDRIVERCODE = C2.CATCODE
INNER JOIN CATEGORY C3
ON ACTIVITY.REASONCODE = C3.CATCODE
INNER JOIN CATEGORY C4
ON ACTIVITY.OUTCOMECODE = C4.SOURCECATCODE
WHERE ACTIVITY.CREATEDATETIME >= (SYSDATE -30)
ORDER BY ACTIVITYID, NOTEID
This almost does what I want it to do, except that it returns many, many more rows than it should (between 12 and 40 rows instead of 1-3). If I remove the joins to CATEGORY, like so:
SELECT
ACTIVITY.ACTIVITYID,
ACTIVITY.ACCOUNTNO,
ACTIVITY.CREATEDATETIME,
NOTE.NOTEID,
NOTE.NOTEDESC
FROM NOTE
RIGHT JOIN ACTIVITY
ON NOTE.ACTIVITYID = ACTIVITY.ACTIVITYID
RIGHT JOIN REFERENCE
ON ACTIVITY.ACCOUNTNO = REFERENCE.ACCOUNTNO
WHERE ACTIVITY.CREATEDATETIME >= (SYSDATE -30)
ORDER BY ACTIVITYID, NOTEID
then it works perfectly as expected, so I know the problem is with the repeated joins to the CATEGORY table.
The PRODUCTCODE, CALLDRIVERCODE, REASONCODE and OUTCOMECODE fields in ACTIVITY all map to the CATCODE (id) and CATDESC (string) fields in CATEGORY. What I'm looking for is an alternative way to look up these values on a row by row basis, while still containing all of this within a single query.
As an alternative you can put the category queries in the select part of the query.
select ...
,(select c1.catdesc from category c1
where c1.catcode=activity.productcode) as product
,(select c2.catdesc from category c2
where c2.catcode=activity.calldrivercode) as calldriver
...

Finding if there's at least one attribute in common in a set

I'm trying to do a validation using an Oracle SQL query, and the idea is:
There are items, and to every item there could be more than one suppliers.
I have a set of items (in a shopping bag) and want to verify if there is at least one common supplier between them. That means that all the items in the bag share at least one supplier.
Simplifying, the tables are:
BAG table with bag_id and item columns.
SUPPLY table with item and supplier columns.
Thank you!
This should give you a list of suppliers that match more than one item, with the most common suppliers on top
SELECT SUPPLY.supplier, COUNT(SUPPLY.item) item_count
FROM BAG
JOIN SUPPLY
ON BAG.item = SUPPLY.item
GROUP BY SUPPLY.supplier
HAVING COUNT(SUPPLY.item) > 1
WHERE BAG.bag_id = ? --substitute bag_id
ORDER BY COUNT(SUPPLY.item) DESC
If you need to find suppliers that match ALL items in the bag, then use this query:
SELECT SUPPLY.supplier
FROM BAG
JOIN (SELECT bag_id, COUNT(*) as item_count FROM BAG GROUP BY bag_id WHERE bag_id = ?) bag_count
ON BAG.id = bag_count.bag_id
JOIN SUPPLY
ON BAG.item = SUPPLY.item
GROUP BY SUPPLY.supplier
HAVING COUNT(SUPPLY.item) = bag_count.item_count
WHERE BAG.bag_id = ?
You can check if the list is empty to determine if no common suppliers exist by wrapping this with an EXISTS statement.
select case when exists
(
select 1
from bag b
inner join item i on i.id = b.item_id
inner join supplier s on s.id = i.supplier_id
and s.is_main = 'Y'
)
then 'Y'
else 'N'
end contains_main_supplier
from dual

SQL Query to fetch all product categories and whether the product is in it

I have three tables. Product, product categories and product category links. The links table is there as a product can be in more than one category.
What I am trying to achieve is a list of ALL the categories with an additional field which states whether for that specific product id, if the product is in that category.
SELECT *, l.product_id as checked FROM `ProductCategories` LEFT JOIN ProductCategoriesLink l ON l.category_id = ProductCategories.id WHERE ( l.product_id = 1 ) AND ( ProductCategories.id > 0 ) GROUP BY ProductCategories.id ;
However at the moment, this only retrieves the categories which the product is in.
Any advice appreciated.
SELECT
ProductCategories.*,
l.product_id IS NOT NULL AS ProductInCategory
FROM
ProductCategories
LEFT JOIN ProductCategoriesLink AS l ON
ProductCategories.id = l.category_id AND
l.product_id = 1
This makes use of the fact that when a LEFT JOIN is performed, at least one row is returned for every row on the left-hand table. If there weren't matching rows on the right-hand table, the columns from that table are all NULL.