How to get all rows from one table which have all relations? - sql

I have 3 tables:
companies (id, name)
union_products (id, name)
products (id, company_id, union_product_id, price_per_one_product)
I need to get all companies which have products with union_product_id in (1,2) and total price of products (per company) is less than 100.
What I am trying to do now:
select * from "companies" where exists
(
select id from "products"
where "companies"."id" = "products"."company_id"
and "union_product_id" in (1, 2)
group by id
having COUNT(distinct union_product_id) = 2 AND SUM(price_per_one_product) < 100
)
The problem I stuck with is that I'm getting 0 rows from the query above, but it works if I'll change COUNT(distinct union_product_id) = 2 to 1.
DB fiddle: https://www.db-fiddle.com/f/iRjfzJe2MTmnwEcDXuJoxn/0

Try to join the three tables as the following:
SELECT C.id, C.name FROM
products P JOIN union_products U
ON P.union_product_id=U.id
JOIN companies C
ON P.company_id=C.id
WHERE P.union_product_id IN (1, 2)
GROUP BY C.id, C.name
HAVING COUNT(DISTINCT P.union_product_id) = 2 AND
SUM(P.price_for_one_product) < 100
ORDER BY C.id
See a demo.

SELECT c.name FROM "companies" c
JOIN "products" p ON c.id = p.company_id
WHERE union_product_id IN (1, 2) AND price_for_one_product < 100
GROUP BY c.name
HAVING COUNT(DISTINCT p.name) =2
This would provide you all the company(s) name(s) which has provides both union_product_id 1 and 2 and the price_for_one_product/ price_per_one_product is less than 100.
Note: You might need to change price_for_one_product with price_per_one_product, as in question you have used price_per_one_product but db-fiddle link table defination has used price_for_one_product.

Related

SQL | List all all tuples(a, b, c) if there exists another tuple with equal (b,c)

I have three tables where the bold attribute(s) is the primary key
Restaurants(restaurant_ID, name, ...)
resturant_ID, name, ...
1, Macdonalds
2, Hubert
3, Dorsia
... ...
Identifier(restaurant_ID, food_ID)
restaurant_ID, food_ID, ...
1, 1
1, 4
2, 1
2, 7
... ...
Food(food_ID, name, ...)
food_ID food_name
1 Chips
2 Burgers
3 Salmon
... ...
Using postgres I want to list out all restaurants (restaurant_id and name - 1 row per restaurant) that have share the exact same set of foods with at least one other restaurant.
For example, let's say
Restaurant with ID "1" has only associated food_id's 1 and 4 as shown in Identifier
Restaurant with ID "3" has only associated food_id's 4 and 1 as shown in Identifier
Restaurant with ID "7" has only associated food_id's 6 as shown in Identifier
Restaurant with ID "9" has only associated food_id's 6 as shown in Identifier
Then output
Restaurant_id name
1 name1
3 name3
7 ...
9 ...
Any help would be greatly appreciated!
Thank you
Use the aggregate function string_agg() to get the full list of foods for each restaurant:
with cte as (
select restaurant_ID,
string_agg(food_ID::varchar(10),',' order by food_ID) foods
from identifier
group by restaurant_ID
)
select r.*
from Restaurants r inner join cte c
on c.restaurant_ID = r.restaurant_ID
where exists (select 1 from cte where restaurant_ID <> c.restaurant_ID and foods = c.foods)
But I would prefer to group restaurants based on matching foods:
with cte as (
select restaurant_ID,
string_agg(food_ID::varchar(10),',' order by food_ID) foods
from identifier
group by restaurant_ID
)
select string_agg(r.name, ',') restaurants
from Restaurants r inner join cte c
on c.restaurant_ID = r.restaurant_ID
group by foods
having count(*) > 1
See the demo.
Here is a way to get the unique set of resturants having exactly same food items. This uses array_agg() and array_to_string() functions
With cte as
(select T.restaurant_id, array_to_string(array_agg(food_id), ',') as food_list
from
(select *
from Identifier t1
order by restaurant_id, food_id) T
group by T.restaurant_id)
select
concat(r1.name,',',r2.name) as resturant_names,
t1.restaurant_id as restaurant_id1,
r1.name as restaurant_1,
t2.restaurant_id as restaurant_id2,
r2.name as restaurant_2,
t1.food_list as common_food_ids
from cte t1
join cte t2
on t1.restaurant_id < t2.restaurant_id
and t1.food_list = t2.food_list
left join Restaurants r1
on t1.restaurant_id = r1.restaurant_id
left join Restaurants r2
on t2.restaurant_id = r2.restaurant_id;
EDIT : Here is a dB fiddle - https://dbfiddle.uk/?rdbms=postgres_12&fiddle=e2de05edfbe036cc0d81c64d60f0b599 . Also, just for reference, solution to the same problem in Oracle using listagg function - https://dbfiddle.uk/?rdbms=oracle_11.2&fiddle=12785c3d5abbca97be5d44dd45a6da4a
Update : Below query addresses the update output format of the question.
With cte as
(select T.restaurant_id, array_to_string(array_agg(food_id), ',') as food_list
from
(select *
from Identifier t1
order by restaurant_id, food_id) T
group by T.restaurant_id)
select
--concat(r1.name,',',r2.name) as resturant_names,
t1.restaurant_id as restaurant_id,
r1.name as restaurant--,
--t2.restaurant_id as restaurant_id2,
--r2.name as restaurant_2,
--t1.food_list as common_food_ids
from cte t1
join cte t2
on t1.restaurant_id = t2.restaurant_id
and t1.food_list = t2.food_list
left join Restaurants r1
on t1.restaurant_id = r1.restaurant_id
left join Restaurants r2
on t2.restaurant_id = r2.restaurant_id;
As I understand your question, you want all restaurants that have the same list of foods as restaurant 1.
If so, that's a relation division problem. Here is an approach using joins and aggregation:
select r.name
from identifier i1
inner join identifier i2 on i2.food_id = i1.food_id
inner join restaurant r on r.restaurant_id = i2.restaurant_id
where i1.restaurant_id = 1
group by r.restaurant_id
having count(*) = (select count(*) from identifier i3 where i3.restaurant_id = 1)

How can I show all "article" which have more than 3 "bids"?

I wanna show the "ArticleName" of all "offers" that have more than 3 "bids". The number of the "Bids" should be output.
I don't know how I can write it down. But I think I know the Logic. It should count the same number of the Table "bid" and the column "OID" and in the end it should paste the number which is more than 3.
Picture:
Well that's easy enough:
Select ArticleName
, count(*) NumberOfBids
from Offer o
join Bid b
on b.oid = o.oid
group by ARticleName
having count(*) >= 3
SELECT * FROM (
SELECT o.ArticleName, count(b.BID) as numberOfBids
FROM Offer as o INNER JOIN bid as b ON o.oid = b.oid
GROUP BY o.ArticleName
) as c
WHERE c.numberOfBids > 3

comparing data from tables in sql server

I am facing great peril.
I have 2 TABLES--- purchaseTbl and CustomerTbl , which contain:
purchaseTbl : C_ID (int - FK) , Purchase_amt (int)
CustomerTbl: C_ID (int - PK), [other details].
So i want to calculate the sum of all purchases where the C_ID in both the tables match
Thank you
Gru
Use group by clause in your query like this....
SELECT CustomerTbl.C_ID, SUM(Purchase_amt) AS PurchaseSUM FROM CustomerTbl, purchaseTbl WHERE purchaseTbl.C_ID = CustomerTbl.C_ID GROUP BY CustomerTbl.C_ID
SELECT C.C_ID,
--You can add more columns (like customer name) here if you wish
SUM(Purchase_amt) AS SUMP
FROM CustomerTbl C
JOIN purchaseTbl P
ON P.C_ID = C.C_ID
GROUP BY C.C_ID
--If you added more columns in the select add them here too separated with comma
If you just want to know the total amount and not split it into customers then:
SELECT SUM(Purchase_amt) AS SUMP
FROM CustomerTbl C
JOIN purchaseTbl P
ON P.C_ID = C.C_ID
The above will get the total amount only if there is a corresponding C_ID in CustomerTbl.

Multiple unique ways of executing a simple query (ORACLE)?

I have to come up with 5 different ways (unique execution plans) to process the following query.
Find the items that are delivered by all suppliers.
My database holds the following tables:
QSPL – it holds a list of supplier names
SPLNO (number)
SPLNAME (varchar)
QDEL– it holds delivery items, suppliers, and departments
DELNO (number)
DELQTY (number)
ITEMNAME (varchar)
DEPTNAME (varchar)
SPLNO (number)
QITEM – it holds list of items
ITEMNAME (varchar)
ITEMTYPE (varchar)
ITEMCOLOR (varchar)
I was able to successfully come up with the following four unique queries.
1.
select itemname --, etc.
from qitem
where itemname not in
(select itemname
from qitem, qspl
where (char(splno)+itemname) not in
(select char(splno)+itemname
from qdel));
2.
select itemname --,etc.
from qitem
where not exists
(select *
from qspl
where not exists
(select *
from qdel
where qdel.itemname = qitem.itemname
and Qdel.splno = qspl.splno));
3.
select a.itemname --, etc
from qitem a join qdel b on a.itemname = b.itemname
group by a.itemname
having count (distinct splno) = (select count(*) from qspl);
4.
select itemname
from qdel
group by itemname
having count (distinct splno) = (select count(*) from qspl);
I have no idea what to do for a 5th unique query.
Does anyone have a clue?
I tried to put this question in the best possible context with significant detail, feedback is greatly appreciated.
Thanks
Maybe some SQL 86 syntax:
select a.itemname --, etc
from qitem a, qdel b
where a.itemname = b.itemname
group by a.itemname
having count (distinct splno) = (select count(*) from qspl);
Or an outer join
select a.itemname --, etc
from qspl s, qdel b
WHERE s.splno (+)= b.splno
group by s.splno
having count (distinct b.splno) = (select count(*) from qspl);
This is another unique way (which I'm sure it's horribly inefficient):
select distinct splname
from (
select qi.itemname,
qs.splname,
count(distinct qi.itemname) over () as total_items,
count(distinct qd.itemname) over (partition by qd.splno) as items_per_supp
from qitem qi
left join qdel qd on qi.itemname = qd.itemname
left join qspl qs on qs.splno = qd.splno
) t
where total_items = items_per_supp
Or a variant of your #3 which will probably use a different execution plan:
with supplier_items as (
select splno, count(*) item_count
from qdel
group by splno
)
select splname
from qspl qs
join supplier_items si on qs.splno = si.splno
where si.item_count = (select count(*) from qitem);
Since this is homework, I will be obtuse: Check out the Oracle MINUS operator.

How to ensure outer join with filter still returns all desired rows?

Imagine I have two tables in a DB like so:
products:
product_id name
----------------
1 Hat
2 Gloves
3 Shoes
sales:
product_id store_id sales
----------------------------
1 1 20
2 2 10
Now I want to do a query to list ALL products, and their sales, for store_id = 1. My first crack at it would be to use a left join, and filter to the store_id I want, or a null store_id, in case the product didn't get any sales at store_id = 1, since I want all the products listed:
SELECT name, coalesce(sales, 0)
FROM products p
LEFT JOIN sales s ON p.product_id = s.product_id
WHERE store_id = 1 or store_id is null;
Of course, this doesn't work as intended, instead I get:
name sales
---------------
Hat 20
Shoes 0
No Gloves! This is because Gloves did get sales, just not at store_id = 1, so the WHERE clause has filtered them out.
How then can I get a list of ALL products and their sales for a specific store?
Here are some queries to create the test tables:
create temp table test_products as
select 1 as product_id, 'Hat' as name;
insert into test_products values (2, 'Gloves');
insert into test_products values (3, 'Shoes');
create temp table test_sales as
select 1 as product_id, 1 as store_id, 20 as sales;
insert into test_sales values (2, 2, 10);
UPDATE: I should note that I am aware of this solution:
SELECT name, case when store_id = 1 then sales else 0 end as sales
FROM test_products p
LEFT JOIN test_sales s ON p.product_id = s.product_id;
however, it is not ideal... in reality I need to create this query for a BI tool in such a way that the tool can simply add a where clause to the query and get the desired results. Inserting the required store_id into the correct place in this query is not supported by this tool. So I'm looking for other options, if there are any.
Add the WHERE condition to the LEFT JOIN clause to prevent that rows go missing.
SELECT p.name, coalesce(s.sales, 0)
FROM products p
LEFT JOIN sales s ON p.product_id = s.product_id
AND s.store_id = 1;
Edit for additional request:
I assume you can manipulate the SELECT items? Then this should do the job:
SELECT p.name
,CASE WHEN s.store_id = 1 THEN coalesce(s.sales, 0) ELSE NULL END AS sales
FROM products p
LEFT JOIN sales s USING (product_id)
Also simplified the join syntax in this case.
I'm not near SQL, but give this a shot:
SELECT name, coalesce(sales, 0)
FROM products p
LEFT JOIN sales s ON p.product_id = s.product_id AND store_id = 1
You don't want a where on the whole query, just on your join