left join two tables on a non-unique column in right table - sql

I have two tables in sql server and i wanna select and join some data from these table.the first tables have some customer like:
---------------
customer id
Dave 1
Tom 2
---------------
and second table i table of purchases that includes list of last purchases with cost and which customer bought that Product:
------------------
product date customer id
PC 1-1-2000 1
phone 2-3-2000 2
laptop 3-1-2000 1
------------------
i wanna select first table (customers info) with last date of their purchases!
i tried left join but that doesn't give me last purchases becuase customer id is not unique in second table! how can i do this function with SQL server query? Regards

If you just want the max date, use aggregation. I would recommend a left join for customers who have made no purchases:
select c.customer, c.id, max(p.date)
from customers c left join
purchases p
on c.id = p.customer_id
group by c.customer, c.id;

Use the not exists clause for the win!
select c.customer, p.*
from Customer as c
inner join Purchase as p
on p.customer_id = c.id
where not exists (
select 1
from Purchase as p2
where p2.customer_id = p.customer_id
and p2.date > p.date
)

I think you can use inner join and group by
select table1.customer, table1.id, table.max(date)
from table1
inner join table2 on table1.id = table2.id
group by table1.customer, table1.id

Related

selecting entries that are referenced in one of multiple tables

I have four tables:
company
id | name
order
number | company_id
quote
number | company_id
invoice
number | company_id
I want to get the list of companies that are referenced in any of the three (i.e., exclude companies that aren't in any).
I wrote this query:
select c.id, c.name from company c
left outer join order o on o.company_id = c.id
left outer join quote q on q.company_id = c.id
left outer join invoice i on i.company_id = c.id
where o.number is not null or q.number is not null or i.number is not null
But the order, quote, and invoice tables are quite large. So the join causes the query to run forever (one join is O(m*n), three joins is O(m*n*p*q)).
I COULD run this query basically three times with only one join on each query (O(m*n) + O(m*p) + O(m*q)), but I'm hoping for a more optimal solution.
This is for Oracle
Join to a subquery which does a union of the three order tables:
SELECT
c.id, c.name
FROM company c
INNER JOIN
(
SELECT company_id FROM order
UNION
SELECT company_id FROM quote
UNION
SELECT company_id FROM invoice
) t
ON c1.id = t.company_id
The union queries should automatically remove duplicate company_id, such that only distinct companies which are present in any of the three tables should be in the result set of that subquery. The inner join then filters off any companies which were not present at all.

SQL: How to pull particular orders based on order details?

I have four tables namely customers, orders, orderDetails and Products.
Customer table
cId cName
1 James
2 Adam
3 Ed
Order table
oId cId
1 1
2 2
3 3
OrderDetails table
oId odId pId Quantity
1 1 1 50
1 2 2 45
2 3 2 52
3 4 1 44
Products table
pId PName
1 Apple
2 Orange
I want the list of customers who have never ordered Oranges. I am able to pull records of customers whose order details don't have oranges. But in one of the case, James has ordered both apples and oranges. So, the query should not pull James. I can do this with a larger query. But I want this with a smaller query where something I'm missing.
SQL
SELECT c.cId, c.cName, p.PName, od.Quantity FROM customers c
LEFT JOIN orders o ON c.cId = o.cId
LEFT JOIN orderDetails od ON o.oId = od.oId
JOIN products p ON od.pId = p.pId
WHERE od.pId != 2
I would do this using not exists:
with has_oranges as (
select o.*
from orders o join
orderlines ol
on o.oid = ol.oid
where ol.pid = 2
)
select c.*
from customers c
where not exists (select 1
from has_oranges ho
where ho.cid = c.cid
);
If you want customer information, I don't see what oid has to do with anything.
Notes:
The CTE determines who actually has oranges.
You don't need the products table, because you are using the pid.
Use NOT EXISTS
SELECT *
FROM Customers c
WHERE NOT EXISTS (
SELECT 1 FROM orders o
JOIN orderDetails od ON o.oId = od.oId
JOIN products p ON od.pId = p.pId
WHERE p.pName = 'oranges' AND c.cId = o.cId
)
You want all customers that have never ordered oranges. So select all customer IDs that ordered oranges and only show customers that are not in this data set.
select *
from customers c
where cid not in
(
select cid
from orderdetails
where pid = (select pid from products where pname = 'Orange'
);
select * from CustomerTbl where Id in (select t1.Id from CustomerTbl t1
left join OrderTbl t2 on t1.Id = t2.CustomerId
left join OrderDetailTbl t3 on t3.OrderId = t2.Id
left join ProductTbl t4 on t4.Id = t3.ProductId
where t4.Id != 2)
This will return Customers who not ordered Oranges.
This is the sqlfiddle link : http://sqlfiddle.com/#!6/c908d/6
SQL Server 2008 introduced the EXCEPT and INTERSECT keywords for doing this sort of thing. I tend to find that the queries are clearer than when using CTEs.
Microsoft Documentation
select c.cId
from Customer c
except
select o.cId
from Orders o
join OrderDetail od on o.oId = od.oId
and od.pId = 2
cId
-----------
3
You can add the name to the result set by joining to the Customer table in the second half of the query:
select c.cId, c.cName
from Customer c
except
select o.cId, c.cName
from Orders o
join OrderDetail od on o.oId = od.oId
join Customer c on c.cId = o.cId
and od.pId = 2
cId cName
----------- --------------------
3 Ed
We have to eliminate users who took orange. So in the below query i have used sub query
Select C.Cname,OH.oid,PM.Pname,OD.Quantity from Customers C
inner join OrderHeader OH ON C.cid=OH.Cid
inner join OrderDetails OD on oh.oid=od.oid
inner join ProductMast PM on PM.pid=OD.pid where OH.oid not in (select oid
from OrderDetails where pid = 2)

Join SQL Statements optimization

I have 2 Tables:
Customer
ID
Customer_ID
Name
Sir_Name
Phone
Email
and
Table Invoice
Manager_Name
Manaer_First_Name
Customer_ID1
Customer_ID2
Customer_ID3
There is only one Customer.Customer_ID for each Customer or a Customer has no Customer_ID
In Invoice.Customer_ID1 i have the same Customer_ID.Customer_ID several times.
I Like to get all Records in Customer Table Join Invoice Table - check if the Customer_ID = Customer_ID1 if not check in Customer_ID = Customer_ID2 Or Customer_ID = Customer_ID2
If customer_ID is found in one of rows stop the search.
Probably the best way to write the query is:
select . . .
from customer c join
invoice i
on c.customer_id = coalesce(i.customer_id1, i.customer_id2, i.customer_id3);
This should be able to take advantage of an index on customer(customer_id). If this is not efficient, then another alternative is left join:
select . . ., coalesce(c1.col1, c2.col1, c3.col1) as col1, . . .
from invoice i left join
customer c1
on c1.customer_id = i.customer_id1 left join
customer c2
on c2.customer_id = i.customer_id2 left join
customer c3
on c3.customer_id = i.customer_id3;
The left join can take advantage of an index on customer(customer_id). You need to use coalesce() in the select to choose the field from the right table.
select
*
from [Table Invoice] A
JOIN [Customer] B
ON B.Customer_ID = A.Customer_ID1 OR (B.Customer_ID <> A.Customer_ID1 AND B.Customer_ID = A.Customer_ID2) OR (B.Customer_ID = A.Customer_ID3 AND B.Customer_ID <> A.Customer_ID2 AND B.Customer_ID <> A.Customer_ID1)
this would return you all the Invoices for all of the Customers. In case you need Invoices just for one customer - add
WHERE B.Customer_ID = #YourCustomerID
statement. If you need only one, first invoice, add 'TOP 1' to select statement:
SELECT TOP 1
Could a inner join on or clause
select Customer.*, Invocie.*
from Customer
inner join Invoice on ( Customer.Customer_ID = Invoce.Customer_ID1
OR Customer.Customer_ID = Invoce.Customer_ID2
OR Customer.Customer_ID = Invoce.Customer_ID3)
This is how I understand your request: You want all customers that have at least one entry in the invoice table. But per customer you want the "best" invoice record only; with ID1 match better than ID2 match and ID2 match better than ID3 match.
So join the tables to get all matches and then rank your matches with row_number giving the best matching record #1. Then only keep those rows ranked #1.
select *
from
(
select
c.*,
i.*,
row_number() over
(
partition by c.customer_id order by
case c.customer_id
when i.customer_id1 then 1
when i.customer_id2 then 2
when i.customer_id3 then 3
end
) as rn
from customer c
join invoice i on c.customer_id in (i.customer_id1, i.customer_id2, i.customer_id3)
)
where rn = 1;

SQL Query or Table Error?

So Im trying to find the total amount spent on Cuts and Products by each Customer
I don't know if my Query is Wrong or my entire Database Schema any ideas?
My Query
`Select First_Name, SUM(B.Cost), SUM(C.Cost)
FROM bookings A, cuts B, products C, customers D
Where A.Customer_ID= D.Customer_ID
AND A.Cut_ID = B.Cut_ID
AND A.Product_ID= C.Product_ID;`
My Database
`Table: bookings
Booking_N0, Customer_ID, Cut_ID, Product_ID, TimeTaken`
`Table: customers
Customre_ID, First_Name, Sex`
`Table: products
Product_ID, Products, Cost`
`Table: cuts
Cut_ID, Cut, Cost`
You should GROUP BY to SUM by each customer :
Select D.First_Name
, SUM(B.Cost)
, SUM(C.Cost)
FROM bookings A LEFT JOIN cuts B ON A.Cut_ID = B.Cut_ID
JOIN products C ON A.Product_ID = C.Product_ID
JOIN customers D ON A.Customer_ID = D.Customer_ID
GROUP BY D.First_Name;
Also, look forward using explicit join notation (FROM table1 t1 JOIN table2 t2 ON t1.field1 = t2.field2) instead of implicit join notation (FROM table1 t1, table2 t2 WHERE t1.field1 = t2.field2), because it is has more intuitive view (tables are listed near conditions on which they are joined).
Start using recommended JOIN / ON syntax for joining instead of using WHERE clause . You also need a GROUP BY clause
Select First_Name, SUM(B.Cost), SUM(C.Cost)
FROM bookings A
INNER JOIN cuts B
ON A.Cut_ID = B.Cut_ID
INNER JOIN products C
ON A.Product_ID= C.Product_ID
INNER JOIN customers D
ON A.Customer_ID= D.Customer_ID
GROUP BY First_Name
If you use aggregate function like SUM you have to add a group by clause
in your case:
...
AND A.Product_ID= C.Product_ID
GROUP BY First_Name

Fetch data from more than one tables using Group By

I am using three tables in a PostgreSql database as:
Customer(Id, Name, City),
Product(Id, Name, Price),
Orders(Customer_Id, Product_Id, Date)
and I want to execute a query to get from them "the customers that have have ordered at least two different products alnong with the products". The query I write is:
select c.*, p.*
from customer c
join orders o on o.customer_id = c.id
join product p on p.id = o.product_id
group by (c.id)
having count(distinct o.product_id)>=2
It throws the error:
"column "p.id" must appear in the GROUP BY clause or be used in an aggregate function
LINE 1: select c.*, p.*".
However if I remove the the p.* from select statement (assuming that I one does not want the products, only the customers), it runs fine. How can I get the products as well?
Update: Having ordered two or more products, a customer must appear on the output as many times as its product he has ordered. I want as output a table with 5 columns:
Cust ID | Cust Name | Cust City | Prod ID | Prod Name | Prod Price
Is it possible in SQL given that group by should be used? Shoul it be used on more than one columns on different tables?
Try this out :
SELECT distinct c.* ,p.*
FROM Customer c
JOIN
(SELECT o.customer_id cid
FROM Product P
JOIN Orders o
ON p.id= o.product_id
GROUP BY o.customer_id
HAVING COUNT(distinct o.product_id)>=2) cp
ON c.id =cp.cid
JOIN Orders o
on c.id=o.customer_id
JOIN Product p
ON o.product_id =p.id
I hope it solves your problem.
I think you can use following query for this question -
SELECT C1.*, p1.*
FROM Customer C1
JOIN Orders O1 ON O1.Customer_Id = C1.Id
JOIN Product P1 ON P1.Id = O1.Product_Id
WHERE C1.Id IN (SELECT c.Id
FROM Customer c
JOIN Orders o ON o.Customer_Id = c.Id
GROUP BY (c.Id)
HAVING COUNT(DISTINCT o.Product_Id) >= 2)