Get a count of rows not matching criteria - sql

Given a simple table (order detail/history kind of thing) with a customer and a product:
+--------------------+
| customer | product |
+--------------------+
| Smith | p1 |
| Smith | p3 |
| Jones | p1 |
| Jones | p2 |
| Davis | p3 |
| Davis | p9 |
| Brown | p1 |
| Brown | p2 |
| Brown | p5 |
+----------+---------+
I want to list all customers that have never ordered product p1, i.e. Davis in the above data set.
This is where i started but, of course, it doesnt work and I can't think of where to go next:
select
customer,
count(*) as c
where product='p1'
and c = 0

Try this out:
select customer
from MyTable
where customer not in (select customer from MyTable where Product = 'P1')

Here is one way, using an aggregation query:
select customer
from t
group by customer
having sum(case when product = 'p1' then 1 else 0 end) = 0
This gives you all the customers in the table. If you have a separate list of customers, then you might use:
select customer
from customerTable
where customer not in (select customer from t where product = 'p1')

You can also use this approach
SELECT t.CustomerName
FROM Table t
WHERE NOT EXISTS (SELECT 1
FROM Table t2
WHERE t2.CustomerName = t.CustomerName
AND t2.ProductName = 'p1')

Related

Select buyers and sellers data from customer

I have two tables Customer and Market
Select * from Customer :
customer_id | f_name | l_name
-------------------------------
1 | Sam | Brow
2 | Alex | Fore
3 | Marc | Lor
4 | Fab | Sow
Select * from Market
Orderid | Product | SellerID | BuyerID
-----------------------------------------
5 | Apple | 1 | 2
6 | Juice | 3 | 4
When doing this SELECT to have Sellers and buyers data, I have data of all customers.
SELECT c.f_name, c.l_name ,m.Orderid
FROM Customer c
INNER JOIN Market m ON m.BuyerID = c.customer_id OR m.SellerID = c.customer_id
Instead, I need to separate the data of buyers on their own and sellers on their own. I'd expect something like this :
Orderid | Seller_f_name | Buyer_f_name
----------------------------------------
5 | Sam | Alex
6 | Marc | Fab
Any idea please ?
You need to join the market table twice with customer table -
SELECT Orderid, C1.f_name Seller_f_name, C2.f_name Buyer_f_name
FROM Market M
LEFT JOIN Customer C1 ON M.SellerID = C1.customer_id
LEFT JOIN Customer C2 ON M.BuyerID = C2.customer_id;

SELECT To obtain results from two tables where a value occurs once only in a table column

Sorry I know this should be relatively simple and probably has been answered before, but I've got myself blocked on this and can't find a post here that matches.
I have two tables companies and contacts. A contact can be linked to 0 or 1 company, a company can have 1 or more contacts and MAY have one set as a 'primary contact'
companies(ccyID,ccyname,primconID)
________________________________________________
| ccyID | ccyname | PrimconID |
------------------------------------------------
| aaaaaaa | Company A | NULL |
| bbbbbbb | Company B | NULL |
| ccccccc | Company C | vvvvvvv |
________________________________________________
contacts(conID,firstname,lastname,ccyID)
__________________________________________________
| conID | first | last | companyID |
--------------------------------------------------
| zzzzzzz | Stand | Alone | NULL |
| yyyyyyy | Only | Contact | aaaaaaa |
| xxxxxxx | CompanyB | First | bbbbbbb |
| wwwwwww | CompanyB | Second | bbbbbbb |
| vvvvvvv | CompanyC | Only | ccccccc |
_________________________________________________
I need a SELECT that will return the companyID and contactID when the company has exactly one contact, AND does not have a PrimconID set i.e. for the above data I want returned
conID ccyID
----------------
yyyyyyy aaaaaaa
(The eventual idea is that I'm then going to update the tables to make the solitary contact for companies the primary contact)
Group contacts by company to keep only those with a count of 1 and make sure they are in the set of companies without primary contact:
select companyid, max(conid)
from contacts
group by companyid
having count(*) = 1
and companyid in (select ccyid from companies where primconid is null)
order by companyid;
I would use a join and group by:
select c.ccyid, max(co.conid)
from companies c join
contacts co
on co.companyid = c.ccyid
where c.PrimconID is null
group by c.ccyid
having min(co.conID) = max(co.conID);
As an update statement, you can do:
update c
set PrimconID = co.conid
from companies c join
(select co.companyid, max(co.conid) as conid
from contacts co
group by companyid
having min(co.conid) = max(co.conid)
) co
on co.companyid = c.ccyid
where c.PrimconID is null ;
Use a CTE to first identify all contacts having only a single company, and then join to the company table:
WITH cte AS (
SELECT ccyID, MAX(conID) AS conID
FROM contacts
GROUP BY ccyID
HAVING COUNT(*) = 1
)
SELECT
t.conID,
c.ccyID
FROM companies c
INNER JOIN cte t
ON c.ccyID = t.ccyID
WHERE
c.PrimconID IS NULL;
See the demo below to see the query working.
Demo

SQL List other products bought and count buyers, by product originally purchased

After years of reading answers, it's finally time to ask a question myself.
I have a list of products purchased and unique customer IDs:
+---------+--------+
| Product | Buyer |
+---------+--------+
| Apples | Rod |
| Apples | Jane |
| Apples | Freddy |
| Bananas | Rod |
| Bananas | Jane |
| Bananas | Freddy |
| Bananas | Zippy |
| Pears | Rod |
| Pears | Zippy |
+---------+--------+
I want to produce the following output in Netezza SQL:
+-----------+-------------+------------------------+---------------------+
| Product A | Buyers of A | A Buyers Also Bought B | No of A Buyers of B |
+-----------+-------------+------------------------+---------------------+
| Apples | 3 | Bananas | 3 |
| Apples | 3 | Pears | 1 |
| Bananas | 4 | Apples | 3 |
| Bananas | 4 | Pears | 2 |
| Pears | 2 | Apples | 1 |
| Pears | 2 | Bananas | 2 |
+-----------+-------------+------------------------+---------------------+
..so that I can see, for each product, the total purchasers. Crucially, I also want to see, for each product, of those purchasers, how many bought other products within the same list. Edit: It's important to reiterate that I should not have any buyers appearing in columns for B if they didn't also buy product A.
What's the most efficient way to do this please?
(I'll then work out a percentage of B buying A, but that part's easy).
Thank you!
You can create a summary of counts and then cross join with itself, excluding same matches.
Like this:
SELECT
A.Product,
A.Buyers,
B.Product,
B.Buyers
FROM (
SELECT
Product
count(*) AS Buyers
FROM
ProductBuyers
GROUP BY
) AS A
CROSS JOIN (
SELECT
Product
count(*) AS Buyers
FROM
ProductBuyers
GROUP BY
) AS B
WHERE
A.Product != B.Product
The basic data on purchases in-common is a self-join and group by:
select p1.product, p2.product, count(*) as in_common
from purchases p1 join
purchases p2
on p1.buyer = p2.buyer
group by p1.product, p2.product;
To get the count for one (or the other) is then a join:
select p1.product, p2.product, pp.cnt, count(*) as in_common
from purchases p1 join
purchases p2
on p1.buyer = p2.buyer join
(select p1.product, count(*) as cnt
from purchases
group by p1.product
) pp
on pp.product = p1.product
group by p1.product, p2.product, pp.cnt;
Alternatively, you could use window functions:
select p1.product, p1.cnt, p2.product, count(*) as in_common
from (select p1.*,
count(*) over (partition by p1.product) as cnt
from purchases p1
) p1 join
purchases p2
on p1.buyer = p2.buyer
group by p1.product, p2.product, p1.cnt;
Here is a rextester showing it working.

How to iterate on subgroups in SQL Server 2008?

I have one table looking like
Customer:
| CUSTOMER_ID | CUSTOMER_NAME | BANK_ID |
-----------------------------------------
| 1 | a | b |
| 2 | b1 | c |
| 3 | b1 | d |
| 4 | C | e |
| 5 | a | f |
| 6 | b1 | g |
I have a query that looks for all customer names that are not unique and group them together. It also assigns a row number to the rows in each group.
The output of this query is:
RowNumber|customer_id | customer_name |
1 | 1 | a |
2 | 5 | a |
1 | 2 | b1 |
2 | 3 | b1 |
3 | 6 | b1 |
I want to iterate on all the groups. For each group I want to join the members of the group with rows in a different table.Is there any way to operate on each sub group and apply business logic on the items in each sub group ?
for example: let's assume that for each group I want to leave the first customer if all the customers in this group live in the same place and work at the same place.
I have the following table:
|customer id | address | workplace-name |
|1 | street1 | work1|
|2 | street2 | work1|
|3 | street1 | work2|
|4 | street5 | work7|
|5 | street1 | work1|
|6 | street2 | work1|
You can notice that only the customers in the first group live and work at the same place (customers id: 1,5). If you look at the second group (customers id:2,3,6) - they don't all live and work at the same place.
The result of this query will be: customer id 5 as it's in the same group with customer id 5 and they both live and work in the same place. But customer 5 is the second in this group.
What's the easiest way to do it ?
Try this:
WITH A(Customer_id, Customer_name)
AS(SELECT Customer_id, Customer_name
FROM Customer
WHERE Customer_name IN
(SELECT Customer_name FROM Customer
GROUP BY Customer_name
HAVING COUNT(Customer_name) >1)
)
SELECT RANK() OVER (ORDER BY Customer_id ASC) AS RowNumber
, Customer_id, Customer_Name
FROM A
ORDER BY Customer_name, Customer_id;
Or you can also use JOIN for that
WITH A(Customer_id, Customer_name)
AS (SELECT c.Customer_id, c.Customer_name
FROM Customer c
JOIN
(SELECT Customer_id FROM Customer
WHERE Customer_name IN ( SELECT Customer_name FROM Customer
GROUP BY Customer_name
HAVING COUNT(Customer_name) >1)
) AS c1
ON c.Customer_id = C1.customer_id)
SELECT RANK() OVER (ORDER BY A.customer_id ASC) AS RowNumber
, Customer_id, Customer_Name
FROM A
ORDER BY Customer_name, Customer_id;
See this SQLFiddle
I couldn't find any way to do it in a single query. I've created a cursor that does what I want to do: Iterate on all the rows of one table, apply business logic and insert relevant rows to the output table.

SQL: Ensuring all rows are checked against a set of rows

I have two tables that look like this:
Supplier
SN SNAME Stat City
+----------+-------+----+--------+
| S1 | Smith | 20 | London |
| S2 | Jones | 10 | Paris |
| S3 | Blake | 30 | Paris |
| S4 | Clark | 20 | London |
| S5 | Adams | 30 | Athens |
+----------+-------+----+--------+
Shipment
SN PN QTY
+----+----+-----+
| S1 | P1 | 300 |
| S1 | P2 | 200 |
| S1 | P3 | 400 |
| S1 | P4 | 200 |
| S1 | P5 | 100 |
| S1 | P6 | 100 |
| S2 | P1 | 300 |
| S2 | P2 | 400 |
| S3 | P2 | 200 |
| S4 | P2 | 200 |
| S4 | P4 | 300 |
| S4 | P5 | 400 |
+----+----+-----+
I have the find the following:
List the supplier names (SNAME) for those suppliers who ship at least all those parts (PN) supplied by supplier S2.
In other words, I need to list all those suppliers who ship at least P1 and P2, although obviously my query needs to be focused more on the question at hand.
I'm pretty sure I have to use some form of NOT EXISTS -- perhaps the double not exists, to achieve this. I tried doing a self-join of the Shipment table to itself -- but I can't understand how to get it beyond checking for ANY item that appears in the PN for S2 and instead check to make sure ALL parts for S2 are in the list before including the name in the result.
I used a Common Table Expression to accomplish this:
--CTE will contain records from the Supplier you are looking to check against
WITH desiredSN AS
(
SELECT s.SN, s.PN
FROM Shipment AS s
WHERE s.SN = 'S2'
)
--SELECT will join all other shippers with each record of your Supplier S2 here
SELECT s.SN
FROM Shipment AS s
INNER JOIN desiredSN AS d ON s.PN = d.PN
WHERE s.SN != 'S2'
GROUP BY s.SN
--Having ensures that the count of your old supplier matches
--the number a potential supplier overlaps with
HAVING COUNT(1) =
(
SELECT COUNT(1)
FROM desiredSN AS d
)
If you do not want to use the CTE, you can use subqueries instead:
SELECT s.SN
FROM Shipment AS s
INNER JOIN
(
SELECT s.SN, s.PN
FROM Shipment AS s
WHERE s.SN = 'S2'
)
AS d ON s.PN = d.PN
WHERE s.SN != 'S2'
GROUP BY s.SN
HAVING COUNT(1) =
(
SELECT COUNT(1)
FROM Shipment AS s
WHERE s.SN = 'S2'
)
Could you not use a nested select statement to refine your results?
For example:
SELECT SNAME FROM Supplier
INNER JOIN Shipment ON Supplier.SN = Shipment.SN
WHERE Shipment.PN IN (SELECT PN FROM Shipment WHERE SN = S2)
This way, the parts (PN) are restricted to just those that are sold by S2.
edited changed the statement to
Shipment.PN IN (SELECT PN FROM Shipment WHERE SN = S2)
Now, even if they sold one of everything, they'll only be returned if they sold at least what S2 sells.