T-SQL Returning records (customers) where value not present - sql

Ok, this seems like it should be an easy one so I will try to simplify what I am trying to do.
Say I have this select statement:
SELECT a.customer, b.fruit
FROM Customer a
INNER JOIN Order b ON a.customer = b.Customer
Which would return the following:
Customer Fruit
-------------------------
Jane Apple
Jane Banana
Bob Apple
Bob Orange
Bob Grape
John Apple
John Banana
Ann Tangerine
Ann Orange
Ann Banana
What I would like to pull from this result set is a list of customers who have never ordered, say, a 'Tangerine', resulting in the following list:
Customer
--------
Jane
Bob
John
How would one go about do this? Thanks in advance!

There are many ways to do this:
SELECT *
FROM Customer c
WHERE NOT EXISTS(SELECT 1 FROM Order
WHERE customer = c.customer
And Fruit = 'Tangerine');
Using NOT IN:
SELECT *
FROM Customer c
WHERE c.Customer NOT IN (SELECT Customer FROM Order
WHERE Customer IS NOT NULL
And Fruit = 'Tangerine');

Here is one method using left join:
SELECT c.customer
FROM Customer c LEFT JOIN
Order o
ON c.customer = o.Customer AND o.fruit = 'Tangerine'
WHERE o.Customer IS NULL;

Existing answers are missing distinct customers
SELECT distinct c.customer
FROM Customer c
WHERE NOT EXISTS ( SELECT 1
FROM Customer T
WHERE T.customer = c.customer
And T.Fruit = 'Tangerine'
);
And I don't like tables and column the same
That would be more and Order table

Related

SQL Server: finding duplicates between two parameters in different tables

I am trying to find duplicates in SQL Server where customers with the same forename, surname, and mobile number match. The thing is they are in different tables.
custid forename surname dateofbirth
-----------------------------------
1 David John 16-09-1985
2 David Jon 16-09-1985
3 Sarah Smith 10-08-2015
4 Peter Proca 11-06-2011
5 Peter Broca 11-06-2011
addid custid line1
-------------------------
1 1 0504135846
2 2 0504135846
3 3 0506523145
4 4 0503698521
5 5 0503698521
I am currently able to find duplicates by forename and surname, but if I want to find based on mobile numbers how can I bring it in?
select c.*
from
(select
c.*,
count(*) over (partition by left(surname, 3)) as cnt
from
customers c) c
order by
surname;
Use join:
select c.*
from (select c.*, t2.line1
count(*) over (partition by surname, forename, line1) as cnt
from customers c join
table2 t2
on t2.custid = c.custid
) c
order by surname;
Here you go, just JOIN on the table. Using HAVING might simplify your query as well
SELECT COUNT(*), c.forename, c.surname, mn.line1
FROM customers c
INNER JOIN mobilenumber mn ON c.custid=mn.custid
GROUP BY c.forename, c.surname, mn.line1
HAVING COUNT(*)>1
Also, you might need to LEFT JOIN if there is a chance that some records wont be in the mobilenumbers table.

Completing a table with rows not existing

I have a table like this:
buyer product quantity
tom skirt 2
anna skirt 3
tom jeans 5
The distinct(product) = skirt and jeans
I want a table that inserts another column with quantity = 0 when the <buyer, product> tuple does not exist for all possible products.
So the result would be:
buyer product quantity
tom skirt 2
anna skirt 3
tom jeans 5
anna jeans 0
It does not look very complicated but I don't know how to do it.
UPDATE
I have found one extra complication.
My product is actually defined by two fields: class and product. Product can be null and I need not to lose the information quantity when the product field is null (now it is happening with the cross join).
So if I have this:
buyer class product quantity
tom clothes skirt 2
anna clothes skirt 3
tom clothes jeans 5
jim shoes NULL 7
I would need:
buyer class product quantity
tom clothes skirt 2
anna clothes skirt 3
tom clothes jeans 5
anna clothes jeans 0
jim shoes NULL 7
jim clothes skirt 0
jim clothes jeans 0
tom shoes NULL 0
anna shoes NULL 0
Thank you all, I know I am complicating things!
You can use a cross join to generate all possible combinations of buyers and products. Then use a left join (or not exists) to filter out the ones already in the table:
insert into table(buyer, product, quantity)
select b.buyer, p.product, 0
from (select distinct buyer from table) b cross join
(select distinct product p from table) p left join
table t
on t.buyer = b.buyer and t.product = p.product
where t.buyer is null;
EDIT:
If you want a query that returns all the rows, then you would use something quite similar:
select b.buyer, p.product, coalesce(t.qty, 0) as qty
from (select distinct buyer from table) b cross join
(select distinct product p from table) p left join
table t
on t.buyer = b.buyer and t.product = p.product;
EDIT II:
If you have NULL values for buyer and/or product, then use NULL safe comparisons:
select b.buyer, p.product, coalesce(t.qty, 0) as qty
from (select distinct buyer from table) b cross join
(select distinct product p from table) p left join
table t
on t.buyer is not distinct from b.buyer and
t.product is not distinct from p.product;
(As a minor side note: I really do not like the use of distinct in this construct. Why did Postgres (ANSI ?) give it such a complicated name?)
The solution of #Gordon is almost full, I edit like this:
declare #tb table (buyer varchar(150), product varchar(150), quantity int)
insert into #tb
values('tom','skirt',2),
('anna','skirt',3),
('tom','jeans',5)
select *
from #tb a
left join( select
distinct(product)
from #tb) b on a.product = a.product
select b.buyer, p.p, isnull(t.quantity,0)
from (select distinct buyer from #tb) b cross join
(select distinct product p from #tb) p left join
#tb t
on t.buyer = b.buyer and t.product = p.p
--where t.buyer is null
Try it.

Specific Ordering in SQL

I have a SQL Server 2008 database. In this database, I have a result set that looks like the following:
ID Name Department LastOrderDate
-- ---- ---------- -------------
1 Golf Balls Sports 01/01/2015
2 Compact Disc Electronics 02/01/2015
3 Tires Automotive 01/15/2015
4 T-Shirt Clothing 01/10/2015
5 DVD Electronics 01/07/2015
6 Tennis Balls Sports 01/09/2015
7 Sweatshirt Clothing 01/04/2015
...
For some reason, my users want to get the results ordered by department, then last order date. However, not by department name. Instead, the departments will be in a specific order. For example, they want to see the results ordered by Electronics, Automotive, Sports, then Clothing. To throw another kink in works, I cannot update the table schema.
Is there a way to do this with a SQL Query? If so, how? Currently, I'm stuck at
SELECT *
FROM
vOrders o
ORDER BY
o.LastOrderDate
Thank you!
You can use case expression ;
order by case when department = 'Electronics' then 1
when department = 'Automotive' then 2
when department = 'Sports' then 3
when department = 'Clothing' then 4
else 5 end
create a table for the departments that has the name (or better id) of the department and the display order. then join to that table and order by the display order column.
alternatively you can do a order by case:
ORDER BY CASE WHEN Department = 'Electronics' THEN 1
WHEN Department = 'Automotive' THEN 2
...
END
(that is not recommended for larger tables)
Here solution with CTE
with c (iOrder, dept)
as (
Select 1, 'Electronics'
Union
Select 2, 'Automotive'
Union
Select 3, 'Sports'
Union
Select 4, 'Clothing'
)
Select * from c
SELECT o.*
FROM
vOrders o join c
on c.dept = o.Department
ORDER BY
c.iOrder

show result from one table

good day, i have these 3 tables...i.e.;
customer table
cust_id cust_name sales_employee
1 abc 1
2 cde 1
3 efg 2
transaction table
order_num cust_id sales_employee
1001 1 1
1002 2 2
sales_employee table
sales_employee employee name
1 john doe
2 jane doe
how can i show the employee name on both customer table and transaction table?
notice how the sales_employee can change per transaction, it does not necessarily have to be the same per customer.
please help.
To select customers with sales person name
select
C.*, E.employee_name
from
Customers as C
inner join Sales_Employees as E on E.sales_employee = C.sales_employee
To select transactions with customer name and salesperson name (at the point in time of the transaction)
select
T.*,
E.employee_name as Trans_employee,
C.cust_name,
EC.employee_name as Cust_employee
from
Transactions as T
inner join Sales_Employees as E on E.sales_employee = T.sales_employee
inner join Customers as C on C.cust_id= T.cust_id
inner join Sales_Employees as EC on EC.sales_employee = C.sales_employee
This code is meant to guide you, you will need to adjust it to match your table and field names.

Cross-multiplying table

I have this SQL code and I want to show the sum of each item on its charge slip and on their receipt:
select item_description, sum(receipt_qty) as Samp1, sum(chargeSlip_qty) as Samp2
from Items inner join Receipt_Detail on (Receipt_Detail.item_number =
Items.item_number)
inner join ChargeSlip_Detail on (ChargeSlip_Detail.item_number =
Items.item_number)
group by item_description
It produces this output:
Acetazolamide 2681 1730
Ascorbic Acid 1512 651
Paracetamol 1370 742
Silk 576 952
But it should be:
Acetazolamide 383 173
Ascorbic Acid 216 93
Paracetamol 274 106
Silk 96 238
What's wrong with my code?
Since you are joining tables, you might have a one-to-many relationship that is causing the problem when you then get the sum(). So you can use subqueries to get the result. This will get the sum() for the receipt and chargeslip for each item_number and then you join that back to your items table to get the final result:
select i.item_description,
r.Samp1,
c.Samp2
from Items i
inner join
(
select sum(receipt_qty) Samp1,
item_number
from Receipt_Detail
group by item_number
) r
on r.item_number = i.item_number
inner join
(
select sum(chargeSlip_qty) Samp2,
item_number
from ChargeSlip_Detail
group by item_number
) c
on c.item_number = i.item_number
Do the GROUP BYs first, per Item_Number, so you don't multiply out rows from Receipt_Detail and ChargeSlip_Detail. That is, you generate the SUM values per Item_Number before JOINing back to Items
select
I.item_description,
R.Samp1,
C.Samp2
from
Items I
inner join
(SELECT item_number, sum(receipt_qty) as Samp1
FROM Receipt_Detail
GROUP BY item_number
) R
on (R.item_number = I.item_number)
inner join
(SELECT item_number, sum(chargeSlip_qty) as Samp2
FROM ChargeSlip_Detail
GROUP BY item_number
) C
on (C.item_number = I.item_number)
A left join returns rows from the left table, and for each row in the left table, all matching rows in the right table.
So for example:
create table Customers (name varchar(50));
insert Customers values
('Tim'),
('John'),
('Spike');
create table Orders (customer_name varchar(50), product varchar(50));
insert Orders values (
('Tim', 'Guitar'),
('John', 'Drums'),
('John', 'Trumpet');
create table Addresses (customer_name varchar(50), address varchar(50));
insert Addresses values (
('Tim', 'Penny Lane 1'),
('John', 'Abbey Road 1'),
('John', 'Abbey Road 2');
Then if you run:
select c.name
, count(o.product) as Products
, count(a.address) as Addresses
from Customers c
left join Orders o on o.customer_name = c.name
left join Addresses a on a.customer_name = c.name
group by name
You get:
name Products Addresses
Tim 1 1
John 4 4
Spike 0 0
But John doesn't have 4 products!
If you run without the group by, you can see why the counts are off:
select *
from Customers c
left join Orders o on o.customer_name = c.name
left join Addresses a on a.customer_name = c.name
You get:
name customer_name product customer_name address
Tim Tim Guitar Tim Penny Lane 1
John John Drums John Abbey Road 1
John John Drums John Abbey Road 2
John John Trumpet John Abbey Road 1
John John Trumpet John Abbey Road 2
Spike NULL NULL NULL NULL
As you can see, the joins end up repeating each other. For each product, the list of addresses is repeated. That gives you the wrong counts. To solve this problem, use one of the excellent other answers:
select c.name
, o.order_count
, a.address_count
from Customers c
left join
(
select customer_name
, count(*) as order_count
from Orders
group by
customer_name
) o
on o.customer_name = c.name
left join
(
select customer_name
, count(*) as address_count
from Addresses
group by
customer_name
) a
on a.customer_name = c.name
The subqueries ensure only one row is joined per customer. The result is much better:
name order_count address_count
Tim 1 1
John 2 2
Spike NULL NULL