How to identify to correct row - sql

I have two tables, namely Price List (Table A) and Order Record (Table B)
Table A
SKU Offer Date Amt
AAA 20120115 22
AAA 20120223 24
AAA 20120331 25
AAA 20120520 28
Table B
Customer SKU Order Date
A001 AAA 20120201
B001 AAA 20120410
C001 AAA 20120531
I have to retrieve the correct pricing for each customer based on the order date. The expected output should be like this:-
Customer SKU Order Date Amt
A001 AAA 20120201 22
B001 AAA 20120410 25
C001 AAA 20120531 28
Thanks.

A left join (or NOT EXISTS subquery) can be used to ensure that the join between the two tables uses the "most recent" row from the prices table that is dated on or before the order date. I assume that's the relationship between the tables that you want to achieve:
Setup:
create table Prices (
SKU char(3) not null,
OfferDate date not null,
Amt int not null
)
go
insert into Prices (SKU, OfferDate, Amt) values
('AAA','20120115', 22),
('AAA','20120223', 24),
('AAA','20120331', 25),
('AAA','20120520', 28)
go
create table Orders (
Customer char(4) not null,
SKU char(3) not null,
OrderDate date not null
)
go
insert into Orders (Customer, SKU, OrderDate) values
('A001','AAA','20120201'),
('B001','AAA','20120410'),
('C001','AAA','20120531')
go
Query:
select
o.*, /* TODO - Explicit columns */
p.Amt
from
Orders o
inner join
Prices p
on
o.SKU = p.SKU and
o.OrderDate >= p.OfferDate
left join
Prices p_later
on
o.SKU = p_later.SKU and
o.OrderDate >= p_later.OfferDate and
p_later.OfferDate > p.OfferDate
where
p_later.SKU is null

Next time, do put up what u have tried....
anyways, here is your answer! try...
Select X.Customer , X.SKU , X.OrderDate , Y.Amt from B as X INNER JOIN A as Y ON X.Order Date= Y. Offer Date
good luck...

SELECT o.Customer, o.SKU, o.[Order Date],
(SELECT TOP 1 l.Amt
FROM PriceList l
WHERE l.[Offer Date] <= o.[Order Date] AND o.SKU = l.SKU
ORDER BY l.[Offer Date] DESC) AS Amount
FROM
Orders o
Some things may differ based on database support

Related

On same row: last purchase quantity + date, and total quantity in stock (several stock places) - SQL server

I am trying to get the following result in SQL server:
From the purchase order rows, last purchase quantity + date from all item codes in the order rows table and from the warehouse table amount in stock for the item codes I get from the rows table.
Order rows:
ORDER_DATE ITEM_CODE QTY
2019-03-01 A 5
2019-03-02 A 3
2019-03-05 A 4
2019-03-03 B 3
2019-03-04 B 10
Warehouse:
ITEM_CODE INSTOCK STOCKPLACE
A 10 VV
A 3 LP
A 8 XV
B 5 VV
B 15 LP
Wanted result (Latest order date, latest order qty and total in stock):
ORDER_DATE ITEM_CODE QTY INSTOCK
2019-03-05 A 4 21
2019-03-04 B 10 20
I have tried some queries but only failed. I have a steep learning curve ahead of me :) Thanks in advance for all the help!
Here is one method:
select o.*, wh.*
from (select wh.item_code, sum(wh.instock) as instock
from warehouse wh
group by wh.item_code
) wh outer apply
(select top (1) o.*
from orders o
where o.item_code = wh.item_code
order by o.order_date desc
) o;
You can use row_number() with apply :
select t.*, wh.instock
from (select o.*, row_number () over (partition by item_code order by o.order_date desc) as seq
from Order o
) t cross apply
( select sum(wh.instock) as instock
from warehouse wh
where wh.item_code = t.item_code
) wh
where t.seq = 1;
Your Orders aren't identified with a unique ID, and therefore if multiple Orders were to coincide on the same date, you have no way of telling which is the most recent order on that day.
Anyway, assuming that the database you posted is correct and an Order date + Item Code combines to form a unique key, you could use grouping and some CTE to get the desired output as follows.
;WITH MostRecentOrders (ITEM_CODE, ORDER_DATE)
AS (
SELECT
O.ITEM_CODE
, MAX(O.ORDER_DATE) AS ORDER_DATE
FROM
#Order O
GROUP BY ITEM_CODE
)
SELECT
O.ORDER_DATE
, O.ITEM_CODE
, O.QTY
, SUM(WH.INSTOCK) AS INSTOCK
FROM
#Warehouse WH
INNER JOIN #Order O ON O.ITEM_CODE = WH.ITEM_CODE
INNER JOIN MostRecentOrders MRO ON MRO.ITEM_CODE = O.ITEM_CODE
AND MRO.ORDER_DATE = O.ORDER_DATE
GROUP BY
O.ORDER_DATE
, O.ITEM_CODE
, O.QTY
ORDER BY O.ITEM_CODE

SQL complex select

I have 2 SQL tables.
for Books:
create table product
(
product_id number primary key
name varchar2(128 byte) not null,
rrp number not null,
available_from date not null
);
and for Orders:
create table orders
(
order_id number primary key,
product_id number not null,
quantity number not null,
order_price number not null,
dispatch_date date not null,
foreign key (product_id) references product(product_id)
);
How to write a query to find books that have sold fewer than 10 copies in the last year, excluding books that have been available for less than 1 month?
I expect this would be smth like:
SELECT name FROM products WHERE
today - products.available_from >= 30
AND
10 > (SELECT COUNT(product_id) FROM orders WHERE orders.product_id = products.product_id AND today - products.dispatch_date <= 365)
On SQL Server this will be something like...............
Select B.ProductId, B.Name, SUM(O.quantity) as OrderedAmount
FROM Books B
INNER JOIN Orders O ON O.product_id = B.ProductId
WHERE
B.available_from <= DateAdd( month, -1, GETDATE()) --Exclude 1 month books
AND O.OrderDate BETWEEN DateAdd( year, -1, GETDATE()) AND GETDATE() --Ordered in the last year
GROUP BY
B.ProductId, B.Name
HAVING SUM(O.quantity) < 10 --Sum of quantity ordered less than 10
On Oracle:
SELECT p.name
FROM product p
LEFT JOIN orders o ON p.product_id=o.product_id -- makes sure books with no orders are also included
WHERE
(p.available_from < add_months(sysdate,-1) -- available for 1 month or more
AND
(o.DISPATCH_DATE IS NULL -- books with no rows in "orders"
OR
p.PRODUCT_ID IN -- books that have sold fewer than 10 copies in last year
(SELECT product_id
FROM ORDERS
WHERE dispatch_date > add_months(sysdate,-12)
GROUP BY product_id
HAVING SUM(quantity) < 10)
)
);

SQL - Join multiple table

I have four tables Customer, Sales, Invoice, and Receipt.
Customer
ID Name
1 A
Sales
ID Name
1 Ben
Invoice
ID Amt Date CustomerID SalesID
1 12 1/9/2014 1 1
2 10 1/10/2014 1 1
3 20 2/10/2014 1 1
4 30 3/10/2014 1 1
Receipt
ID Amt Date CustomerID SalesID
1 10 4/10/2014 1 1
I wish to join those 4 table as below with sum up the Ammount(s), but I am stuck as to how I can achieve my desired
RESULT
CustomerID SalesID Inv_Amt Rep_Amt Month
1 1 12 0 9
1 1 60 10 10
I've been stuck for days. But, I have no idea how to proceed.
You can get month wise total receipt and invoice amount by grouping and sub query like below :
SELECT Invoice.CustomerID [CustomerID],
Invoice.SalesID [SalesID],
SUM(Invoice.Amt) [Invoice_Amt],
ISNULL((SELECT SUM(Amt)
FROM Receipt
WHERE CustomerID = Invoice.CustomerID
AND SalesID = Invoice.SalesID
AND Month(Date) = Month(Invoice.Date)),0) [Receipt_Amt],
MONTH(Invoice.Date) Month
FROM Invoice
GROUP BY Invoice.CustomerID, Invoice.SalesID, MONTH(Invoice.Date)
SQL Fiddle Demo1
Warning : Here data will come for all months which are in Invoice table. If for any month, there is no any data in invoice table then no result will come for that month even for receipt also.
UPDATE:
To get result from all months of invoice and receipt table, you need to get it using CTE as like below :
;with CTE as
(
SELECT Invoice.CustomerID, Invoice.SalesID, MONTH(Invoice.Date) MonthNo FROM Invoice
UNION
SELECT Receipt.CustomerID, Receipt.SalesID, MONTH(Receipt.Date) MonthNo FROM Receipt
)
SELECT CTE.CustomerID [CustomerID],
CTE.SalesID [SalesID],
ISNULL((SELECT SUM(Amt)
FROM Invoice
WHERE CustomerID = CTE.CustomerID
AND SalesID = CTE.SalesID
AND Month(Date) = CTE.MonthNo),0) [Invoice_Amt],
ISNULL((SELECT SUM(Amt)
FROM Receipt
WHERE CustomerID = CTE.CustomerID
AND SalesID = CTE.SalesID
AND Month(Date) = CTE.MonthNo),0) [Receipt_Amt],
MonthNo
FROM CTE
SQL Fiddle Demo2
Frankly, since you're just selecting customer and sales IDs (as opposed to names), you don't even need to joint all four tables:
SELECT i.CustomerID,
i.SalesID,
SUM(i.Amt) AS InvAmt,
SUM(r.Amt) AS RepAmt,
MONTH(i.`Date`) AS `Month`
FROM Invoice i
JOIN Receipt r ON i.CustomerID = r.CustomerID AND
i.SalesID = r.SalesID AND
MONTH(i.`Date`) = MONTH(r.`Date`)
GROUP BY i.CustomerID, i.SalesID, MONTH(i.`Date`) AS `Month`
Looks like a homework, but ...
SELECT
Customer.ID AS CustomerID,
Sales.ID AS SalesID,
Invoice.Amt AS Inv_Amt,
Receipt.Amt AS Rep_Amt,
MONTH(Invoice.Date) AS Month
FROM
Customer
INNER JOIN Receipt ON Customer.ID = Receipt.CustomerID
INNER JOIN Invoice ON Customer.ID = Invoice.CustomerID
INNER JOIN Sales ON Sales.ID = Receipt.SalesID
I didn't bother checking the result is what you expect, but the query should be something like that. You can play with the join conditions in order to get the result.

How to do a group by without having to pass all the columns from the select?

I have the following select, whose goal is to select all customers who had no sales since the day X, and also bringing the date of the last sale and the number of the sale:
select s.customerId, s.saleId, max (s.date) from sales s
group by s.customerId, s.saleId
having max(s.date) <= '05-16-2013'
This way it brings me the following:
19 | 300 | 26/09/2005
19 | 356 | 29/09/2005
27 | 842 | 10/05/2012
In another words, the first 2 lines are from the same customer (id 19), I wish to get only one record for each client, which would be the record with the max date, in the case, the second record from this list.
By that logic, I should take off s.saleId from the "group by" clause, but if I do, of course, I get the error:
Invalid expression in the select list (not contained in either an
aggregate function or the GROUP BY clause)
I'm using Firebird 1.5
How can I do this?
GROUP BY summarizes data by aggregating a group of rows, returning one row per group. You're using the aggregate function max(), which will return the maximum value from one column for a group of rows.
Let's look at some data. I renamed the column you called "date".
create table sales (
customerId integer not null,
saleId integer not null,
saledate date not null
);
insert into sales values
(1, 10, '2013-05-13'),
(1, 11, '2013-05-14'),
(1, 12, '2013-05-14'),
(1, 13, '2013-05-17'),
(2, 20, '2013-05-11'),
(2, 21, '2013-05-16'),
(2, 31, '2013-05-17'),
(2, 32, '2013-03-01'),
(3, 33, '2013-05-14'),
(3, 35, '2013-05-14');
You said
In another words, the first 2 lines are from the same customer(id 19), i wish he'd get only one record for each client, which would be the record with the max date, in the case, the second record from this list.
select s.customerId, max (s.saledate)
from sales s
where s.saledate <= '2013-05-16'
group by s.customerId
order by customerId;
customerId max
--
1 2013-05-14
2 2013-05-16
3 2013-05-14
What does that table mean? It means that the latest date on or before May 16 on which customer "1" bought something was May 14; the latest date on or before May 16 on which customer "2" bought something was May 16. If you use this derived table in joins, it will return predictable results with consistent meaning.
Now let's look at a slightly different query. MySQL permits this syntax, and returns the result set below.
select s.customerId, s.saleId, max(s.saledate) max_sale
from sales s
where s.saledate <= '2013-05-16'
group by s.customerId
order by customerId;
customerId saleId max_sale
--
1 10 2013-05-14
2 20 2013-05-16
3 33 2013-05-14
The sale with ID "10" didn't happen on May 14; it happened on May 13. This query has produced a falsehood. Joining this derived table with the table of sales transactions will compound the error.
That's why Firebird correctly raises an error. The solution is to drop saleId from the SELECT clause.
Now, having said all that, you can find the customers who have had no sales since May 16 like this.
select distinct customerId from sales
where customerID not in
(select customerId
from sales
where saledate >= '2013-05-16')
And you can get the right customerId and the "right" saleId like this. (I say "right" saleId, because there could be more than one on the day in question. I just chose the max.)
select sales.customerId, sales.saledate, max(saleId)
from sales
inner join (select customerId, max(saledate) max_date
from sales
where saledate < '2013-05-16'
group by customerId) max_dates
on sales.customerId = max_dates.customerId
and sales.saledate = max_dates.max_date
inner join (select distinct customerId
from sales
where customerID not in
(select customerId
from sales
where saledate >= '2013-05-16')) no_sales
on sales.customerId = no_sales.customerId
group by sales.customerId, sales.saledate
Personally, I find common table expressions make it easier for me to read SQL statements like that without getting lost in the SELECTs.
with no_sales as (
select distinct customerId
from sales
where customerID not in
(select customerId
from sales
where saledate >= '2013-05-16')
),
max_dates as (
select customerId, max(saledate) max_date
from sales
where saledate < '2013-05-16'
group by customerId
)
select sales.customerId, sales.saledate, max(saleId)
from sales
inner join max_dates
on sales.customerId = max_dates.customerId
and sales.saledate = max_dates.max_date
inner join no_sales
on sales.customerId = no_sales.customerId
group by sales.customerId, sales.saledate
then you can use following query ..
EDIT changes made after comment by likeitlikeit for only one row per CustomerID even when we will have one case where we have multiple saleID for customer with certain condition -
select x.customerID, max(x.saleID), max(x.x_date) from (
select s.customerId, s.saleId, max (s.date) x_date from sales s
group by s.customerId, s.saleId
having max(s.date) <= '05-16-2013'
and max(s.date) = ( select max(s1.date)
from sales s1
where s1.customeId = s.customerId))x
group by x.customerID
You can Try Maxing the s.saleId (Max(s.saleId)) and removing it from the Group By clause
A subquery should do the job, I can't test it right now but it seems ok:
SELECT s.customerId, s.saleId, subq.maxdate
FROM sales AS s
INNER JOIN (SELECT customerId, MAX(date) AS maxdate
FROM sales
GROUP BY customerId, saleId
HAVING MAX(s.date) <= '05-16-2013'
) AS subq
ON s.customerId = subq.customerId AND s.date = subq.maxdate

How find Customers who Bought Product A and D > 6 months apart?

I need advice from more advanced SQL experts on this.
I am being asked to create a report showing customers who bought Product 105 and who then bought Product 312 more than 6 months later.
For example, I have the following Orders table:
RecID CustID ProdID InvoiceDate
1 20 105 01-01-2009
2 20 312 01-04-2009
3 20 300 04-20-2009
4 31 105 07-10-2005
5 45 105 10-03-2007
6 45 300 11-10-2007
7 45 312 08-25-2008
I need a report that looks at this table and comes back with:
CustID ElapsedDays
45 327
Do I need to use a cursor and iterate record by record, comparing dates as I go?
If so, what would the cursor procedure look like? I have not worked with cursors, although I have done years of procedural programming.
Thanks!
You've got some good answers above; a self-join is the way to go. I want to suggest to you how best to think about a problem like this. What if you had the purchases of Product A and D in different tables? Not that you should store the data that way, but you should think about the data that way. If you did, you could join, say, product_a_purchases to product_d_purchases on customer ID and compare the dates. So, for purposes of your query, that's what you need to produce. Not an actual on-disk table that is product_a_purchases, but a table of records from your purchases table that includes only Product A purchases, and the same for Product D. That's where the self-join comes about.
select A.CustID, ElapsedDays = datediff(d, A.InvoiceDate, B.InvoiceDate)
from Orders A
inner join Orders B on B.CustID = A.CustID
and B.ProdID = 312
-- more than 6 months ago
and B.InvoiceDate > dateadd(m,6,A.InvoiceDate)
where A.ProdID = 105
The above query is a simple interpretation of your requirement, where ANY purchase of A(105) and D(312) occurred 6 months apart. If the customer purchased
A in Jan,
A in March,
A in July, and then purchased
D in September
it would return 2 rows for the customer (Jan and March), since both of those are followed by a D purchase more than 6 months later.
The following query instead finds all cases where the LAST A purchase is 6 months or more before the FIRST D purchase.
select A.CustID, ElapsedDays = datediff(d, A.InvoiceDate, B.InvoiceDate)
from (
select CustID, Max(InvoiceDate) InvoiceDate
from Orders
where ProdID = 105
group by CustID) A
inner join (
select CustID, Min(InvoiceDate) InvoiceDate
from Orders
where ProdID = 312
group by CustID) B on B.CustID = A.CustID
-- more than 6 months ago
and B.InvoiceDate > dateadd(m,6,A.InvoiceDate)
And if for the same scenario above, you don't want to see this customer because the A (Jul) and D (Sep) purchases are not 6 months apart, you can exclude them from the first query using an EXISTS filter.
select A.CustID, ElapsedDays = datediff(d, A.InvoiceDate, B.InvoiceDate)
from Orders A
inner join Orders B on B.CustID = A.CustID
and B.ProdID = 312
-- more than 6 months ago
and B.InvoiceDate > dateadd(m,6,A.InvoiceDate)
where A.ProdID = 105
AND NOT EXISTS (
SELECT *
FROM Orders C
WHERE C.CustID=A.CustID
AND C.InvoiceDate > A.InvoiceDate
and C.InvoiceDate < B.InvoiceDate
and C.ProdID in (105,312))
You can do this with a self-join:
select a.custid, DATEDIFF(dd, a.invoicedate, b.invoicedate)
from #t a
inner join #t b
on a.custid = b.custid
and a.prodid = 105
and b.prodid = 312
where DATEDIFF(dd, a.invoicedate, b.invoicedate) > 180
The first use of #t (aliased a) is for the first product and the second use of #t (aliased b) is for the second product. Here's the script I used to test it:
create table #t (
recid int,
custid int,
prodid int,
invoicedate date)
insert into #t select 1, 20, 105, '1/1/2009'
insert into #t select 2, 20, 312,'1/4/2009'
insert into #t select 3, 20, 300,'4/20/2009'
insert into #t select 4, 31, 105,'7/10/2005'
insert into #t select 5, 45, 105,'10/3/2007'
insert into #t select 6, 45, 300,'11/10/2007'
insert into #t select 7, 45, 312,'8/25/2008'
select a.custid, DATEDIFF(dd, a.invoicedate, b.invoicedate)
from #t a
join #t b
on a.custid = b.custid
and a.prodid = 105
and b.prodid = 312
where DATEDIFF(dd, a.invoicedate, b.invoicedate) > 180
drop table #t
Probably something like this would work:
select CustID, datediff(day, O1.InvoiceDate, O2.InvoiceDate) as ElapsedDays
from Orders O1
inner join Orders O2
on O1.CustId = O2.CustId
and dateadd(month, 6, O1.InvoiceDate) <= O2.InvoiceDate
where
O1.ProdId = 105
and O2.ProdId = 312