I would like to differentiate between full refund vs partial refund. There is no flag in SQL database thus this request. Little explanation about data.There could be full refund or partial refund to the customers. Full refund is refund of full amount paid by customer and partial is partial amount refund.
I have 2 tables , Order Header and Order Line (Header table have CustomerId,OrderId, OrderDate, OrderGuid, OrderType,Amount,Reasoncode)
(line Tabe have CustomerId,OrderId, OrderDate, OrderGuid,ItemNo,Quantity,LineAmount).
When customer orders it will be like something below in Header level:
OrderId OrderDate CustomerID OrderGuid OrderType Amount ReasonCode
FN1 2018-07-15 1 FN1-1 Sales 50
FN2 2018-07-16 2 FN2-1 Sales 100
Same at Line level:
OrderId OrderDate CustomerID OrderGuid ItemNo LineAmount Qty
FN1 2018-07-15 1 FN1-1 123-0 20 1
FN1 2018-07-15 1 FN1-1 111-0 30 1
FN2 2018-07-16 2 FN2-1 586-0 40 1
FN2 2018-07-16 2 FN2-1 482-1 20 1
FN2 2018-07-16 2 FN2-1 784-1 20 1
FN2 2018-07-16 2 FN2-1 624-0 20 1
When something is refunded Header level:
OrderId OrderDate CustomerID OrderGuid OrderType Amount ReasonCode
FN1 2018-07-20 1 FN1-RF1 Credit 50 Lost_in_post
FN2 2018-07-21 2 FN2-RF1 Credit 60 Damaged_in_transit
Same at Line leveL:
OrderId OrderDate CustomerID OrderGuid ItemNo LineAmount Qty
FN1 2018-07-20 1 FN1-RF1 123-0 20 1
FN1 2018-07-20 1 FN1-RF1 111-0 30 1
FN2 2018-07-21 2 FN2-RF1 482-1 20 1
FN2 2018-07-21 2 FN2-RF1 784-1 20 1
FN2 2018-07-21 2 FN2-RF1 624-0 20 1
Note: There is no RefundDate column but still it's OrderDate the date will the day of refund.
Result expecting: I would like to see them in two different tables
Full refund:
OrderNo ItemNo Qty
FN1_RF1 123-0 1
FN1-RF1 111-0 1
Partial refund:
OrderNo ItemNo Qty
FN2-RF1 482-1 1
FN2-RF1 784-1 1
FN2-RF1 624-0 1
Hope this make sense.
Please ask me if you have any question. Thank you in advance.
Best
The following is a recreation of your problem with an example solution,
but a quick question why is FN2-RF1 considered a partial refund? Since the summed line items = the credited amount in your example.
http://rextester.com/EODK86280
This should do what you want. You can run the following example in SSMS.
First, create the data you provided for testing.
Create [header] table and insert data:
DECLARE #header TABLE ( OrderID VARCHAR(10), OrderDate DATETIME, CustomerID INT, OrderGuid VARCHAR(10), OrderType VARCHAR(10), Amount DECIMAL(18,2), ReasonCode VARCHAR(50) );
INSERT INTO #header (
OrderID, OrderDate, CustomerID, OrderGuid, OrderType, Amount, ReasonCode
) VALUES
( 'FN1', '2018-07-15', 1, 'FN1-1', 'Sales', 50, NULL )
, ( 'FN2', '2018-07-16', 2, 'FN2-1', 'Sales', 100, NULL )
, ( 'FN1', '2018-07-15', 1, 'FN1-RF1', 'Credit', 50, 'Lost_in_post' )
, ( 'FN2', '2018-07-16', 2, 'FN2-RF1', 'Credit', 60, 'Damaged_in_transit' );
Create [line] table and insert data:
DECLARE #line TABLE ( OrderID VARCHAR(10), OrderDate DATETIME, CustomerID INT, OrderGuid VARCHAR(10), ItemNo VARCHAR(10), LineAmount DECIMAL(18,2), Qty INT );
INSERT INTO #line (
OrderID, OrderDate, CustomerID, OrderGuid, ItemNo, LineAmount, Qty
) VALUES
( 'FN1', '2018-07-15', 1, 'FN1-1', '123-0', 20, 1 )
, ( 'FN1', '2018-07-15', 1, 'FN1-1', '111-0', 30, 1 )
, ( 'FN1', '2018-07-20', 1, 'FN1-RF1', '123-0', 20, 1 )
, ( 'FN1', '2018-07-20', 1, 'FN1-RF1', '111-0', 30, 1 )
, ( 'FN2', '2018-07-12', 2, 'FN2-1', '586-0', 40, 1 )
, ( 'FN2', '2018-07-12', 2, 'FN2-1', '482-0', 20, 1 )
, ( 'FN2', '2018-07-12', 2, 'FN2-1', '784-0', 20, 1 )
, ( 'FN2', '2018-07-12', 2, 'FN2-1', '624-0', 20, 1 )
, ( 'FN2', '2018-07-21', 2, 'FN2-RF1', '482-0', 20, 1 )
, ( 'FN2', '2018-07-21', 2, 'FN2-RF1', '784-0', 20, 1 )
, ( 'FN2', '2018-07-21', 2, 'FN2-RF1', '624-0', 20, 1 );
Then we can query against it.
Full Refunds
SELECT
ref.OrderGuid, line.ItemNo, line.Qty
FROM #header ref
INNER JOIN #header sale
ON ref.OrderID = sale.OrderID
AND sale.OrderType = 'Sales'
INNER JOIN #line line
ON ref.OrderGuid = line.OrderGuid
WHERE
ref.OrderType = 'Credit'
AND ref.Amount = sale.Amount
ORDER BY
ref.OrderID;
Returns
+-----------+--------+-----+
| OrderGuid | ItemNo | Qty |
+-----------+--------+-----+
| FN1-RF1 | 123-0 | 1 |
| FN1-RF1 | 111-0 | 1 |
+-----------+--------+-----+
Partial Refunds
SELECT
ref.OrderGuid, line.ItemNo, line.Qty
FROM #header ref
INNER JOIN #header sale
ON ref.OrderID = sale.OrderID
AND sale.OrderType = 'Sales'
INNER JOIN #line line
ON ref.OrderGuid = line.OrderGuid
WHERE
ref.OrderType = 'Credit'
AND ref.Amount < sale.Amount
ORDER BY
ref.OrderID;
Returns
+-----------+--------+-----+
| OrderGuid | ItemNo | Qty |
+-----------+--------+-----+
| FN2-RF1 | 482-0 | 1 |
| FN2-RF1 | 784-0 | 1 |
| FN2-RF1 | 624-0 | 1 |
+-----------+--------+-----+
Take a look at the ref.Amount vs. sale.Amount comparisons to see how this works.
Related
I am new to SQL and I was facing a problem.
I have 2 tables as shown below,
Order_table
Ord_num
Ord_date
Customer_name
Order_total
1111
2021-03-11
ABC
1000
Shipping_table
Ord_num
Pkg_num
Pkg_weight
shipped_date
shipping_cost
1111
1
30
2021-03-12
10
1111
2
20
2021-03-13
8
I wrote the following query,
select sum(order_total), sum(pkg_weight), sum(shipping_cost)
from order_table O join shipping_table P
on O.Ord_num = P.Ord_num
By this, if I sum my Order total, it shows 2000 but Order was only for 1000.
I basically want my output to be,
Ord_num
Ord_date
Cust_name
Order_total
Pkg_num
shipped_date
pkg_weight
shipping_cost
1111
2021-03-11
ABC
1000
1
2021-03-12
30
10
1111
2021-03-11
ABC
0 or null
2
2021-03-13
20
8
The reason I want Order_total as 0 or null in the second line is because when I aggregate other columns like pkg_weight and shipping_cost, it should show their sum whereas for Order_total, it should not show as 2000 because the order was for 1000 but shipped in two different packages with 2 weights, 2 costs and shipped on 2 different days.
Can anyone help me what I should write my query as?
Thanks in advance.
Start with this:
Declare #order_table Table (Ord_num int, Ord_date date, Customer_name varchar(30), Order_total int);
Insert Into #order_table (Ord_num, Ord_date, Customer_name, Order_total)
Values (1111, '2021-03-11', 'ABC', 1000)
, (2222, '2021-04-11', 'XYZ', 2000);
Declare #shipping_table Table (Ord_Num int, Pkg_num int, Pkg_weight int, Shipped_date date, Shipping_cost int)
Insert Into #shipping_table (Ord_Num, Pkg_num, Pkg_weight, Shipped_date, Shipping_cost)
Values (1111, 1, 30, '2021-03-12', 10)
, (1111, 2, 20, '2021-03-13', 8)
, (2222, 1, 15, '2021-04-12', 5)
, (2222, 2, 10, '2021-04-13', 3);
Select ord.Ord_num
, ord.Ord_date
, ord.Customer_name
, Order_total = iif(shp.Pkg_num = 1, ord.Order_total, 0)
, shp.Pkg_num
, shp.Shipped_date
, shp.Pkg_weight
, shp.Shipping_cost
From #order_table ord
Inner Join #shipping_table shp On shp.Ord_Num = ord.Ord_num;
Which can then be converted to this for totals:
Select ord.Ord_num
, ord.Ord_date
, ord.Customer_name
, Order_total = sum(iif(shp.Pkg_num = 1, ord.Order_total, 0))
, Pkg_weight = sum(shp.Pkg_num)
, Shipping_cost = sum(shp.Shipping_cost)
From #order_table ord
Inner Join #shipping_table shp On shp.Ord_Num = ord.Ord_num
Group By
ord.Ord_num
, ord.Ord_date
, ord.Customer_name;
I have 3 tables:
INVENTORY_IN:
ID INV_TIMESTAMP PRODUCT_ID IN_QUANTITY SUPPLIER_ID
...
1 10.03.21 01:00:00 101 100 4
2 11.03.21 02:00:00 101 50 3
3 14.03.21 01:00:00 101 10 2
INVENTORY_OUT:
ID INV_TIMESTAMP PRODUCT_ID OUT_QUANTITY CUSTOMER_ID
...
1 10.03.21 02:00:00 101 30 1
2 11.03.21 01:00:00 101 40 2
3 12.03.21 01:00:00 101 80 1
INVENTORY_BALANCE:
INV_DATE PRODUCT_ID QUANTITY
...
09.03.21 101 20
10.03.21 101 90
11.03.21 101 100
12.03.21 101 20
13.03.21 101 20
14.03.21 101 30
I want to use FIFO (first in-first out) logic for the inventory, and to see which quantities correspond to each SUPPLIER-CUSTOMER combination.
The desired ouput looks like this (queried for dates >= 2021-03-10):
PRODUCT_ID SUPPLIER_ID CUSTOMER_ID QUANTITY
101 1 20
101 4 1 60
101 4 2 40
101 3 1 30
101 3 20
101 2 10
edit. fixed little typo in numbers.
edit. Added a diagram which explains every row. All of the black arrows correspond to supplier and customer combinations, there are 7 of them, because for supplier_id = 4 and customer_id = 1 the desired results is the sum of matched quantities happening between them. So, it explains why there are 7 arrows, while the desired results contains only 6 rows.
Option 1
This is probably a job for PL/SQL. Starting with the data types to output:
CREATE TYPE supply_details_obj AS OBJECT(
product_id NUMBER,
quantity NUMBER,
supplier_id NUMBER,
customer_id NUMBER
);
CREATE TYPE supply_details_tab AS TABLE OF supply_details_obj;
Then we can define a pipelined function to read the INVENTORY_IN and INVENTORY_OUT tables one row at a time and merge the two keeping a running total of the remaining inventory or amount to supply:
CREATE FUNCTION assign_suppliers_to_customers (
i_product_id IN INVENTORY_IN.PRODUCT_ID%TYPE
)
RETURN supply_details_tab PIPELINED
IS
v_supplier_id INVENTORY_IN.SUPPLIER_ID%TYPE;
v_customer_id INVENTORY_OUT.CUSTOMER_ID%TYPE;
v_quantity_in INVENTORY_IN.IN_QUANTITY%TYPE := NULL;
v_quantity_out INVENTORY_OUT.OUT_QUANTITY%TYPE := NULL;
v_cur_in SYS_REFCURSOR;
v_cur_out SYS_REFCURSOR;
BEGIN
OPEN v_cur_in FOR
SELECT in_quantity, supplier_id
FROM INVENTORY_IN
WHERE product_id = i_product_id
ORDER BY inv_timestamp;
OPEN v_cur_out FOR
SELECT out_quantity, customer_id
FROM INVENTORY_OUT
WHERE product_id = i_product_id
ORDER BY inv_timestamp;
LOOP
IF v_quantity_in IS NULL THEN
FETCH v_cur_in INTO v_quantity_in, v_supplier_id;
IF v_cur_in%NOTFOUND THEN
v_supplier_id := NULL;
END IF;
END IF;
IF v_quantity_out IS NULL THEN
FETCH v_cur_out INTO v_quantity_out, v_customer_id;
IF v_cur_out%NOTFOUND THEN
v_customer_id := NULL;
END IF;
END IF;
EXIT WHEN v_cur_in%NOTFOUND AND v_cur_out%NOTFOUND;
IF v_quantity_in > v_quantity_out THEN
PIPE ROW(
supply_details_obj(
i_product_id,
v_quantity_out,
v_supplier_id,
v_customer_id
)
);
v_quantity_in := v_quantity_in - v_quantity_out;
v_quantity_out := NULL;
ELSE
PIPE ROW(
supply_details_obj(
i_product_id,
v_quantity_in,
v_supplier_id,
v_customer_id
)
);
v_quantity_out := v_quantity_out - v_quantity_in;
v_quantity_in := NULL;
END IF;
END LOOP;
END;
/
Then, for the sample data:
CREATE TABLE INVENTORY_IN ( ID, INV_TIMESTAMP, PRODUCT_ID, IN_QUANTITY, SUPPLIER_ID ) AS
SELECT 0, TIMESTAMP '2021-03-09 00:00:00', 101, 20, 0 FROM DUAL UNION ALL
SELECT 1, TIMESTAMP '2021-03-10 01:00:00', 101, 100, 4 FROM DUAL UNION ALL
SELECT 2, TIMESTAMP '2021-03-11 02:00:00', 101, 50, 3 FROM DUAL UNION ALL
SELECT 3, TIMESTAMP '2021-03-14 01:00:00', 101, 10, 2 FROM DUAL;
CREATE TABLE INVENTORY_OUT ( ID, INV_TIMESTAMP, PRODUCT_ID, OUT_QUANTITY, CUSTOMER_ID ) AS
SELECT 1, TIMESTAMP '2021-03-10 02:00:00', 101, 30, 1 FROM DUAL UNION ALL
SELECT 2, TIMESTAMP '2021-03-11 01:00:00', 101, 40, 2 FROM DUAL UNION ALL
SELECT 3, TIMESTAMP '2021-03-12 01:00:00', 101, 80, 1 FROM DUAL;
The query:
SELECT product_id,
supplier_id,
customer_id,
SUM( quantity ) AS quantity
FROM TABLE( assign_suppliers_to_customers( 101 ) )
GROUP BY
product_id,
supplier_id,
customer_id
ORDER BY
MIN( inv_timestamp )
Outputs:
PRODUCT_ID | SUPPLIER_ID | CUSTOMER_ID | QUANTITY
---------: | ----------: | ----------: | -------:
101 | 0 | 1 | 20
101 | 4 | 1 | 60
101 | 4 | 2 | 40
101 | 3 | 1 | 30
101 | 3 | null | 20
101 | 2 | null | 10
Option 2
A (very) complicated SQL query:
WITH in_totals ( ID, INV_TIMESTAMP, PRODUCT_ID, IN_QUANTITY, SUPPLIER_ID, TOTAL_QUANTITY ) AS (
SELECT i.*,
SUM( in_quantity ) OVER ( PARTITION BY product_id ORDER BY inv_timestamp )
FROM inventory_in i
),
out_totals ( ID, INV_TIMESTAMP, PRODUCT_ID, OUT_QUANTITY, CUSTOMER_ID, TOTAL_QUANTITY ) AS (
SELECT o.*,
SUM( out_quantity ) OVER ( PARTITION BY product_id ORDER BY inv_timestamp )
FROM inventory_out o
),
split_totals ( product_id, inv_timestamp, supplier_id, customer_id, quantity ) AS (
SELECT i.product_id,
MIN( COALESCE( LEAST( i.inv_timestamp, o.inv_timestamp ), i.inv_timestamp ) )
AS inv_timestamp,
i.supplier_id,
o.customer_id,
SUM(
COALESCE(
LEAST(
i.total_quantity - o.total_quantity + o.out_quantity,
o.total_quantity - i.total_quantity + i.in_quantity,
i.in_quantity,
o.out_quantity
),
0
)
)
FROM in_totals i
LEFT OUTER JOIN
out_totals o
ON ( i.product_id = o.product_id
AND i.total_quantity - i.in_quantity <= o.total_quantity
AND i.total_quantity >= o.total_quantity - o.out_quantity )
GROUP BY
i.product_id,
i.supplier_id,
o.customer_id
ORDER BY
inv_timestamp
),
missing_totals ( product_id, inv_timestamp, supplier_id, customer_id, quantity ) AS (
SELECT i.product_id,
i.inv_timestamp,
i.supplier_id,
NULL,
i.in_quantity - COALESCE( s.quantity, 0 )
FROM inventory_in i
INNER JOIN (
SELECT product_id,
supplier_id,
SUM( quantity ) AS quantity
FROM split_totals
GROUP BY product_id, supplier_id
) s
ON ( i.product_id = s.product_id
AND i.supplier_id = s.supplier_id )
ORDER BY i.inv_timestamp
)
SELECT product_id, supplier_id, customer_id, quantity
FROM (
SELECT product_id, inv_timestamp, supplier_id, customer_id, quantity
FROM split_totals
WHERE quantity > 0
UNION ALL
SELECT product_id, inv_timestamp, supplier_id, customer_id, quantity
FROM missing_totals
WHERE quantity > 0
ORDER BY inv_timestamp
);
Which, for the sample data above, outputs:
PRODUCT_ID | SUPPLIER_ID | CUSTOMER_ID | QUANTITY
---------: | ----------: | ----------: | -------:
101 | 0 | 1 | 20
101 | 4 | 1 | 60
101 | 4 | 2 | 40
101 | 3 | 1 | 30
101 | 3 | null | 20
101 | 2 | null | 10
db<>fiddle here
If your system controls the timestamps so you cannot consume what was not supplied (I've met systems, that didn't track intraday balance), then you can use SQL solution with interval join. The only thing to take care here is to track the last supply that was not consumed in full: it should be added as supply with no customer.
Here's the query with comments:
CREATE TABLE INVENTORY_IN ( ID, INV_TIMESTAMP, PRODUCT_ID, IN_QUANTITY, SUPPLIER_ID ) AS
SELECT 0, TIMESTAMP '2021-03-09 00:00:00', 101, 20, 0 FROM DUAL UNION ALL
SELECT 1, TIMESTAMP '2021-03-10 01:00:00', 101, 100, 4 FROM DUAL UNION ALL
SELECT 2, TIMESTAMP '2021-03-11 02:00:00', 101, 50, 3 FROM DUAL UNION ALL
SELECT 3, TIMESTAMP '2021-03-14 01:00:00', 101, 10, 2 FROM DUAL;
CREATE TABLE INVENTORY_OUT ( ID, INV_TIMESTAMP, PRODUCT_ID, OUT_QUANTITY, CUSTOMER_ID ) AS
SELECT 1, TIMESTAMP '2021-03-10 02:00:00', 101, 30, 1 FROM DUAL UNION ALL
SELECT 2, TIMESTAMP '2021-03-11 01:00:00', 101, 40, 2 FROM DUAL UNION ALL
SELECT 3, TIMESTAMP '2021-03-12 01:00:00', 101, 80, 1 FROM DUAL;
with i as (
select
/*Get total per product, supplier at each timestamp
to calculate running sum on timestamps without need to resolve ties with over(... rows between) addition*/
inv_timestamp
, product_id
, supplier_id
, sum(in_quantity) as quan
, sum(sum(in_quantity)) over(
partition by product_id
order by
inv_timestamp asc
, supplier_id asc
) as rsum
from INVENTORY_IN
group by
product_id
, supplier_id
, inv_timestamp
)
, o as (
select /*The same for customer*/
inv_timestamp
, product_id
, customer_id
, sum(out_quantity) as quan
, sum(sum(out_quantity)) over(
partition by product_id
order by
inv_timestamp asc
, customer_id asc
) as rsum
/*Last consumption per product: when lead goes beyond the current window*/
, lead(0, 1, 1) over(
partition by product_id
order by
inv_timestamp asc
, customer_id asc
) as last_consumption
from INVENTORY_OUT
group by
product_id
, customer_id
, inv_timestamp
)
, distr as (
select
/*Distribute the quantity. This is the basic interval intersection:
new_value_to = least(t1.value_to, t2.value_to)
new_value_from = greatest(t1.value_from, t2.value_from)
So we need a capacity of the interval
*/
i.product_id
, least(i.rsum, nvl(o.rsum, i.rsum))
- greatest(i.rsum - i.quan, nvl(o.rsum - o.quan, i.rsum - i.quan)) as supplied_quan
/*At the last supply we can have something not used.
Calculate it to add later as not consumed
*/
, case
when last_consumption = 1
and i.rsum > nvl(o.rsum, i.rsum)
then i.rsum - o.rsum
end as rest_quan
, i.supplier_id
, o.customer_id
, i.inv_timestamp as i_ts
, o.inv_timestamp as o_ts
from i
left join o
on i.product_id = o.product_id
/*No equality here, because values are continuous:
>= will include the same value in two intervals if some of value_to of one table equals
another's table value_to (which is value_from for the next interval)*/
and i.rsum > o.rsum - o.quan
and o.rsum > i.rsum - i.quan
)
select
product_id
, supplier_id
, customer_id
, sum(quan) as quan
from (
select /*Get distributed quantities*/
product_id
, supplier_id
, customer_id
, supplied_quan as quan
, i_ts
, o_ts
from distr
union all
select /*Add not consumed part of last consumed supply*/
product_id
, supplier_id
, null
, rest_quan
, i_ts
, null /*No consumption*/
from distr
where rest_quan is not null
)
group by
product_id
, supplier_id
, customer_id
order by
min(i_ts) asc
/*To order not consumed last*/
, min(o_ts) asc nulls last
PRODUCT_ID | SUPPLIER_ID | CUSTOMER_ID | QUAN
---------: | ----------: | ----------: | ---:
101 | 0 | 1 | 20
101 | 4 | 1 | 60
101 | 4 | 2 | 40
101 | 3 | 1 | 30
101 | 3 | null | 20
101 | 2 | null | 10
db<>fiddle here
I have these tables:
http://sqlfiddle.com/#!18/b871d/8
create table ItemOrder
(
ID int,
ItemNumber int,
Qty int,
Price int,
Cost int,
DateSold datetime
)
insert into ItemOrder (ID, ItemNumber, Qty, Price, Cost, DateSold)
Values
('1', '145', '5', '50', '25', '08-06-18'),
('2', '145', '5', '50', '25', '07-04-18'),
('3', '145', '5', '50', '25', '06-06-18')
Result:
| ID | ItemNumber | DateSold | Qty | Price | Cost |
|----|------------|----------------------|-----|-------|------|
| 1 | 145 | 2018-08-06T00:00:00Z | 5 | 50 | 25 |
| 2 | 145 | 2018-07-04T00:00:00Z | 5 | 50 | 25 |
| 3 | 145 | 2018-06-06T00:00:00Z | 5 | 50 | 25 |
But i was looking for a result that was split out by month like:
e.g.
| ID | ItemNumber | Aug-18 Qty | Aug-18 Price | Aug-18 Cost |July-18 Qty|July-18 Price|
|----|------------|------------|--------------|-------------|
| 1 | 145 | 5 | 50 | 25 |
and so on....
select
ID,
ItemNumber,
DateSold,
(
select ID, ItemNumber, Qty, DateSold
from ItemOrder
) x
PIVOT
(
SUM(QTY), SUM(Price), SUM(Cost) FOR DateSold in(DateSold1)
) p;
I have tried a couple of queries but cant seem to get it right. It would be great for any guidance. Thanks
I would suggest simply doing conditional aggregation:
select id, itemnumber,
sum(case when datesold >= '2018-08-01' and datesold < '2018-09-01' then qty else 0 end) as qty_201808,
sum(case when datesold >= '2018-08-01' and datesold < '2018-09-01' then price else 0 end) as price_201808,
sum(case when datesold >= '2018-07-01' and datesold < '2018-08-01' then qty else 0 end) as qty_201807,
sum(case when datesold >= '2018-07-01' and datesold < '2018-08-01' then price else 0 end) as price_201807
from itemorder
group by id, itemnumber
order by id, itemnumber;
Here is a SQL Fiddle.
WITH Table1 AS
(
select
ID,
ItemNumber,
CAST(year(DateSold) AS VARCHAR(4)) + ' ' + DATENAME(m, DateSold) AS [DateSold2],
Qty
from ItemOrder
)
select * from Table1
pivot (sum(Qty) for[DateSold2] IN ([2018 August], [2018 July], [2018 June])) as d
More what i was looking for :)
http://sqlfiddle.com/#!18/b871d/23
I don't even know what's a good title for this question.
But I'm having a table:
create table trans
(
[transid] INT IDENTITY (1, 1) NOT NULL,
[customerid] int not null,
[points] decimal(10,2) not null,
[date] datetime not null
)
and records:
--cus1
INSERT INTO trans ( customerid , points , date )
VALUES ( 1, 10, '2016-01-01' ) , ( 1, 20, '2017-02-01' ) , ( 1, 22, '2017-03-01' ) ,
( 1, 24, '2018-02-01' ) , ( 1, 50, '2018-02-25' ) , ( 2, 44, '2016-02-01' ) ,
( 2, 20, '2017-02-01' ) , ( 2, 32, '2017-03-01' ) , ( 2, 15, '2018-02-01' ) ,
( 2, 10, '2018-02-25' ) , ( 3, 10, '2018-02-25' ) , ( 4, 44, '2015-02-01' ) ,
( 4, 20, '2015-03-01' ) , ( 4, 32, '2016-04-01' ) , ( 4, 15, '2016-05-01' ) ,
( 4, 10, '2017-02-25' ) , ( 4, 10, '2018-02-27' ) ,( 4, 20, '2018-02-28' ) ,
( 5, 44, '2015-02-01' ) , ( 5, 20, '2015-03-01' ) , ( 5, 32, '2016-04-01' ) ,
( 5, 15, '2016-05-01' ) ,( 5, 10, '2017-02-25' );
-- selecting the data
select * from trans
Produces:
transid customerid points date
----------- ----------- --------------------------------------- -----------------------
1 1 10.00 2016-01-01 00:00:00.000
2 1 20.00 2017-02-01 00:00:00.000
3 1 22.00 2017-03-01 00:00:00.000
4 1 24.00 2018-02-01 00:00:00.000
5 1 50.00 2018-02-25 00:00:00.000
6 2 44.00 2016-02-01 00:00:00.000
7 2 20.00 2017-02-01 00:00:00.000
8 2 32.00 2017-03-01 00:00:00.000
9 2 15.00 2018-02-01 00:00:00.000
10 2 10.00 2018-02-25 00:00:00.000
11 3 10.00 2018-02-25 00:00:00.000
12 4 44.00 2015-02-01 00:00:00.000
13 4 20.00 2015-03-01 00:00:00.000
14 4 32.00 2016-04-01 00:00:00.000
15 4 15.00 2016-05-01 00:00:00.000
16 4 10.00 2017-02-25 00:00:00.000
17 4 10.00 2018-02-27 00:00:00.000
18 4 20.00 2018-02-28 00:00:00.000
19 5 44.00 2015-02-01 00:00:00.000
20 5 20.00 2015-03-01 00:00:00.000
21 5 32.00 2016-04-01 00:00:00.000
22 5 15.00 2016-05-01 00:00:00.000
23 5 10.00 2017-02-25 00:00:00.000
I'm trying to group all the customerid and sum their points. But here's the catch, If the trans is not active for 1 year(the next tran is 1 year and above), the points will be expired.
For this case:
Points for each customers should be:
Customer1 20+22+24+50
Customer2 20+32+15+10
Customer3 10
Customer4 10+20
Customer5 0
Here's what I have so far:
select
t1.transid as transid1,
t1.customerid as customerid1,
t1.date as date1,
t1.points as points1,
t1.rank1 as rank1,
t2.transid as transid2,
t2.customerid as customerid2,
t2.points as points2,
isnull(t2.date,getUTCDate()) as date2,
isnull(t2.rank2,t1.rank1+1) as rank2,
cast(case when(t1.date > dateadd(year,-1,isnull(t2.date,getUTCDate()))) Then 0 ELSE 1 END as bit) as ShouldExpire
from
(
select transid,CustomerID,Date,points,
RANK() OVER(PARTITION BY CustomerID ORDER BY date ASC) AS RANK1
from trans
)t1
left join
(
select transid,CustomerID,Date,points,
RANK() OVER(PARTITION BY CustomerID ORDER BY date ASC) AS RANK2
from trans
)t2 on t1.RANK1=t2.RANK2-1
and t1.customerid=t2.customerid
which gives
from the above table,how do I check for ShouldExpire field having max(rank1) for customer, if it's 1, then totalpoints will be 0, otherwise,sum all the consecutive 0's until there are no more records or a 1 is met?
Or is there a better approach to this problem?
The following query uses LEAD to get the date of the next record withing the same CustomerID slice:
;WITH CTE AS (
SELECT transid, CustomerID, [Date], points,
LEAD([Date]) OVER (PARTITION BY CustomerID
ORDER BY date ASC) AS nextDate,
CASE
WHEN [date] > DATEADD(YEAR,
-1,
-- same LEAD() here as above
ISNULL(LEAD([Date]) OVER (PARTITION BY CustomerID
ORDER BY date ASC),
getUTCDate()))
THEN 0
ELSE 1
END AS ShouldExpire
FROM trans
)
SELECT transid, CustomerID, [Date], points, nextDate, ShouldExpire
FROM CTE
ORDER BY CustomerID, [Date]
Output:
transid CustomerID Date points nextDate ShouldExpire
-------------------------------------------------------------
1 1 2016-01-01 10.00 2017-02-01 1 <-- last exp. for 1
2 1 2017-02-01 20.00 2017-03-01 0
3 1 2017-03-01 22.00 2018-02-01 0
4 1 2018-02-01 24.00 2018-02-25 0
5 1 2018-02-25 50.00 NULL 0
6 2 2016-02-01 44.00 2017-02-01 1 <-- last exp. for 2
7 2 2017-02-01 20.00 2017-03-01 0
8 2 2017-03-01 32.00 2018-02-01 0
9 2 2018-02-01 15.00 2018-02-25 0
10 2 2018-02-25 10.00 NULL 0
11 3 2018-02-25 10.00 NULL 0 <-- no exp. for 3
12 4 2015-02-01 44.00 2015-03-01 0
13 4 2015-03-01 20.00 2016-04-01 1
14 4 2016-04-01 32.00 2016-05-01 0
15 4 2016-05-01 15.00 2017-02-25 0
16 4 2017-02-25 10.00 2018-02-27 1 <-- last exp. for 4
17 4 2018-02-27 10.00 2018-02-28 0
18 4 2018-02-28 20.00 NULL 0
19 5 2015-02-01 44.00 2015-03-01 0
20 5 2015-03-01 20.00 2016-04-01 1
21 5 2016-04-01 32.00 2016-05-01 0
22 5 2016-05-01 15.00 2017-02-25 0
23 5 2017-02-25 10.00 NULL 1 <-- last exp. for 5
Now, you seem to want to calculate the sum of points after the last expiration.
Using the above CTE as a basis you can achieve the required result with:
;WITH CTE AS (
... above query here ...
)
SELECT CustomerID,
SUM(CASE WHEN rnk = 0 THEN points ELSE 0 END) AS sumOfPoints
FROM (
SELECT transid, CustomerID, [Date], points, nextDate, ShouldExpire,
SUM(ShouldExpire) OVER (PARTITION BY CustomerID ORDER BY [Date] DESC) AS rnk
FROM CTE
) AS t
GROUP BY CustomerID
Output:
CustomerID sumOfPoints
-----------------------
1 116.00
2 77.00
3 10.00
4 30.00
5 0.00
Demo here
The tricky part here is to dump all points when they expire, and start accumulating them again. I assumed that if there was only one transaction that we don't expire the points until there's a new transaction, even if that first transaction was over a year ago now?
I also get a different answer for customer #5, as they do appear to have a "transaction chain" that hasn't expired?
Here's my query:
WITH ordered AS (
SELECT
*,
ROW_NUMBER() OVER (PARTITION BY customerid ORDER BY [date]) AS order_id
FROM
trans),
max_transid AS (
SELECT
customerid,
MAX(transid) AS max_transid
FROM
trans
GROUP BY
customerid),
not_expired AS (
SELECT
t1.customerid,
t1.points,
t1.[date] AS t1_date,
CASE
WHEN m.customerid IS NOT NULL THEN GETDATE()
ELSE t2.[date]
END AS t2_date
FROM
ordered t1
LEFT JOIN ordered t2 ON t2.customerid = t1.customerid AND t1.transid != t2.transid AND t2.order_id = t1.order_id + 1 AND t1.[date] > DATEADD(YEAR, -1, t2.[date])
LEFT JOIN max_transid m ON m.customerid = t1.customerid AND m.max_transid = t1.transid
),
max_not_expired AS (
SELECT
customerid,
MAX(t1_date) AS max_expired
FROM
not_expired
WHERE
t2_date IS NULL
GROUP BY
customerid)
SELECT
n.customerid,
SUM(n.points) AS points
FROM
not_expired n
LEFT JOIN max_not_expired m ON m.customerid = n.customerid
WHERE
ISNULL(m.max_expired, '19000101') < n.t1_date
GROUP BY
n.customerid;
It could be refactored to be simpler, but I wanted to show the steps to get to the final answer:
customerid points
1 116.00
2 77.00
3 10.00
4 30.00
5 57.00
can you try this:
SELECT customerid,
Sum(t1.points)
FROM trans t1
WHERE NOT EXISTS (SELECT 1
FROM trans t2
WHERE Datediff(year, t1.date, t2.date) >= 1)
GROUP BY t1.customerid
Hope it helps!
try this:
select customerid,Sum(points)
from trans where Datediff(year, date, GETDATE()) < 1
group by customerid
output:
customerid Points
1 - 74.00
2 - 25.00
3 - 10.00
4 - 30.00
I have a table of transactions for various products. I want to calculate the profit made on each
Product Date Profit Incremental Profit
--------------------- --------------------------- -----------
Apple 2016-05-21 100
Banana 2016-05-21 60
Apple 2016-06-15 30
Apple 2016-08-20 10
Banana 2016-08-20 5
Can I create a SQL query that can group based on product and give me incremental profit on every date for each product. For example on 21-05-2015 since it is first date so incremental profit will be 0. But on 15-06-2016 it will be -70 (30-100).
The expected output is:
Product Date Profit Incremental Profit
--------------------- --------------------------- -----------
Apple 2016-05-21 100 0
Banana 2016-05-21 60 0
Apple 2016-06-15 30 -70
Apple 2016-08-20 10 -20
Banana 2016-08-20 5 -55
maybe u can use this.
select
a.product
,a.date
,a.profit
,isnull(a.profit - (select top 1 x.profit from profit x where x.product = a.product and x.date < a.date),0) as profit
from PROFIT a
order by product, date
Try this
DECLARE #Tbl TABLE (Product NVARCHAR(50), Date_ DATETIME, Profit INT)
INSERT INTO #Tbl
VALUES
('Apple' , '2016-05-21', 100),
('Banana', '2016-05-21', 60 ),
('Apple', '2016-06-15', 30 ),
('Apple', '2016-08-20', 10 ),
('Banana', '2016-08-20', 5 )
;WITH CTE
AS
(
SELECT
*,
ROW_NUMBER() OVER (PARTITION BY Product ORDER BY Date_) RowId
FROM #Tbl
)
SELECT
CurrentRow.Product ,
CurrentRow.Date_ ,
CurrentRow.Profit ,
CurrentRow.Profit - ISNULL(PrevRow.Profit, CurrentRow.Profit) 'Incremental Profit'
FROM
CTE CurrentRow LEFT JOIN
(SELECT CTE.Product ,CTE.Profit, CTE.RowId + 1 RowId FROM CTE) PrevRow ON CurrentRow.Product = PrevRow.product AND
CurrentRow.RowId = PrevRow.RowId
ORDER BY CurrentRow.Date_
Result:
Product Date_ Profit Incremental Profit
Apple 2016-05-21 100 0
Banana 2016-05-21 60 0
Apple 2016-06-15 30 -70
Apple 2016-08-20 10 -20
Banana 2016-08-20 5 -55
Edit:
UPDATE #Tbl
SET [Incremental Profit] = A.[Incremental Profit]
FROM
(
SELECT
CurrentRow.Product ,
CurrentRow.Date_ ,
CurrentRow.Profit ,
CurrentRow.Profit - ISNULL(PrevRow.Profit, CurrentRow.Profit) 'Incremental Profit'
FROM
(SELECT *, ROW_NUMBER() OVER (PARTITION BY Product ORDER BY Date_) RowId FROM #Tbl) CurrentRow LEFT JOIN
(SELECT *, ROW_NUMBER() OVER (PARTITION BY Product ORDER BY Date_) + 1 RowId FROM #Tbl) PrevRow ON CurrentRow.Product = PrevRow.Product AND
CurrentRow.RowId = PrevRow.RowId
) A
WHERE
[#Tbl].Product = A.Product AND
[#Tbl].Date_ = A.Date_