SQL complex select - sql

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)
)
);

Related

SQLite conditional SELECT statement - multiple criteria but single statement

I have 2 tables, one is a list of customers, the other a list of historic and future delivery dates for these customers.
Convenience code to create example table:
CREATE TABLE Customers (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT
);
INSERT INTO Customers (name) VALUES ('David');
INSERT INTO Customers (name) VALUES ('John');
INSERT INTO Customers (name) VALUES ('Anne');
CREATE TABLE Orders (
id INTEGER,
item TEXT,
delivery_date TEXT
);
INSERT INTO Orders VALUES ( 1, 'gopro' , '2016-04-05');
INSERT INTO Orders VALUES ( 1,'car', '2015-12-30');
INSERT INTO Orders VALUES ( 1,'watch', '2015-10-20');
INSERT INTO Orders VALUES ( 2, 'laptop', '2016-04-15');
INSERT INTO Orders VALUES ( 3, 'car', '2016-01-15');
INSERT INTO Orders VALUES ( 3,'cup', '2015-11-30');
I need to create a SQL statement that shows all customers and their most recent and next delivery date.
(Something like - SELECT * from Customers with recent and next item and date FROM Orders.)
Expected Result Table
Name, Most Recent Item, Recent Date, Next Item, Next Date
David, car, 2015-12-30, gopro, 2016-04-05
John, NULL, NULL, laptop, 2016-04-15
Anne, car, 2016-01-15, NULL, NULL
But the closest I can get with a single statement is:
SELECT
(SELECT o.item from Orders WHERE delivery_date < date('now') ORDER BY delivery_date DESC LIMIT 1) as Last_Item_Ordered,
(SELECT o.delivery_date from Orders WHERE delivery_date < date('now') ORDER BY delivery_date DESC LIMIT 1) as Last_Delivery_Date,
(SELECT o.item from Orders WHERE delivery_date >= date('now') ORDER BY delivery_date ASC LIMIT 1) as Next_Item_Ordered,
(SELECT o.delivery_date from Orders WHERE delivery_date >= date('now') ORDER BY delivery_date ASC LIMIT 1) as Next_Delivery_Date,
c.*
FROM (Orders o INNER JOIN Customers c ON c.id = o.id)
GROUP BY c.name
ORDER BY c.id;
and the Actual results are as this:
Last_Item_Ordered Last_Delivery_Date Next_Item_Ordered Next_Delivery_Date id name
watch 2015-10-20 watch 2015-10-20 1 David
laptop 2016-04-15 laptop 2016-04-15 2 John
cup 2015-11-30 cup 2015-11-30 3 Anne
Todays date is 21st February 2016, hence some values are expected to be NULL for the above customers future results.
If anyone can help, be greatly appreciated. thanks
You can use the following query:
SELECT (SELECT o.item
from Orders AS o
WHERE o.id = c.id AND delivery_date < date('now')
ORDER BY delivery_date DESC LIMIT 1) as Last_Item_Ordered,
(SELECT o.delivery_date
from Orders AS o
WHERE o.id = c.id AND delivery_date < date('now')
ORDER BY delivery_date DESC LIMIT 1) as Last_Delivery_Date,
(SELECT o.item
from Orders AS o
WHERE o.id = c.id AND delivery_date >= date('now')
ORDER BY delivery_date ASC LIMIT 1) as Next_Item_Ordered,
(SELECT o.delivery_date
from Orders AS o
WHERE o.id = c.id AND delivery_date >= date('now')
ORDER BY delivery_date ASC LIMIT 1) as Next_Delivery_Date,
c.*
FROM Customers c
ORDER BY c.id;
Demo here

Selecting sum and running balance for last 18 months with generate_series

I have this working query, but I need to add all months to my result, no matter if the items sold during that month:
select * from (
select
to_char(max(change_date), 'YYYY-MON')::varchar(8) as yyyymmm,
max(change_date) as yearmonth,
sum(vic.sold_qty / item_size.qty)::numeric(18,2) as sold_qty, -- sold monthly
sum(sum(on_hand)) OVER (PARTITION BY vic.item_id order by year,month) as on_hand --running balance
from (((view_item_change vic
left join item on vic.item_id = item.item_id)
left join item_size on item_size.item_id = vic.item_id and item_size.name = item.sell_size)
left join item_plu on vic.item_id = item_plu.item_id and item_plu.seq_num = 0)
where 1 = 1 -- cannot limit date here as its used to show running balance.
and vic.change_date < current_date - date_part('day',current_date)::integer --show only till end of last month
and item.item_id = (select item_id from item_plu where number = '51515')
group by vic.item_id, year, month
) as t
where yearmonth > current_date - date_part('day',current_date)::integer - 540 -- 18 months
which gives me something like this:
"2013-JUN";"2013-06-29";0.00;7.0000
"2013-JUL";"2013-07-22";0.00;6.0000
"2013-AUG";"2013-08-28";2.00;4.0000
"2013-SEP";"2013-09-02";0.00;4.0000
"2013-OCT";"2013-10-28";0.00;4.0000
"2013-NOV";"2013-11-15";0.00;4.0000
"2013-DEC";"2013-12-16";0.00;6.0000
"2014-FEB";"2014-02-10";1.00;5.0000
"2014-APR";"2014-04-09";0.00;5.0000
But I also want to show the months 2014-JAN and 2014-MAR so that my chart will be better time scaled.
I know how to do the generate_series(start_date, end_date, '1 month') intervals, but I can't quite see how I can join this series to the result set above.
I accepted first answer, but after 2 weeks of testing, found a problem. The left join to the series and then adding a coalesce to show 0's and not nulls for the empty months causes a problem wit the running balance.
Resulting query:
SELECT yyyymmm, yyyymmm,
coalesce(sold_qty,0) sold_qty, coalesce(on_hand,0) on_hand
FROM (
SELECT date_trunc('month', month_series)::date as yyyymmm
FROM generate_series(current_date - date_part('day',current_date)::integer - 540
,current_date- date_part('day',current_date)::integer
, interval '1 month') month_series
) month_series
LEFT JOIN (
select * from (
select
date_trunc('month', max(change_date))::date as yyyymmm,
max(change_date) as yearmonth,
sum(vic.sold_qty / item_size.qty)::numeric(18,2) as sold_qty,
sum(sum(on_hand)) OVER (PARTITION BY vic.item_id order by year,month) as on_hand
from (((view_item_change vic
left join item on vic.item_id = item.item_id)
left join item_size on item_size.item_id = vic.item_id and item_size.name = item.sell_size)
left join item_plu on vic.item_id = item_plu.item_id and item_plu.seq_num = 0)
where 1 = 1 -- cannot limit date here as its used to show running balance.
--vic.change_date >= current_date - date_part('day',current_date)::integer - 730 -- only get results for last
--show only till end of last month
and vic.change_date <= current_date - date_part('day',current_date)::integer
and item.item_id = (select item_id from item_plu where number = '19M7077')
group by vic.item_id, year, month
) as a
where yyyymmm > current_date - date_part('day',current_date)::integer - 540 -- 18 months
) q USING (yyyymmm)
order by 1
Results I get:
"2013-07-01";"2013-07-01";0;0
"2013-08-01";"2013-08-01";0;0
"2013-09-01";"2013-09-01";1.00;53.0000
"2013-10-01";"2013-10-01";0;0
"2013-11-01";"2013-11-01";0;0
"2013-12-01";"2013-12-01";0.00;53.0000
"2014-01-01";"2014-01-01";0.00;52.0000
"2014-02-01";"2014-02-01";0;0
"2014-03-01";"2014-03-01";0;0
"2014-04-01";"2014-04-01";0;0
But I want:
"2013-07-01";"2013-07-01";0;53.0000
"2013-08-01";"2013-08-01";0;53.0000
"2013-09-01";"2013-09-01";1.00;53.0000
"2013-10-01";"2013-10-01";0;53.0000
"2013-11-01";"2013-11-01";0;0;53.0000
"2013-12-01";"2013-12-01";0.00;53.0000
"2014-01-01";"2014-01-01";0.00;52.0000
"2014-02-01";"2014-02-01";0;0;52.0000
"2014-03-01";"2014-03-01";0;0;52.0000
"2014-04-01";"2014-04-01";0;0;52.0000
Table definitions
CREATE TABLE item
(
item_id character(22) NOT NULL,
version integer NOT NULL,
created_by character varying(16) NOT NULL,
updated_by character varying(16),
inactive_by character varying(16),
created_on date NOT NULL,
updated_on date,
inactive_on date,
external_id numeric(14,0),
description character varying(40) NOT NULL,
dept_id character(22),
subdept_id character(22),
sell_size character varying(8) NOT NULL,
purch_size character varying(8) NOT NULL
);
CREATE TABLE item_change
(
item_id character(22) NOT NULL,
size_name character varying(8) NOT NULL,
store_id character(22) NOT NULL,
change_date date NOT NULL,
on_hand numeric(18,4) NOT NULL, -- sum column / item_id = total on_hand
total_cost numeric(18,4) NOT NULL,
on_order numeric(18,4) NOT NULL,
sold_qty numeric(18,4) NOT NULL,
sold_cost numeric(18,4) NOT NULL,
sold_price numeric(18,4) NOT NULL,
recv_qty numeric(18,4) NOT NULL,
recv_cost numeric(18,4) NOT NULL,
adj_qty numeric(18,4) NOT NULL,
adj_cost numeric(18,4) NOT NULL
);
CREATE TABLE item_size
(
item_id character(22) NOT NULL,
seq_num integer NOT NULL,
name character varying(8) NOT NULL,
qty numeric(18,4) NOT NULL,
weight numeric(18,4) NOT NULL,
CONSTRAINT item_size_pkey PRIMARY KEY (item_id, seq_num),
CONSTRAINT item_size_c0 FOREIGN KEY (item_id)
REFERENCES item (item_id) MATCH SIMPLE
ON UPDATE NO ACTION ON DELETE CASCADE,
CONSTRAINT item_size_c1 UNIQUE (item_id, name)
);
CREATE TABLE item_plu
(
item_id character(22) NOT NULL,
seq_num integer NOT NULL,
"number" character varying(18) NOT NULL,
size_name character varying(8)
);
CREATE OR REPLACE VIEW view_item_change AS
SELECT date_part('year'::text, item_change.change_date) AS year,
date_part('month'::text, item_change.change_date) AS month,
date_part('week'::text, item_change.change_date) AS week,
date_part('quarter'::text, item_change.change_date) AS quarter,
date_part('dow'::text, item_change.change_date) AS dow,
item_change.item_id,
item_change.size_name,
item_change.store_id,
item_change.change_date,
item_change.on_hand,
item_change.total_cost,
item_change.on_order,
item_change.sold_qty,
item_change.sold_cost,
item_change.sold_price,
item_change.recv_qty,
item_change.recv_cost,
item_change.adj_qty,
item_change.adj_cost
FROM item_change;
There is only a single row per month because I also do a group by year, month. As you see, the view has the year, quarter, month, week, dow columns for easier reporting.
To get working data for this, if required, I can do it, but it would require manually creating it. I have simplified the tables and left out all constraints and some columns.
Basic solution
Generate a complete list of months and LEFT JOIN the rest to it:
SELECT *
FROM (
SELECT to_char(m, 'YYYY-MON') AS yyyymmm
FROM generate_series(<start_date>, <end_date>, interval '1 month') m
) m
LEFT JOIN ( <your query here> ) q USING (yyyymmm);
Related answers with more explanation:
Join a count query on a generate_series in postgres and also retrieve Null-values as "0"
Best way to count records by arbitrary time intervals in Rails+Postgres
Advanced solution for your case
Your query is more complicated than I first understood. You need the running sum over all rows of the selected item, then you want to trim rows older than a minimum date, and fill in missing months with the pre-calculated sum of the previous month.
I achieve this now with LEFT JOIN LATERAL.
SELECT COALESCE(m.yearmonth, c.yearmonth)::date, sold_qty, on_hand
FROM (
SELECT yearmonth
, COALESCE(sold_qty, 0) AS sold_qty
, sum(on_hand_mon) OVER (ORDER BY yearmonth) AS on_hand
, lead(yearmonth) OVER (ORDER BY yearmonth)
- interval '1 month' AS nextmonth
FROM (
SELECT date_trunc('month', c.change_date) AS yearmonth
, sum(c.sold_qty / s.qty)::numeric(18,2) AS sold_qty
, sum(c.on_hand) AS on_hand_mon
FROM item_change c
LEFT JOIN item i USING (item_id)
LEFT JOIN item_size s ON s.item_id = i.item_id AND s.name = i.sell_size
LEFT JOIN item_plu p ON p.item_id = i.item_id AND p.seq_num = 0
WHERE c.change_date < date_trunc('month', now()) - interval '1 day'
AND c.item_id = (SELECT item_id FROM item_plu WHERE number = '51515')
GROUP BY 1
) sub
) c
LEFT JOIN LATERAL generate_series(c.yearmonth
, c.nextmonth
, interval '1 month') m(yearmonth) ON TRUE
WHERE c.yearmonth > date_trunc('year', now()) - interval '540 days'
ORDER BY COALESCE(m.yearmonth, c.yearmonth);
SQL Fiddle with a minimum test case.
Major points:
I removed your VIEW from the query completely. Much cost for no gain.
Since you select a single item_id, you don't need to GROUP BY item_id or PARTITION BY item_id.
Use short table aliases and make all reference unambiguous - especially when posting in a public forum.
Parentheses in your joins were just noise. Joins are executed left-to-right anyway by default.
Simplified date bounds (since I operate with timestamps):
date_trunc('year', current_date) - interval '540 days'
date_trunc('month', current_date) - interval '1 day'
equivalent, but simpler & faster than:
current_date - date_part('day',current_date)::integer - 540
current_date - date_part('day',current_date)::integer
I now fill in missing months after all calculations with generate_series() calls per row.
It must be LEFT JOIN LATERAL ... ON TRUE, not the short form of a JOIN LATERAL to catch the corner case of the last row. Detailed explanation:
Find most common elements in array with a group by
Important side notes:
character(22) is a terrible data type for a primary key (or any column). Details:
Any downsides of using data type "text" for storing strings?
Ideally this would be an int or bigint column, or possibly a UUID.
Also, storing money amounts as money type or integer(representing Cents) performs much better overall.
In the long run, performance is bound to deteriorate, since you have to include all rows from the very beginning in your calculation. You should cut off old rows and materialize the balance of on_hold on a yearly basis or something.

How to get the discount number of customers in prior period?

I have a requirement where I supposed to roll customer data in the prior period of 365 days.
Table:
CREATE TABLE orders (
persistent_key_str character varying,
ord_id character varying(50),
ord_submitted_date date,
item_sku_id character varying(50),
item_extended_actual_price_amt numeric(18,2)
);
Sample data:
INSERT INTO orders VALUES
('01120736182','ORD6266073' ,'2010-12-08','100856-01',39.90),
('01120736182','ORD33997609' ,'2011-11-23','100265-01',49.99),
('01120736182','ORD33997609' ,'2011-11-23','200020-01',29.99),
('01120736182','ORD33997609' ,'2011-11-23','100817-01',44.99),
('01120736182','ORD89267964' ,'2012-12-05','200251-01',79.99),
('01120736182','ORD89267964' ,'2012-12-05','200269-01',59.99),
('01011679971','ORD89332495' ,'2012-12-05','200102-01',169.99),
('01120736182','ORD89267964' ,'2012-12-05','100907-01',89.99),
('01120736182','ORD89267964' ,'2012-12-05','200840-01',129.99),
('01120736182','ORD125155068','2013-07-27','201443-01',199.99),
('01120736182','ORD167230815','2014-06-05','200141-01',59.99),
('01011679971','ORD174927624','2014-08-16','201395-01',89.99),
('01000217334','ORD92524479' ,'2012-12-20','200021-01',29.99),
('01000217334','ORD95698491' ,'2013-01-08','200021-01',19.99),
('01000217334','ORD90683621' ,'2012-12-12','200021-01',29.990),
('01000217334','ORD92524479' ,'2012-12-20','200560-01',29.99),
('01000217334','ORD145035525','2013-12-09','200972-01',49.99),
('01000217334','ORD145035525','2013-12-09','100436-01',39.99),
('01000217334','ORD90683374' ,'2012-12-12','200284-01',39.99),
('01000217334','ORD139437285','2013-11-07','201794-01',134.99),
('01000827006','W02238550001','2010-06-11','HL 101077',349.000),
('01000827006','W01738200001','2009-12-10','EL 100310 BLK',119.96),
('01000954259','P00444170001','2009-12-03','PC 100455 BRN',389.99),
('01002319116','W02242430001','2010-06-12','TR 100966',35.99),
('01002319116','W02242430002','2010-06-12','EL 100985',99.99),
('01002319116','P00532470001','2010-05-04','HO 100482',49.99);
Using the query below I am trying to get the number of distinct customers by order_submitted_date:
select
g.order_date as "Ordered",
count(distinct o.persistent_key_str) as "customers"
from
generate_series(
(select min(ord_submitted_date) from orders),
(select max(ord_submitted_date) from orders),
'1 day'
) g (order_date)
left join
orders o on o.ord_submitted_date between g.order_date - interval '364 days'
and g.order_date
WHERE extract(year from ord_submitted_date) <= 2009
group by 1
order by 1
This is the output I expected.
Ordered Customers
2009-12-03 1
2009-12-10 1
When I execute the query above I get incorrect results.
How can I make this right?
To get your expected output ("the number of distinct customers") - only days with actual orders 2009:
SELECT ord_submitted_date, count(DISTINCT persistent_key_str) AS customers
FROM orders
WHERE ord_submitted_date >= '2009-1-1'
AND ord_submitted_date < '2010-1-1'
GROUP BY 1
ORDER BY 1;
Formulate the WHERE conditions this way to make the query sargable, and input easy.
If you want one row per day (from the earliest entry up to the latest in orders) - within 2009:
SELECT ord_submitted_date AS ordered
, count(DISTINCT o.persistent_key_str) AS customers
FROM (SELECT generate_series(min(ord_submitted_date) -- single query ...
, max(ord_submitted_date) -- ... to get min / max
, '1d')::date FROM orders) g (ord_submitted_date)
LEFT join orders o USING (ord_submitted_date)
WHERE ord_submitted_date >= '2009-1-1'
AND ord_submitted_date < '2010-1-1'
GROUP BY 1
ORDER BY 1;
SQL Fiddle.
Distinct customers per year
SELECT extract(year from ord_submitted_date) AS year
, count(DISTINCT persistent_key_str) AS customers
FROM orders
GROUP BY 1
ORDER BY 1;
SQL Fiddle.

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 to identify to correct row

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