Selecting Distinct Records Not In A Set - sql

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.

Related

Query to get all companies that have products with all specified attributes

Let's say I have two tables (1 to many): table company with fk id and table product with fk id and also fields sId and tId (if sId has data tId is null and vice versa, not sure if it matters).
I want every company that has products with, for example sId=1, sId=2, tId=3 and tId=4.
So for a company to be eligible it has to have products with sId=1 and sId=2 and tId=3 and tId=4. If one is missing it shouldnt appear.
I tried joining the tables and doing
where pro.sId in ('1', '2')
and pro.tId in ('3','4')
But it doesn't give me any companies. Any help would be appreciated.
You can use aggregation. Assuming no duplicate sld or tld per company_id in the products table:
select c.id
from companies c
inner join products p on p.company_id = c.id
where p.sld in (1, 2) or p.tld in (3, 4)
group by c.id
having count(*) = 4
If there are duplicates, you can change the having clause to:
having count(distinct p.sld) = 2 and count(distinct p.tld) = 2
There are many ways to achieve this. One is:
select * from company where id in
(
select company_id from product where sid = 1
intersect
select company_id from product where sid = 2
intersect
select company_id from product where tid = 3
intersect
select company_id from product where tid = 4
);
Another:
select * from company
where id in (select company_id from product where sid = 1)
and id in (select company_id from product where sid = 2)
and id in (select company_id from product where tid = 3)
and id in (select company_id from product where tid = 4);

How to SELECT with several conditions? (WHERE ... AND ... IN (...))

For example: in my database I have 3 tables: COMPANY, COUPON and COMPANY_COUPON.
COMPANY table has fields: ID and NAME, COUPON table has: ID, TITLE and TYPE, COMPANY_COUPON table has: ID of the COMPANies and ID of the COUPONs that they own.
So, in java to get all coupons of the company I use command:
SELECT coupon_id FROM company_coupon WHERE company_id = ?
And put it into Collection.
But I need something to get all coupons of the company by the type,
something like:
SELECT * FROM company_coupon WHERE company_id = 1 AND coupon_id = (SELECT * FROM coupon WHERE type = camping)
of course this one is not working, but I'm looking for something like that.
I know that i can get all coupons of the company and put them into Collection and then just delete all coupons that not equals to the specified type, but is there any way to do this process in database by SQL commands?
You might want to use WHERE IN here:
SELECT *
FROM COMPANY_COUPON
WHERE COMPANY_ID = 1 AND COUPON_ID IN (SELECT ID FROM COUPON WHERE TYPE = 'CAMPING');
You could also use EXISTS, which is probably the best way to write your logic:
SELECT cc.*
FROM COMPANY_COUPON cc
WHERE
cc.COMPANY_ID = 1 AND
EXISTS (SELECT 1 FROM COUPON c WHERE c.TYPE = 'CAMPING' AND c.ID = cc.COUPON_ID);
Using EXISTS might outperform doing a join between the two tables, because the database can stop as soon as it finds the very first match.
Use only one column with an IN operator
SELECT *
FROM COMPANY_COUPON
WHERE COMPANY_ID = 1
AND COUPON_ID IN (SELECT COUPON_ID FROM COUPON WHERE TYPE = CAMPING)
I think you just want a join:
SELECT cc.COUPON_ID
FROM COMPANY_COUPON cc JOIN
COUPON c
ON cc.COUPON_ID = c.ID
WHERE cc.COMPANY_ID = ? AND c.TYPE = ?;

SQL Query – records within the SQL Select statement, but NOT in the table being queried

I have a large list of CustIDs that I need to query on to find if they are within the CUSTOMER table; I want the result to tell me which CustIDs ARE on the table and which CustIDs are NOT on the table.
I provided a short list below to give an idea of what I need to do.
Oracle database
Table: Customer
Primary Key: CustID
Scenario:
Customer table only has the following (2) CustID: ‘12345’, ‘56789’
Sql:
Select * from CUSTOMERS where CUSTID in (‘12345’, ‘56789’, ‘01234’);
I want the result to tell me that both ‘12345’ and ‘56789’ are in the table, AND that ‘01234’ is NOT.
select
v.CustID,
exists (select * from Customer where Customer.CustID = v.CustID)
from (values (12345), (56789), (01234)) v (CustID);
Results:
custid exists
12345 true
56789 true
1234 false
You need a left join or subquery for this. The precise syntax varies by database. Typical syntax is:
select i.custid,
(case when c.custid is not null then 1 else 0 end) as exists_flag
from (select '12345' as custid union all
select '56789' union all
select '01234'
) ci left join
customers c
on c.cust = i.custid;

SQL Query Exclude Records

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'
);

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