SQL - How can I exclude results without subselect? - mysql

I stuck with sql query to list customer who bought product a but did not buy product b.
This is my table
Table customer
id_customer customer_name
1 name1
2 name2
3 name3
Table order
id_order id_customer product
1 1 a
2 1 b
3 2 b
4 3 a
I try:
SELECT * FROM customer, order WHERE customer.id_customer = order.id_customer
AND (order.product='a' AND order.product<>'b')
SELECT * FROM customer, order WHERE customer.id_customer = order.id_customer
AND (order.product IN ('a') AND order.product NOT IN ('b'))
[AND (order.product = 'a' AND order.product <> 'b')]
SELECT table1.id_customer, table1.customer_name FROM customer INNER JOIN order ON customer.id_customer = order.id_customer
WHERE order.product IN ('a') AND order.product NOT IN ('b')
[WHERE order.product = 'a' AND order.product <> 'b']
but it did not the right answer because it return:
1 1 a
4 3 a
The answer should be:
4 3 a
Anyone help me please. Thank you so much

You can use joins to filter out whether customer has each of product a, b, and then query the join to implement your particular logic. It would look something like this:
select distinct -- pull only unique customer information
C.*
from
customer C
left join -- orders of product a, which should exist
order OA on OA.id_customer = C.id_customer and OA.product = 'a'
left join -- orders of product b, which should not exist
order OB on OB.id_customer = C.id_customer and OB.product = 'b'
where -- orders of product a should exist
OA.id_order is not null
and -- orders of product b should not exist
OB.id_order is null

I don;t think we have all the details so here is a query that uses subqueries (ignoring question title) and returns customers rather than orders (ignoring expected resultset):
SELECT *
FROM customer AS c
WHERE EXISTS (
SELECT *
FROM order AS o
WHERE o.id_customer = c.id_customer
AND product = 'a'
)
AND NOT EXISTS (
SELECT *
FROM order AS o
WHERE o.id_customer = c.id_customer
AND product = 'b'
);

Related

Retriving data from 2 tables

I have 2 tables with below description.
table 1: customer, columns : customer_id, source
table 2: source, columns: source, rank
one customer would have many sources, each source has a particular rank in the rank table, i need to fetch the data in such a way that for each individual customer which ever has a lowest ranked source i need to fetch those records.
Here is an example:
customer table data is
1 abc
2 efg
3 abc
1 efg
1 hij
2 hij
source table data is
abc 2
hij 1
efg 3
the result set should be:
1 hij
2 hij
3 abc
You could use either of the two queries below to satisfy your requirement.
QUERY 1
SELECT c.customer_id,
c.source
FROM customer c
INNER JOIN source s
ON c.source = s.source
WHERE s.rank = (SELECT Min(s1.rank)
FROM source s1 inner join customer c1 on s1.source = c1.source
WHERE c1.customer_id = c.customer_id)
QUERY 2
SELECT x.customer_id ,
c1.source
FROM
(SELECT c.customer_id ,
MIN(s.rank) AS MinRank
FROM customer c
INNER JOIN SOURCE s ON c.source = s.source
GROUP BY c.customer_id) x
INNER JOIN customer c1 ON x.customer_id = c1.customer_id
INNER JOIN SOURCE s1 ON s1.source = c1.source
AND s1.rank = x.MinRank;
UPDATE 1
This update is in response to your comment for 3 tables rather than 2 tables. The query below extends Query 1 when your schema is spread across 3 tables.
SELECT c.customer_id,
s.source_name
FROM customer c
INNER JOIN source s
ON c.cust_id = s.cust_id
INNER JOIN rank r
ON s.source_name = r.source_name
WHERE r.rank = (SELECT Min(r1.rank)
FROM customer c1
INNER JOIN source s1
ON s1.cust_id = c1.cust_id
INNER JOIN rank r1
ON r1.source_name = s.source_name
WHERE c1.cust_id = c.cust_id);
For Oracle:
select d.customer_id, d.source
from (
select
c.customer_id,
s.source,
row_number() over (partition by c.customer_id order by s.rank asc) as rn
from customer c
join source s
on c.source = s.source
) d
where d.rn = 1
;
A much simpler way. Try this -
select c.cid,c.sourceid,min(s.rankid)
from customer c inner join sourc s
on (c.sourceid=s.sourceid)
group by c.cid order by c.cid asc
Here's an SQLFiddle
Select a.customer_id,b.source
from
(select c.customer_id,min(s.rank) as rank
from customer c
inner join source s
on c.source=s.source
group by c.customer_id) as a
inner join source b
on a.rank = b.rank

Get records that have 2 or more entries in a joining table matching a condition

customers
id INT PK
name VARCHAR
details
id INT PK
detail_name VARCHAR
customers_details
customer_id INT FK
detail_id INT FK
value INT
For each customer I have a set of details.
The following query will get all users that have the detail #2 equals to 10:
SELECT c.* FROM customers c
INNER JOIN customers_details cd ON cd.customer_id = c.customer_id
WHERE cd.detail_id = 2 AND cd.value = 10
My problem is that I need to get all customers that have 2 or more specific details. For example: I want to get all customers that have detail #2 = 10 AND detail #3 = 20.
Is there a simple way to do that using SQL?
I would do that:
select c.*
from customers_details cd
inner join customers c
on c.id = cd.customer_id
where cd.detail_id in (2,10)
group by cd.customer_id
having
sum(cd.detail_id = 2 and cd.value = 1) = 1
and sum(cd.detail_id = 10) = 1
What I do here is:
Grouping details by customer
Sum 1 if the condition is satisfied. If there is a detail = 2
Having filters only customers which has the both conditions
I use the WHERE clause in order to filter less results for HAVING filters again trying to avoid a full-scan.
Regards,
You're just looking for customers that have more than one detail ID, where one equals 2 and one equals 20?
SELECT c.customer_id, count (*) FROM customers c
INNER JOIN customers_details cd ON cd.customer_id = c.customer_id
WHERE cd.detail_id IN (2, 20)
GROUP BY c.customer_id
HAVING count(*) > 1
This should give you every customer_id that has a detail_id of 2 and a detail_id of 20
Use Group by and having
SELECT c.customer_id
, min(c.name) customer_name
, count(cd.customer_id) detailcount
FROM customers c
INNER JOIN customers_details cd ON cd.customer_id = c.customer_id
WHERE cd.Value is not null
group by c.customer_id
having COUNT(cd.customer_id)>=2
You can use count (distinct detail_id) = 2 to select all customers that have both detail ids
select c.* from customers_details cd
join customers c on c.id = cd.customer_id
where (detail_id = 2 and value = 10)
or (detail_id = 3 and value = 20)
group by customer_id
having count (distinct detail_id) = 2
select *
from customers as c
where exists (
select 1
from customer_details as cd
where
cd.customer_id = c.customer_id and
(detail_id = 2 and value = 10 or detail_id = 3 and value = 20)
having count(*) = 2
)
Be careful with parens. Not sure if some platforms might require a grouping on the subquery with a having clause. You can add a dummy group by like group by cd.customer_id if it does.

Intersection in mysql for multiple values

The intersect keyword is not available in mysql. I want to know how to implement the following in mysql db. My tables are:
customer(cid,city,name,state)
orders(cid,oid,date)
product(pid,price,productname)
lineitem(lid,pid,oid,totalquantity,totalprice)
I want the products bought by all the customers of a particular city 'X'. i.e. every customer in city 'x' should have bought the product. I managed to select the oid's and the pid's of customers living in that particular city. Now I should select the pid's which is present in all the oid's.
Example.
Oid Pid
2400 1
2400 2
2401 3
2401 1
2402 1
2403 1
2403 3
The answer from the above input should be 1 because it is present in all oid's. The query which I used to get the oid's and pid's:
select t.oid,l.pid
from lineitem l
join (select o.oid,c1.cid
from orders o
join (select c.cid
from customer c
where c.city='X') c1
where o.cid=c1.cid) t on l.oid=t.oid
Now I need to intersect all the oid's and get the result.The query should not be dependent on data.
Try:
select pid, count(*)
from (select t.oid, l.pid
from lineitem l
join (select o.oid, c1.cid
from orders o
join (select c.cid from customer c where c.city = 'X') c1
where o.cid = c1.cid) t
on l.oid = t.oid) x
group by pid
having count(*) = (select count(*)
from (select distinct oid
from lineitem l
join (select o.oid, c1.cid
from orders o
join (select c.cid
from customer c
where c.city = 'X') c1
where o.cid = c1.cid) t
on l.oid = t.oid) y) z
I think you can achieve what you want by using IN

sum sql group by values from another table

I've got 2 tables: Online Orders, Online orders details
I have an issue on how to calculate the total amount of the order in Online Order table based on the detail order table.
The detail table looks like:
id_order Id_product quantity price value
1 2 1 3 3
1 3 2 2 4
2 1 1 5 5
I would like to sum all the values from an id_order and insert them into the total amount of the order in the Online orders table.
Can you help me with the SQL command?
It's not clear what database system you use.
If you want to UPDATE orders.total_amount
This update statement will work under any DB:
update orders
set total_amount = (
select SUM(value)
from orders_details
where id_order = orders.id
)
where EXISTS(select *
from orders_details
where id_order = orders.id)
This update statement works under MySQL:
update orders u
inner join (select id_order, SUM(value) as total
from orders_details
GROUP BY id_order) s on
u.id = s.id_order
set u.total_amoun = s.total
Is this what you want?
SELECT o.id_order, SUM(quantity * price) AS total_price
FROM online o
INNER JOIN detail d
ON d.id_order = o.id_order
GROUP BY o.id_order

MySQL: SELECT if non of group by members is equal to x

I have troubles getting proper data.
I have table structure like:
id INT(11) AI
order_id INT(11)
status varchar(45)
This table log status changes for orders.
So order_id's will have few statuses.
Now I need to select rows and group them by order_id, where order never had status (not even one status with given order_id) != 'example'
We don't show orders, where one of members had status = example
Sample data
1 12 ready
1 12 example
2 13 ready
2 13 sent
So I don't want order 12 to show at all, because one of it members have "example" status
I've tried grouping results, but it's not enough.
you can do it by simple join query :
select a.order_id
from ordrstatus as a left outer join (select orderid , count(*) as status from orderstatus where status = 'example' group by orderid) as b on a.orderid = b.orderid
where b.status = 0 or b.status is NUll
Join query always run faster then IN query . by using Join in query it will run only one time .
You can try like this...it will return all order id which never had status -example
Select
Order_id,
from TableName A where Not Exists(
Select id from TableName B where
status='example' and
a.Order_id=b.Order_id
)
group by Order_id
Not quite sure if you want the records for order which have had a status of example, or ones which have never had a status of example
To get a list of orders (with the status grouped up) which have had a status of example:-
SELECT a.order_id, GROUP_CONCAT(a.status)
FROM SomeTable a
INNER JOIN
(
SELECT order_id, COUNT(*)
FROM SomeTable
WHERE status = 'example'
GROUP BY order_id
) b
ON a.order_id = b.order_id
GROUP BY order_id
To get those which have NEVER had a status of exmaple
SELECT a.order_id, GROUP_CONCAT(a.status)
FROM SomeTable a
LEFT OUTER JOIN
(
SELECT order_id, COUNT(*)
FROM SomeTable
WHERE status = 'example'
GROUP BY order_id
) b
ON a.order_id = b.order_id
WHERE b.order_id IS NULL
GROUP BY order_id
EDIT
SELECT a.order_id, GROUP_CONCAT(a.status)
FROM SomeTable a -- Statuses
LEFT OUTER JOIN
(
SELECT order_id, COUNT(*)
FROM SomeTable
WHERE status = 'example'
GROUP BY order_id
) b -- Get any order id which has had a status of example (as a LEFT JOIN)
ON a.order_id = b.order_id
INNER JOIN
(
SELECT order_id, MAX(id) AS Latestid
FROM SomeTable
GROUP BY order_id
) c -- Get the latest status for each order (ie, max id)
ON a.order_id = c.order_id
LEFT OUTER JOIN
(
SELECT order_id, id
FROM SomeTable
WHERE status = 'example2'
) d -- Get the id of the order status of example2
ON a.order_id = d.order_id AND c.Latestid = d.id -- join on the same order id and that the record id matches the latest record id
WHERE b.order_id IS NULL -- reject those where a match was found on example for any status
AND d.order_id IS NULL -- reject those where a match was found on example2 for the latest status
GROUP BY order_id
try this
SELECT Order_ID FROM tbl_Orders
WHERE Status NOT IN ('example')
GROUP BY Order_ID
SELECT DISTINCT x.order_id
FROM order_status x
LEFT
JOIN order_status y
ON y.order_id = x.order_id
AND y.status = 'example'
WHERE y.id IS NULL;