identify stores not selling a product - sql

I have 2 tables city_products that contains all products in the city and product_list that contains all the products that exist in different cities.I want a list of products not available in each city.
[![enter image description here][1]][1]
Table 1: city_prd
city product
------ -------
city 1 p1
city 1 p3
city 1 p2
city 2 p1
city 2 p5
Table 2: pdt_list
product
-------
p1
p2
p3
p4
p5
Desired output:
city product
------ -------
city 1 p4
city 1 p5
city 2 p2
city 2 p3
city 2 p4
I know its something to do with a cross join but I am not getting the exact answer

This is a variation of TheImpaler's answer, but it should work in all databases. Use a cross join to generate all combinations of cities and products. Then use left join (or a similar mechanism) to remove the ones that exist:
select c.city, p.product
from (select distinct city from city_prd) c cross join
pdt_list p left join
city_prd cp
on c.city = cp.city and p.product = cp.product
where cp.city is null;
I am also guessing that you have a cities table of some sort. You can use that instead:
select c.city, p.product
from cities c cross join
pdt_list p left join
city_prd cp
on c.city = cp.city and p.product = cp.product
where cp.city is null;

You don't mention which database you are using, so I'll give you a generic SQL solution. You can adapt it to your specific RDBMS.
select
c.city, p.product
from (
select distinct city from city_prd
) c
cross join pdt_list p
except
select city, product from city_prd
order by c.city, p.product

Related

I have to find the oldest worker in the stores, searching by city

At the moment I have it find the oldest clerks living in a city, but somehow I have to change it to oldest clerks in the stores of a city. I have tried adding the StoreID in there, but it only messes things up. I also added a picture of the relationships
SELECT Name AS [Name of the oldest clerk]
FROM Clerks AS c, Cities AS ct, Stores AS s
WHERE ct.CityID=c.CityID
AND s.StoreID=c.StoreID
AND City=[Name of the city]
AND Age=(SELECT MAX(Age)
FROM Clerks AS c, Cities AS ct
WHERE ct.CityID=c.City
AND City = [Name of the city]);
Not tested yet, just an idea. I'll take a better look in a moment.
select cy.city,c.name,c.age,max_age.mx_age
from
clerks as c
join stores as s on c.storeid=s.storeid
join cities as cy on s.cityid=cy.cityid
join
(
select s2.cityid,max(c2.age) as mx_age from stores as s2
left join clerks as c2 on s2.storeid=c2.storeid
group by s2.cityid
) as max_age_by_city on s.cityid=max_age_by_city.cityid and c.age=max_age_by_city.mx_age
order by cy.city,c.age,c.name
Select c.*
from Clerks c
join Stores s on c.StoreID = s.StoreID
join Cities ct on ct.CityID= s.CityID
join ( select max(C2.Age) as Age, S2.CityID
from Clerks as C2
join Stores as S2 on C2.StoreID = S2.StoreID
group by S2.CityID) as MaxAge
on c.Age = MaxAge.Age
and s.CityID = MaxAge.CityID
where ct.City="London"
I've joined to Clerks the tuple of (max(Clerks.Age), Stores.City)
Fiddle Here
EDIT: joined Strores table to find max Age by store's city
Without all those joins :
SELECT ... FROM CLERKS C
WHERE NOT EXISTS (SELECT 1 FROM CLERKS C2
WHERE C2.CITYID = C.CITYID AND C2.AGE > C.AGE)
SELECT ... FROM CLERKS C
WHERE NOT EXISTS (SELECT 1 FROM CLERKS C2
WHERE C2.STOREID = C.STOREID AND C2.AGE > C.AGE)
As for "oldest clerks in the stores of a city" it should then become
SELECT ... FROM CLERKS C JOIN STORES S ON ...
WHERE NOT EXISTS (SELECT 1 FROM CLERKS C2
WHERE C2.STOREID = C.STOREID AND C2.AGE > C.AGE)
AND S.CITYID = ...

self join after an inner join

I am finding what cities have the same name in different states. The city name and state name are in seperate tables (cities and states) and can be inner joined over a seperate common column.
select c1.city, c1.state, c2.city, c2.state
from cities
inner join states on cities.commonid = states.commonid
After inner joining i need to self join to perform a function as follows
select c1.city, c1.state, c2.city, c2.state
from *joined table* c1 join
*joined table* c2
on c1.city = c2.city and c1.state <> c2.state
i am wondering how i can self join a table that is the result of another join in the same query
output will be like this
+----------+-------+--------+--------+
| city1 | state1|city2 |state2 |
+----------+-------+--------+--------+
| x | melb | x | syd |
| y | bris | y | ACT |
+----------+-------+--------+--------+
I assume that the table cities has a column like state_id that references a column state_id in the table states (change the names to the actual names of the columns).
First do a self join for cities with the conditions:
c1.city = c2.city AND c1.state_id < c2.state_id
The < operator makes sure that each pair of cities wil be returned only once.
Then join 2 copies of states, because each of them will be used to get the name of the state for each of the 2 cities:
SELECT c1.city city1, s1.state state1,
c2.city city2, s2.state state2
FROM cities c1
INNER JOIN cities c2 ON c1.city = c2.city AND c1.state_id < c2.state_id
INNER JOIN states s1 ON s1.state_id = c1.state_id
INNER JOIN states s2 ON s2.state_id = c2.state_id
ORDER BY city1
You can do select of your inner join query and give it alias.
Then it will become something like below...
Select c.city,c.state from
(Select City,state from cities inner join states where cities.id = states.id) as c
Now make a self join for c.
I would perform a pre-query of all cities that have more than one state. Then join to the states table to see which states they are encountered.
select
Duplicates.city,
s.state
from
( select c1.city
from cities c1
group by c1.city
having count(*) > 1 ) Duplicates
JOIN cities c2
on Duplicates.city = c2.city
JOIN states s
on c2.commonid = s.commonid
order by
Duplicates.city,
s.state
By NOT trying to do cross-tab of just two city/states, you would get a single list. If one city name exists in 5 states, how would you plan on showing that. This way you would see all alphabetized.
I would suggest a CTE:
with cs as (
select c.name as city_name, s.name as state_name
from cities c join
states s
on c.commonid = s.commonid
)
select cs1.*, cs2.*
from cs cs1 join
cs cs2
on cs1.name = cs2.name and cs1.state <> cs2.state;

SQL: How to pull particular orders based on order details?

I have four tables namely customers, orders, orderDetails and Products.
Customer table
cId cName
1 James
2 Adam
3 Ed
Order table
oId cId
1 1
2 2
3 3
OrderDetails table
oId odId pId Quantity
1 1 1 50
1 2 2 45
2 3 2 52
3 4 1 44
Products table
pId PName
1 Apple
2 Orange
I want the list of customers who have never ordered Oranges. I am able to pull records of customers whose order details don't have oranges. But in one of the case, James has ordered both apples and oranges. So, the query should not pull James. I can do this with a larger query. But I want this with a smaller query where something I'm missing.
SQL
SELECT c.cId, c.cName, p.PName, od.Quantity FROM customers c
LEFT JOIN orders o ON c.cId = o.cId
LEFT JOIN orderDetails od ON o.oId = od.oId
JOIN products p ON od.pId = p.pId
WHERE od.pId != 2
I would do this using not exists:
with has_oranges as (
select o.*
from orders o join
orderlines ol
on o.oid = ol.oid
where ol.pid = 2
)
select c.*
from customers c
where not exists (select 1
from has_oranges ho
where ho.cid = c.cid
);
If you want customer information, I don't see what oid has to do with anything.
Notes:
The CTE determines who actually has oranges.
You don't need the products table, because you are using the pid.
Use NOT EXISTS
SELECT *
FROM Customers c
WHERE NOT EXISTS (
SELECT 1 FROM orders o
JOIN orderDetails od ON o.oId = od.oId
JOIN products p ON od.pId = p.pId
WHERE p.pName = 'oranges' AND c.cId = o.cId
)
You want all customers that have never ordered oranges. So select all customer IDs that ordered oranges and only show customers that are not in this data set.
select *
from customers c
where cid not in
(
select cid
from orderdetails
where pid = (select pid from products where pname = 'Orange'
);
select * from CustomerTbl where Id in (select t1.Id from CustomerTbl t1
left join OrderTbl t2 on t1.Id = t2.CustomerId
left join OrderDetailTbl t3 on t3.OrderId = t2.Id
left join ProductTbl t4 on t4.Id = t3.ProductId
where t4.Id != 2)
This will return Customers who not ordered Oranges.
This is the sqlfiddle link : http://sqlfiddle.com/#!6/c908d/6
SQL Server 2008 introduced the EXCEPT and INTERSECT keywords for doing this sort of thing. I tend to find that the queries are clearer than when using CTEs.
Microsoft Documentation
select c.cId
from Customer c
except
select o.cId
from Orders o
join OrderDetail od on o.oId = od.oId
and od.pId = 2
cId
-----------
3
You can add the name to the result set by joining to the Customer table in the second half of the query:
select c.cId, c.cName
from Customer c
except
select o.cId, c.cName
from Orders o
join OrderDetail od on o.oId = od.oId
join Customer c on c.cId = o.cId
and od.pId = 2
cId cName
----------- --------------------
3 Ed
We have to eliminate users who took orange. So in the below query i have used sub query
Select C.Cname,OH.oid,PM.Pname,OD.Quantity from Customers C
inner join OrderHeader OH ON C.cid=OH.Cid
inner join OrderDetails OD on oh.oid=od.oid
inner join ProductMast PM on PM.pid=OD.pid where OH.oid not in (select oid
from OrderDetails where pid = 2)

Fetch data from more than one tables using Group By

I am using three tables in a PostgreSql database as:
Customer(Id, Name, City),
Product(Id, Name, Price),
Orders(Customer_Id, Product_Id, Date)
and I want to execute a query to get from them "the customers that have have ordered at least two different products alnong with the products". The query I write is:
select c.*, p.*
from customer c
join orders o on o.customer_id = c.id
join product p on p.id = o.product_id
group by (c.id)
having count(distinct o.product_id)>=2
It throws the error:
"column "p.id" must appear in the GROUP BY clause or be used in an aggregate function
LINE 1: select c.*, p.*".
However if I remove the the p.* from select statement (assuming that I one does not want the products, only the customers), it runs fine. How can I get the products as well?
Update: Having ordered two or more products, a customer must appear on the output as many times as its product he has ordered. I want as output a table with 5 columns:
Cust ID | Cust Name | Cust City | Prod ID | Prod Name | Prod Price
Is it possible in SQL given that group by should be used? Shoul it be used on more than one columns on different tables?
Try this out :
SELECT distinct c.* ,p.*
FROM Customer c
JOIN
(SELECT o.customer_id cid
FROM Product P
JOIN Orders o
ON p.id= o.product_id
GROUP BY o.customer_id
HAVING COUNT(distinct o.product_id)>=2) cp
ON c.id =cp.cid
JOIN Orders o
on c.id=o.customer_id
JOIN Product p
ON o.product_id =p.id
I hope it solves your problem.
I think you can use following query for this question -
SELECT C1.*, p1.*
FROM Customer C1
JOIN Orders O1 ON O1.Customer_Id = C1.Id
JOIN Product P1 ON P1.Id = O1.Product_Id
WHERE C1.Id IN (SELECT c.Id
FROM Customer c
JOIN Orders o ON o.Customer_Id = c.Id
GROUP BY (c.Id)
HAVING COUNT(DISTINCT o.Product_Id) >= 2)

SQL troubling with multiple count in query

I already provide sql fiddle with schema and sample data.
http://sqlfiddle.com/#!2/e9d22/7/0
If I would like to know how many province and how many cities in Thailand.
Country Name | No. Provinces | No. Cities
Thailand | 77 | 1234
I guess that it need to use multiple COUNT(*) but I dont know how to use it.
Anybody know please suggest solution?
You need to use GROUP BY and COUNT:
SELECT c.name, count(distinct p.id) provincecoutn, count(distinct city.id) citycount
FROM country c
LEFT JOIN province p on c.id = p.country_id
LEFT JOIN City on p.id = city.province_id
GROUP BY c.name
Good luck.
Try this:
SELECT
C.Name, COUNT(DISTINCT P.Id) NoProvance, COUNT(CC.Id) NoCities
FROM country C
JOIN province P
ON C.Id = P.COUNTRY_ID
JOIN city CC
ON P.Id = CC.province_id
WHERE C.Name = 'Thailand'
GROUP BY C.Name
SQL FIDDLE DEMO
It's probably faster to count cities per province before joining to province:
SELECT c.name AS "Country Name"
,count(p.id) AS "No. Provinces"
,sum(ci.city_ct) AS "No. Cities"
FROM country c
LEFT JOIN province p ON p.country_id = c.id
LEFT JOIN (
SELECT province_id, count(*) AS city_ct FROM city GROUP BY 1
) ci ON ci.province_id = p.id
GROUP BY 1
-> sqlfiddle for PostgreSQL(!)