SQL Query Exclude Records - sql

I want to query a database of guests that bought certain items. I want to see what customers bought item 'A' but not item 'B'.
I tried:
SELECT customerName
FROM Customers
WHERE NOT item = 'A' AND item = 'B';
But I return customers that bought both items. I would like to exclude these customers from that query.
I am using SQLite

There are multiple ways to do this. I like to use group by and having, because it is very flexible for many conditions:
SELECT customerName
FROM Customers
GROUP BY customerName
HAVING SUM(CASE WHEN item = 'A' THEN 1 ELSE 0 END) > 0 AND
SUM(CASE WHEN item = 'B' THEN 1 ELSE 0 END) = 0;

You can also use the MINUS operator which returns all rows in the first SELECT statement that are not returned by the second SELECT statement. Such as:
(SELECT customerName FROM Customers WHERE item='A')
MINUS
(SELECT customerName FROM Customers WHERE item='B');

I would use EXISTS with NOT EXISTS:
select c.*
from Customers c
where exists (select 1
from Customers c1
where c1.customerName = c.customerName and c1.item = 'A'
) and not exists
(select 1
from Customers c2
where c2.customerName = c.customerName and c2.item = 'B'
);

Related

SQL WHERE both values of a variable exist

Suppose that I have a table of global box office information including columns "filmName", "country" and "earnings". The question is how to find out the films that sell better in country A than in country B. Here is my answer:
SELECT filmName
FROM Boxoffice
WHERE (SELECT earnings FROM Boxoffice WHERE country = "A") >
(SELECT earnings FROM Boxoffice WHERE country = "B")
GROUP BY filmName
But then I found out that there are some films that are not shown in both countries. I wonder how I can add the condition to the films that are shown in both countries to my existed answer. And I also have no idea if my answer has any problem since I do not have the real data.
I think a self join would be simpler:
SELECT a.filmname
FROM boxoffice a
JOIN boxoffice b ON a.country = 'A' AND b.country = 'B' AND a.earnings > b.earnings;
It seems filmname and country is the unique key for your table, i.e. there is one row per film and country.
One way to get films that sell better in country A than B is to aggregate and compare the earnings in the HAVING clause:
select filmname
from boxoffice
group by filmname
having max(case when country = 'A' then earnings end) >
max(case when country = 'B' then earnings end)
order by filmname;
Another way is to join, e.g.:
select a.filmname
from (select * from boxoffice where country = 'A') a
join (select * from boxoffice where country = 'B') b
on a.filmname = b.filmname and a.earnings > b.earnings
order by a.filmname;

Case statement for join condition

Hi I want to use case statement in where condition or some similar logic.
I want to ignore where condition when there are no rows in tmp_collaboration table and use the condition when table has some rows in it.
Select clbid from tmp_collaboration;
select customer_id, product_id ,clbid
from customers c
where hdr_id = 10
and clbid in (select clbid from tmp_collaboration)
and status = 'y';
Is this what you want?
select customer_id, product_id ,clbid
from customers c
where hdr_id = 10 and status = 'y' and
(clbid in (select clbid from tmp_collaboration) or
not exists (select 1 from tmp_collaboration)
);
why not use a JOIN .. if there are rows in table the rows are involved otherwise not .
select customer_id, product_id ,clbid
from customers c
INNER JOIN tmp_collaboration t on t.clbid = c.clbid
AND hdr_id = 10
AND status = 'y';

Selecting Distinct Records Not In A Set

I have a table of customers with their customer contact options.
Customers can be contact in one of three ways via:
Telephone (1)
SMS (2)
Email (3)
the FK id is in brackets.
If I wanted to pull out a list of distinct customer id's for both say SMS and Email I could do the following:
SELECT DISTINCT customer_id
FROM contact_options
WHERE contact_option_type_id IN (2,3)
But how do I do the inverse? Say I want a (DISTINCT) list of customers who don't have a Telephone contact. Can I do this without using a sub-query?
I realise the example is contrived, in practice I have very many different contact options (around 80).
One option is to use aggregation:
SELECT customer_id
FROM contact_options
GROUP BY customer_id
HAVING SUM(CASE WHEN contact_option_type_id = 1 THEN 1 ELSE 0 END) = 0;
We can also try doing this using EXISTS:
SELECT DISTINCT customer_id
FROM contact_options c1
WHERE NOT EXISTS (SELECT 1 FROM contact_options c2
WHERE c1.customer_id = c2.customer_id AND c2.contact_option_type_id = 1);
You can use not exists:
select distinct c.customer_id
from contact_options c
where not exists (select 1
from contact_options
where customer_id = c.customer_id and
contact_option_type_id = 1
);
I don't think exists has performance issue if you have proper indexing delcared.

Joining SQL tables and removing repetitions

I am trying to get a list of the customers with the same phone number, as there are instances of the same customer being created two or three times with slightly different names.
The query below has almost the intended behavior:
SELECT C1.CUSTOMER_NAME, C2.CUSTOMER_NAME, C1.PHONE_NUMBER
FROM CUSTOMER C1
JOIN CUSTOMER C2
ON C1.PHONE_NUMBER = C2.PHONE_NUMBER
WHERE C1.CUSTOMER_NAME != C2.CUSTOMER_NAME
AND C1.PHONE_NUMBER != ''
ORDER BY C1.CUSTOMER_NAME
But I get repetions like:
Customer A - Customer B
Customer A - Customer C
Customer B - Customer A
Customer B - Customer C
Customer C - Customer A
Customer C - Customer B
When all I want to get is the first two lines, which are enough to cover all the cases.
Thanks in advance for the help.
I'm not sure you want just the first two lines . . . because the last line seems different.
In any case, you can replace the != with < to get what you want:
SELECT C1.CUSTOMER_NAME, C2.CUSTOMER_NAME, C1.PHONE_NUMBER
FROM CUSTOMER C1 JOIN
CUSTOMER C2
ON C1.PHONE_NUMBER = C2.PHONE_NUMBER AND
C1.CUSTOMER_NAME < C2.CUSTOMER_NAME
WHERE C1.PHONE_NUMBER <> ''
ORDER BY C1.CUSTOMER_NAME;
If you just want all the customers on a given phone number -- when there is more than one customer -- then you do not need a join:
select c.phone_number, c.name
from (select c.*, count(*) over (partition by phone_number) as cnt
from customer c
) c
where cnt > 1
order by c.phone_number, c.name;
You could use a subquery (or JOIN with same login) to get the duplicate numbers first, then report on all the customers with that number:
SELECT CUSTOMER_NAME, PHONE_NUMBER
FROM CUSTOMER
WHERE PHONE_NUMBER IN (SELECT PHONE_NUMBER
FROM CUSTOMER
WHERE COUNT(PHONE_NUMBER) > 1 AND PHONE_NUMBER != '')
ORDER BY PHONE_NUMBER

SQL ignore rows based on criteria

This is going to sound stupid.
So 2 tables ORDER and ORDERDETAILs
ORDER_ID ITEM_NAME
======== =========
111 Paper
111 Toner
222 Paper
333 Pencils
I want to query only Order_ID's were the ITEM_Name is Paper
so for instance my query only result should be
ORDER_ID ITEM_NAME
======== =========
222 Paper
I don't want ORder_ID's that have other Item's related to it. I only want ORDERID's were the the only ITEM_Name is Paper.
If you want order ids that are only paper, I would recommend using group by and a having clause:
select od.Order_Id
from OrderDetails od
group by od.Order_Id
having sum(case when od.Item_Name = 'Paper' then 1 else 0 end) > 0 and
sum(case when od.Item_Name <> 'Paper' then 1 else 0 end) = 0
The having clause has two conditions. The first counts the number of rows for an order that have Paper as an item. The > 0 says that there needs to be at least one. The second counts the number of rows that do not have paper. The = 0 says that there needs to be none.
This can also be written as:
having sum(case when od.Item_Name = 'Paper' then 1 else 0 end) = count(*)
i.e. All the items for the order are "Paper".
I like this method because it is very general. You can readily extend it to include scissors and rocks. Or to get orders that have paper, but no rocks, and so on.
In this way, you can get every ORDER_ID with ITEM_NAME equals Paper
SELECT ORDER_ID FROM TABLE_NAME WHERE ITEM_NAME = 'Paper';
If you want to retrieve only the order_ids:
select DISTINCT(ORDER_ID)
from orderdetails
where ITEM_NAME='PAPER'
AND ORDER_ID NOT IN
(SELECT DISTINCT(ORDER_ID) FROM ORDERDETAILS WHERE ITEM_NAME!='PAPER')
If you want all the columns from the order:
select *
from orderdetails
where ITEM_NAME='PAPER'
AND ORDER_ID NOT IN
(SELECT DISTINCT(ORDER_ID) FROM ORDERDETAILS WHERE ITEM_NAME!='PAPER')
SELECT OrderDetails.*
FROM OrderDetails
WHERE ITEM_Name = 'Paper'
AND Order_ID NOT IN (SELECT Order_ID FROM OrderDetails WHERE ITEM_Name <> 'Paper')
In this one, you select all items where item_name = 'Paper'. Then exclude all orders where they have a line that <> 'Paper'
Alternates:
If we can assume that the Item_Name is unique for each order, then (at least in MS SQL Server) you could do:
;with NumberOfRows as
(
SELECT COUNT(1) as TotalRows, Order_ID
FROM OrderDetails
GROUP BY Order_ID
)
SELECT OrderDetails.*
FROM OrderDetails
INNER JOIN NumberOfRows
ON NumberOfRows.Order_ID = OrderDetails.OrderID
AND NumberOfRows.TotalRows = 1
WHERE OrderDetails.Item_Name = 'Paper'
As another option:
select OrderDetails1.*
from OrderDetails as OrderDetails1
LEFT JOIN OrderDetails OrderDetails2
ON OrderDetails1.Order_ID = OrderDetails2.Order_ID
AND OrderDetails2.ITEM_Name <> 'Paper'
Where ITEM_Name = 'Paper'
AND OrderDetails2.Order_ID IS NULL
In this last one, you get all the orders and join back onto themselves where they have a 2nd line that is not paper. Then Exclude all of the orders where the join was successful. This would be easier to understand if the order details table had something unique (like a line id).