Sql join solution is possible for below case? - sql

Can we solve below case with joins, I have solved with window functions
Relation: In the tables below, each order in the Orders table, is associated with a given Customer through the cust_id foreign key column that references the ID column in the Customer table.
Question: Find the largest order amount for each salesperson and the associated order number, along with the customer to whom that order belongs and sales person name.
Create Table Salesperson
(
ID int,
name varchar(100),
age float,
salary money
);
Create Table Orders
(
Number int,
order_date datetime,
cust_id int,
salesperson_id int,
Amount money
);
Create Table Customer
(
ID int,
name varchar(100),
city varchar(100),
IndustryType varchar(100)
);
insert into Salesperson values
( 1,'Rohit',25,50000),
( 2,'Pramod',25,50000),
( 3,'Atul',25,50000);
insert into Orders values
( 1,getdate(),101,1,50000),
( 2,getdate(),101,1,500000),
( 3,getdate(),102,1,10000),
( 4,getdate(),101,2,5000),
( 5,getdate(),102,2,700000),
( 6,getdate(),102,2,10000);
insert into Customer values
( 101,'Altu','bhopal','IT'),
( 102,'bltu','bhopal','ITES'),
( 103,'cltu','bhopal','NW');
Solution on with window function:
with CTE_MaxAmount
as
(
select max(amount) over (partition by salesperson_id ) as amount,
dense_rank() over (partition by salesperson_id order by amount) as rowid,
cust_id,
salesperson_id,number
from Orders with(nolock)
)
select ct.amount,
ct.cust_id,
c.name as customername,
s.name as salesman,
ct.salesperson_id,
number as OrderNumbner
from Customer c
join CTE_MaxAmount ct
on (c.id = ct.cust_id)
join Salesperson s
on (s.id = ct.salesperson_id)
where rowid = 1;

I'm breaking with my personal policy not to answer homework questions because the question is an opportunity to show how easily English is translated into SQL. The question is phrased exactly as the query can be built up.
find the largest order amount for each salesperson
select max(Amount) as Amount, salesperson_id from Orders group by salesperson_id
and the associated order number
select o.Number, M.salesperson_id, M.Amount
from Orders as o join (
select max(Amount) as amount, salesperson_id
from Orders group by salesperson_id
) as M
on o.salesperson_id = M.salesperson_id
and o.Amount = M.Amount
along with the customer
select c.name, o.Number, M.salesperson_id, M.Amount
from Orders as o join (
select max(Amount) as amount, salesperson_id
from Orders group by salesperson_id
) as M
on o.salesperson_id = M.salesperson_id
and o.Amount = M.Amount
join Customer as c
on o.cust_id = c.ID
and sales person name
select s.name as 'salesperson',
c.name as 'customer',
o.Number, M.salesperson_id, M.Amount
from Orders as o join (
select max(Amount) as amount, salesperson_id
from Orders group by salesperson_id
) as M
on o.salesperson_id = M.salesperson_id
and o.Amount = M.Amount
join Customer as c
on o.cust_id = c.ID
join Salesperson as s
on o.salesperson_id = s.ID

Related

SQL query optimization on cross production?

I have the following schema
CUSTOMER (CID INTEGER NOT NULL, NAME VARCHAR(30), ADDRESS VARCHAR(50))
PRODUCT (PID INTEGER NOT NULL, NAME VARCHAR(50), PRICE DECIMAL(10,2))
SALE (SID BIGINT NOT NULL, STATUS VARCHAR(10), CID INTEGER, TOTALPRICE DECIMAL(30,2))
PRODUCTSALE (SID BIGINT NOT NULL, PID INTEGER NOT NULL, UNITS INTEGER, SUBTOTAL DECIMAL(30,2))
I am currently have a statement like this:
SELECT
P.NAME, COUNT(DISTINCT C.CID) AS NUM_CUSTOMERS
FROM
CUSTOMER AS C, PRODUCT AS P, PRODUCTSALE AS PS, SALE AS S
WHERE
C.CID = S.CID
AND S.SID = PS.SID
AND PS.PID = P.PID
GROUP BY
P.NAME
ORDER BY
NUM_CUSTOMERS DESC
I think it's creating a four table(P,S,PS,C) cross product? Can I optimize it by using nature join on four of them? What are the other way of optimize this statement?
Start with the biggest table and filter down.
SELECT p.NAME, COUNT(DISTINCT c.CID) AS NUM_CUSTOMERS
FROM ProductSale ps
INNER JOIN Product p ON ps.PID=p.PID
INNER JOIN Sale s ON ps.SID=s.SID
INNER JOIN Customer c ON s.CID=c.CID
GROUP BY p.Name
ORDER BY NUM_CUSTOMERS DESC

Oracle query for customers who buy popular products

I have three tables: customer, order and line items. They are set up as follows:
CREATE TABLE cust_account(
cust_id DECIMAL(10) NOT NULL,
first VARCHAR(30),
last VARCHAR(30),
address VARCHAR(50),
PRIMARY KEY (cust_id));
CREATE TABLE orders(
order_num DECIMAL(10) NOT NULL,
cust_id DECIMAL(10) NOT NULL,
order_date DATE,
PRIMARY KEY (order_num));
CREATE TABLE line_it(
order_id DECIMAL(10) NOT NULL,
line_id DECIMAL(10) NOT NULL,
item_num DECIMAL(10) NOT NULL,
PRIMARY KEY (order_id, line_id),
FOREIGN KEY (item_id) REFERENCES products);
I need to write a query that selects customers, their names and addresses who have purchased items that have been bought by 3 or more people. I have the following query:
SELECT cust_account.cust_id, cust_account.first, cust_account.last, cust_account.address
FROM cust_account
INNER JOIN orders ON cust_account.cust_id = orders.cust_id
INNER JOIN line_it ON orders.order_id = line_it.order_id
GROUP BY cust_account.cust_id, cust_account.last
HAVING COUNT(line_it.item_num) = (
SELECT COUNT (DISTINCT order_num > 3)
FROM line_it
);
Do I even need to make it a subquery? I am a bit lost. Appreciate any help, thanks.
Start with "items bought by 3 or more people". You can get these by doing:
select li.item_id
from line_item li join
order_info oi
on li.order_id = oi.order_id
group by li.item_id
having count(distinct oi.customer_id) >= 3;
Now you want customers in this set. Hmmmm:
select distinct ca.*
from customer_account ca join
orderinfo oi
on ca.customer_id = oi.customer_id join
line_item li
on li.order_id = oi.order_id
where li.item_id in (select li.item_id
from line_item li join
order_info oi
on li.order_id = oi.order_id
group by li.item_id
having count(distinct oi.customer_id) >= 3
);
You can also express this with window functions:
select distinct ca.*
from (select ca.*, count(distinct customer_id) over (partition by li.item_id) as num_customers_on_item
from customer_account ca join
orderinfo oi
on ca.customer_id = oi.customer_id join
line_item li
on li.order_id = oi.order_id
) ca
where num_customers_on_item >= 3;
You can use the following query
SELECT distinct customer_account.* FROM line_item, order_info ,customer_account where item_id in (
--Selecting only item purchased 3 or more
SELECT item_id FROM line_item group by item_id having count(1) >=3
)
and line_item.order_id = order_info.order_id
and customer_account.customer_id = order_info.customer_id
;

How can I select lastest purchase price before a selling date?

I have 5 tables in my database, products, purchase_orders, invoice, invoice_details, and product_prices and their schema are like below.
Table: products
id
trade_name
Table: purchase_orders
id
product_id
created
Table: invoices
id
created
Table invoice_details
id
invoice_id
product_id
price_id
Table product_prices
id
retail_price
effective_from
effective_to
I think that I need to somehow join or check created on purchase_orders to created on invoices. So, I started with getting drug id, invoice date.
select d.id as drug_id
, i.created as invoice_created
, dp.retail_price
from drugs d
inner join invoice_details id
on d.id = id.drug_id
inner join invoices i
on i.id = id.invoice_id
inner join drug_prices dp
on dp.id = id.price_id
The next step is to match created on invoice that I have to created on purchase_orders which I haven't figured it out.
inner join (
select drug_id
, purchase_price
, ISNULL(created, CONVERT(DATETIME, '2015-10-07 01:37:12.370')) as created
from purchase_orders po
) as prepared_po
on prepared_po.created <= i.created
How can I get the lasted purchase price for each item that I sold?
Here's a simplified version of the logic you need (I renamed your columns so that it's easier to see which are which without doing all the intermediary joins you've already written yourself):
;With CTE as (select a.productID, a.saledate, b.price, b.purchasedate
, row_number() over (partition by a.productID, a.saledate
order by b.purchasedate desc) RN
from sales a
left join purchases b
on a.productID = b.productID and a.saledate >= b.purchasedate
)
Select productID, saledate, price, purchasedate
from CTE where RN = 1
Basically, you join to get all purchase records up to the sale date, then use row_number to find the latest one.
http://sqlfiddle.com/#!6/a0f68/2/0

SQL: How can I retrieve total amount from joined tables?

My tables
CREATE TABLE Customers (
id SERIAL PRIMARY KEY,
firstname VARCHAR(50),
lastname VARCHAR(50)
);
CREATE TABLE Payments (
id SERIAL PRIMARY KEY,
amount INT,
customer_id INT,
CONSTRAINT fk_CustomerPayment FOREIGN KEY (customer_id) REFERENCES Customers (id)
);
I want to get total payment amount for all customers. Here is my try:
SELECT SUM(p.amount)
FROM Customers c
JOIN Payments p
ON c.id = p.customer_id
GROUP BY p
select sum(p.amount) as total
from
customers c
inner join
payments p on c.id = p.customer_id
If there can be null values in payments.customer_id the join condition will exclude them.
Or cheaper without the join:
select sum(amount) as total
from payments
where customer_id is not null
remove group by from query..
SELECT SUM(p.amount)
FROM Customers c
JOIN Payments p
ON c.id = p.customer_id

Sum of all values except the first

I have the following three tables:
Customers:
Cust_ID,
Cust_Name
Products:
Prod_ID,
Prod_Price
Orders:
Order_ID,
Cust_ID,
Prod_ID,
Quantity,
Order_Date
How do I display each costumer and how much they spent excluding their very first purchase?
[A] - I can get the total by multiplying Products.Prod_Price and Orders.Quantity, then GROUP by Cust_ID
[B] - I also can get the first purchase by using TOP 1 on Order_Date for each customer.
But I couldnt figure out how to produce [A]-[B] in one query.
Any help will be greatly appreciated.
For SQL-Server 2005, 2008 and 2008R2:
; WITH cte AS
( SELECT
c.Cust_ID, c.Cust_Name,
Amount = o.Quantity * p.Prod_Price,
Rn = ROW_NUMBER() OVER (PARTITION BY c.Cust_ID
ORDER BY o.Order_Date)
FROM
Customers AS c
JOIN
Orders AS o ON o.Cust_ID = c.Cust_ID
JOIN
Products AS p ON p.Prod_ID = o.Prod_ID
)
SELECT
Cust_ID, Cust_Name,
AmountSpent = SUM(Amount)
FROM
cte
WHERE
Rn >= 2
GROUP BY
Cust_ID, Cust_Name ;
For SQL-Server 2012, using the FIRST_VALUE() analytic function:
SELECT DISTINCT
c.Cust_ID, c.Cust_Name,
AmountSpent = SUM(o.Quantity * p.Prod_Price)
OVER (PARTITION BY c.Cust_ID)
- FIRST_VALUE(o.Quantity * p.Prod_Price)
OVER (PARTITION BY c.Cust_ID
ORDER BY o.Order_Date)
FROM
Customers AS c
JOIN
Orders AS o ON o.Cust_ID = c.Cust_ID
JOIN
Products AS p ON p.Prod_ID = o.Prod_ID ;
Another way (that works in 2012 only) using OFFSET FETCH and CROSS APPLY:
SELECT
c.Cust_ID, c.Cust_Name,
AmountSpent = SUM(x.Quantity * x.Prod_Price)
FROM
Customers AS c
CROSS APPLY
( SELECT
o.Quantity, p.Prod_Price
FROM
Orders AS o
JOIN
Products AS p ON p.Prod_ID = o.Prod_ID
WHERE
o.Cust_ID = c.Cust_ID
ORDER BY
o.Order_Date
OFFSET
1 ROW
-- FETCH NEXT -- not needed,
-- 20000000000 ROWS ONLY -- can be removed
) AS x
GROUP BY
c.Cust_ID, c.Cust_Name ;
Tested at SQL-Fiddle
Note that the second solution returns also the customers with only one order (with the Amount as 0) while the other two solutions do not return those customers.
Which version of SQL? If 2012 you might be able to do something interesting with OFFSET 1, but I'd have to ponder much more how that works with grouping.
EDIT: Adding a 2012 specific solution inspired by #ypercube
I wanted to be able to use OFFSET 1 within the WINDOW to it al in one step, but the syntax I want isn't valid:
SUM(o.Quantity * p.Prod_Price) OVER (PARTITION BY c.Cust_ID
ORDER BY o.Order_Date
OFFSET 1)
Instead I can specify the row boxing, but have to filter the result set to the correct set. The query plan is different from #ypercube's, but the both show 50% when run together. They each run twice as as fast as my original answer below.
WITH cte AS (
SELECT c.Cust_ID
,c.Cust_Name
,SUM(o.Quantity * p.Prod_Price) OVER(PARTITION BY c.Cust_ID
ORDER BY o.Order_ID
ROWS BETWEEN 1 FOLLOWING
AND UNBOUNDED FOLLOWING) AmountSpent
,rn = ROW_NUMBER() OVER(PARTITION BY c.Cust_ID ORDER BY o.Order_ID)
FROM Customers AS c
INNER JOIN
Orders AS o ON o.Cust_ID = c.Cust_ID
INNER JOIN
Products AS p ON p.Prod_ID = o.Prod_ID
)
SELECT Cust_ID
,Cust_Name
,ISNULL(AmountSpent ,0) AmountSpent
FROM cte WHERE rn=1
My more general solution is similar to peter.petrov's, but his didn't work "out of the box" on my sample data. That might be an issue with my sample data or not. Differences include use of CTE and a NOT EXISTS with a correlated subquery.
CREATE TABLE Customers (Cust_ID INT, Cust_Name VARCHAR(10))
CREATE TABLE Products (Prod_ID INT, Prod_Price MONEY)
CREATE TABLE Orders (Order_ID INT, Cust_ID INT, Prod_ID INT, Quantity INT, Order_Date DATE)
INSERT INTO Customers SELECT 1 ,'Able'
UNION SELECT 2, 'Bob'
UNION SELECT 3, 'Charlie'
INSERT INTO Products SELECT 1, 10.0
INSERT INTO Orders SELECT 1, 1, 1, 1, GetDate()
UNION SELECT 2, 1, 1, 1, GetDate()
UNION SELECT 3, 1, 1, 1, GetDate()
UNION SELECT 4, 2, 1, 1, GetDate()
UNION SELECT 5, 2, 1, 1, GetDate()
UNION SELECT 6, 3, 1, 1, GetDate()
;WITH CustomersFirstOrder AS (
SELECT Cust_ID
,MIN(Order_ID) Order_ID
FROM Orders
GROUP BY Cust_ID
)
SELECT c.Cust_ID
,c.Cust_Name
,ISNULL(SUM(Quantity * Prod_Price),0) CustomerOrderTotalAfterInitialPurchase
FROM Customers c
LEFT JOIN (
SELECT Cust_ID
,Quantity
,Prod_Price
FROM Orders o
INNER JOIN
Products p ON o.Prod_ID = p.Prod_ID
WHERE NOT EXISTS (SELECT 1 FROM CustomersFirstOrder a WHERE a.Order_ID=o.Order_ID)
) b ON c.Cust_ID = b.Cust_ID
GROUP BY c.Cust_ID
,c.Cust_Name
DROP TABLE Customers
DROP TABLE Products
DROP TABLE Orders
Try this. It should do it.
SELECT c1.cust_name ,
c1.cust_id ,
SUM(p1.Prod_Price)
FROM orders o1
JOIN products p1 ON o1.prod_id = p1.prod_id
JOIN customers c1 ON o1.cust_id = c1.cust_id
LEFT JOIN ( SELECT o2.cust_id ,
MIN(o2.Order_Date) AS Order_Date
FROM orders o2
GROUP BY o2.cust_id
) t ON o1.cust_id = t.cust_id
AND o1.Order_Date = t.Order_Date
WHERE t.Order_Date IS NULL
GROUP BY c1.cust_name ,
c1.cust_id
You have to number orders by Customer and then you can have the amount for the first order and next orders with a CTE and ROW_NUMBER() like this:
; WITH NumberedOrders
AS ( SELECT Customers.Cust_Id ,
Customers.Cust_Name ,
ROW_NUMBER() OVER ( ORDER BY Customers.Cust_id ) AS Order_Number ,
Orders.Order_Date ,
Products.Prod_price * Orders.Quantity AS Amount
FROM Orders
INNER JOIN Customers ON Orders.Cust_Id = Customers.Cust_Id
INNER JOIN Products ON Orders.Prod_Id = Products.Prod_Id
)
SELECT Cust_Id ,
SUM(CASE WHEN Order_Number = 1 THEN Amount
ELSE 0
END) AS A_First_Order ,
SUM(CASE WHEN Order_Number = 1 THEN 0
ELSE Amount
END) AS B_Other_orders ,
SUM(Amount) AS C_All_orders
FROM NumberedOrders
GROUP BY Cust_Id
ORDER BY Cust_Id