Aggregation/Joins in Order and Shipping tables - sql

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;

Related

Display Average Billing Amount For Each Customer only between years 2019-2021

QUESTION : Display Average Billing Amount For Each Customer ONLY between YEAR(2019-2021).
If customer doesn't have any billing amount for any of the particular year then consider as 0.
-------: OUTPUT :
Customer_ID | Customer_Name | AVG_Billed_Amount
-------------------------------------------------------------------------
1 | A | 87.00
2 | B | 200.00
3 | C | 183.00
--------: EXPLANATION :
If any customer doesn't have any billing records for these 3 years then we need to consider as one record with billing_amount = 0
Like Customer C doesn't have any record for Year 2020, so for C Average will be
(250+300+0)/3 = 183.33 OR 183.00
TEMP TABLE HAS FOLLOWING DATA
DROP TABLE IF EXISTS #TEMP;
CREATE TABLE #TEMP
(
Customer_ID INT
, Customer_Name NVARCHAR(100)
, Billing_ID NVARCHAR(100)
, Billing_creation_Date DATETIME
, Billed_Amount INT
);
INSERT INTO #TEMP
SELECT 1, 'A', 'ID1', TRY_CAST('10-10-2020' AS DATETIME), 100 UNION ALL
SELECT 1, 'A', 'ID2', TRY_CAST('11-11-2020' AS DATETIME), 150 UNION ALL
SELECT 1, 'A', 'ID3', TRY_CAST('12-11-2021' AS DATETIME), 100 UNION ALL
SELECT 2, 'B', 'ID4', TRY_CAST('10-11-2019' AS DATETIME), 150 UNION ALL
SELECT 2, 'B', 'ID5', TRY_CAST('11-11-2020' AS DATETIME), 200 UNION ALL
SELECT 2, 'B', 'ID6', TRY_CAST('12-11-2021' AS DATETIME), 250 UNION ALL
SELECT 3, 'C', 'ID7', TRY_CAST('01-01-2018' AS DATETIME), 100 UNION ALL
SELECT 3, 'C', 'ID8', TRY_CAST('05-01-2019' AS DATETIME), 250 UNION ALL
SELECT 3, 'C', 'ID9', TRY_CAST('06-01-2021' AS DATETIME), 300
-----------------------------------------------------------------------------------
Here, 'A' has 3 transactions - TWICE in year 2020(100+150) and 1 in year 2021(100), but none in 2019(SO, Billed_Amount= 0).
so the average will be calculated as (100+150+100+0)/4
DECLARE #BILL_dATE DATE = (SELECT Billing_creation_date from #temp group by customer_id, Billing_creation_date) /*-- THIS THROWS ERROR AS #BILL_DATE WON'T ACCEPT MULTIPLE VALUES.*/
OUTPUT should look like this:
Customer_ID
Customer_Name
AVG_Billed_Amount
1
A
87.00
2
B
200.00
3
C
183.00
You just need a formula to count the number of missing years.
That's 3 - COUNT(DISTINCT YEAR(Billing_creation_Date)
Then the average = SUM() / (COUNT() + (3 - COUNT(DISTINCT YEAR)))...
SELECT
Customer_ID,
Customer_Name,
SUM(Billed_Amount) * 1.0
/
(COUNT(*) + 3 - COUNT(DISTINCT YEAR(Billing_creation_Date)))
AS AVG_Billed_amount
FROM
#temp
WHERE
Billing_creation_Date >= '2019-01-01'
AND Billing_creation_Date < '2022-01-01'
GROUP BY
Customer_ID,
Customer_Name
Demo : https://dbfiddle.uk/ILcfiGWL
Note: The WHERE clause in another answer here would cause a scan of the table, due to hiding the filtered column behind a function. The way I've formed the WHERE clause allows a "Range Seek" if the column is in an index.
Here is a query that can do that :
select s.Customer_ID, s.Customer_Name, sum(Billed_amount)/ ( 6 - count(1)) as AVG_Billed_Amount from (
select Customer_ID, Customer_Name, sum(Billed_Amount) as Billed_amount
from TEMP
where year(Billing_creation_Date) between 2019 and 2021
group by Customer_ID, year(Billing_creation_Date)
) as s
group by Customer_ID;
According to your description the customer_name C will be 137.5000 not 183.00 since 2018 is not counted and 2020 is not there.

How to distribute sales with partitions

I have 2 tables:
1st table columns: ItemCode int, Amount float (I have over 1000 ItemCodes)
2nd table columns: ItemCode int, SoldAmount float, Price float (I have over 10000 sale rows for different items)
Example:
ItemId 1528's Amount in 1st table is 244. That items sales in the 2nd table is as below:
Sale 1 Amount = 120, Price = 10
Sale 2 Amount = 120, Price = 30
Sale 3 Amount = 100, Price = 20
Sale 4 Amount = 10, Price = 25
ItemCode
Amount
1528
244
1530
150
ItemCode
Date
Amount
Price
1528
2021.11.01
120
10
1530
2021.10.01
120
30
1528
2021.09.01
100
20
1530
2021.08.01
10
25
Tried cursor and loop , but no desired output.
The desired outcome is to distribute that 100 amount with the sales above like following:
Sale 1 Amount 60: 100 - 60 = 40 with price 5 --- So we continue to the next row and subtract whatever is left
Sale 2 Amount 30: 40 - 30 = 10 with price 6 --- So we continue to the next row and subtract whatever is left
Sale 3 Amount 20: 10 - 20 = -10 with price 7 --- So we stop here as the amount is equal to 0 or below .
As the result we should get this:
60 * 5 = 300
30 * 6 = 180
10 * 7 = 70 (that 10 is derived from whatever could be subtracted before it hits 0)
Desired table as below
ItemCode
Date
Amount
Price
1528
2021.11.01
120
10
1528
2021.10.01
120
30
1528
2021.09.01
4
20
My last attempt was as below
WITH CTE AS (
SELECT ItemCode, SUM(Amount) AS Amount
FROM table 1
GROUP BY STOCKREF )
SELECT *,
IIF(LAG(table1.Amount - table2.amount) OVER (PARTITION BY table1.Amount ORDER BY Date DESC) IS NULL, table1.Amount - table2.amount,
LAG(table1.Amount - table2.amount) OVER (PARTITION BY CTE.ItemCode ORDER BY Date DESC) - table2.AMOUNT) AS COL
FROM CTE JOIN (SELECT ItemCode, DATE_, AMOUNT, PRICE FROM table2) table 2 ON table1.ItemCode = table2.Amount
Hopefully this addresses the right question - if you're trying to create a running total per item_code, deducting the sale quantity from starting inventory from first-to-last sale, maybe this would work:
CREATE TABLE #items (item_code INT,
item_amount INT);
INSERT INTO #items (item_code, item_amount)
VALUES (1528, 244);
INSERT INTO #items (item_code, item_amount)
VALUES (1529, 240);
CREATE TABLE #sales (item_code INT,
sale_date DATE,
sale_amount INT,
sale_price DECIMAL(12,2));
INSERT INTO #sales (item_code, sale_date, sale_amount, sale_price)
VALUES (1528, '2021-12-01', 50, 5);
INSERT INTO #sales (item_code, sale_date, sale_amount, sale_price)
VALUES (1528, '2021-11-29', 120, 6.76292);
INSERT INTO #sales (item_code, sale_date, sale_amount, sale_price)
VALUES (1528, '2021-11-15', 120, 6.6453);
INSERT INTO #sales (item_code, sale_date, sale_amount, sale_price)
VALUES (1528, '2021-11-01', 100, 6.96875);
INSERT INTO #sales (item_code, sale_date, sale_amount, sale_price)
VALUES (1529, '2021-11-30', 48, 7.2);
INSERT INTO #sales (item_code, sale_date, sale_amount, sale_price)
VALUES (1529, '2021-11-18', 48, 3.5);
INSERT INTO #sales (item_code, sale_date, sale_amount, sale_price)
VALUES (1529, '2021-11-09', 96, 3.9);
INSERT INTO #sales (item_code, sale_date, sale_amount, sale_price)
VALUES (1529, '2021-11-05', 96, 3.75);
;WITH all_sales_with_running_totals AS ( --Calculate the running total of each item, deducting sale amount from total starting amount, in order of first sale to last
SELECT s.item_code,
s.sale_date,
s.sale_price,
i.item_amount AS starting_amount,
s.sale_amount,
i.item_amount - SUM(sale_amount) OVER(PARTITION BY s.item_code
ORDER BY s.sale_date
ROWS UNBOUNDED PRECEDING
) AS running_sale_amount
FROM #sales AS s
JOIN #items AS i ON s.item_code = i.item_code
),
sales_with_prev_running_total AS ( --Add the previous rows' running total, to assist with the final calculation
SELECT item_code,
sale_date,
sale_price,
starting_amount,
sale_amount,
running_sale_amount,
LAG(running_sale_amount, 1, NULL) OVER(PARTITION BY item_code
ORDER BY sale_date
)AS prev_running_sale_amount
FROM all_sales_with_running_totals
)
SELECT item_code, --Return the final running sale amount for each sale - if the inventory has already run out, return null. If there is insufficient inventory to fill the order, fill it with the qty remaining. Otherwise, fill the entire order.
sale_date,
sale_price,
starting_amount,
sale_amount,
running_sale_amount,
prev_running_sale_amount,
CASE WHEN prev_running_sale_amount <= 0
THEN NULL
WHEN running_sale_amount < 0
THEN prev_running_sale_amount
ELSE sale_amount
END AS result_sale_amount
FROM sales_with_prev_running_total;

SQL FIFO query with group by

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

Differentiate full refund vs partial refund

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.

Multiple joins on same table giving duplicate data

Here is the sale_order table which contains CCNID
sale_orderid client_channel_nameid
1 1
2 1
3 2
4 2
5 2
6 2
7 1
8 1
sale_order_item Table has sale_orderid as a foreign key
sale_order_itemid sale_orderid order_date selling_price
42219 1 2018-03-21 00:00:00 200
28948 2 2018-03-21 16:17:55 100
42220 3 2018-03-21 00:00:00 300
13194 4 2018-03-21 13:33:58 400
42839 5 2018-03-20 07:54:29 550
42840 6 2018-03-20 07:58:20 600
42086 7 2018-03-20 00:00:00 700
11691 8 2018-03-20 05:32:31 500
And I want to get the sum of price of soid of 21 and 20 dates in different columns grouped by CCNID
client_channel_nameid 21 20
1 300 1200
2 700 1150
I am joining Sale order twice which is giving me wrong results
select ccn.client_channel_nameid,
round (sum(soi.selling_price)),
round(sum(soi1.selling_price))
from app.client_channel_name ccn
join app.sale_order so on so.client_channel_nameid = ccn.client_channel_nameid
inner join app.sale_order_item soi on soi.sale_orderid = so.sale_orderid
join app.sale_order so1 on so1.client_channel_nameid = ccn.client_channel_nameid
inner join app.sale_order_item soi1 on soi1.sale_orderid = so1.sale_orderid
where ccn.clientid = 1
and to_char(soi.order_date, 'DD-MM-YYYY') = '20-03-2018'
and to_char(soi1.order_date, 'DD-MM-YYYY') = '21-03-2018'
group by client_channel_nameid;
You can group the data by CCNID and then only sum selling_price when the order_date day is the 21 or 20.
SELECT client_channel_nameid
, SUM(CASE
WHEN EXTRACT(day FROM order_date) = 21 THEN selling_price
ELSE 0
END) AS "21"
, SUM(CASE
WHEN EXTRACT(day FROM order_date) = 20 THEN selling_price
ELSE 0
END) AS "20"
FROM sale_order so
JOIN sale_order_item soi
ON soi.sale_orderid = so.sale_orderid
GROUP BY so.client_channel_nameid
Below query produce the desired result. From your above tried solution and question you asked I think you are looking for exactly 21 and 20 dates. The below will need slight changes with extra filters for add more dates ex.22,23,24...
with sale_order(
sale_orderid, client_channel_nameid
) as (
select
*
from
(
values
(1, 1),
(2, 1),
(3, 2),
(4, 2),
(5, 2),
(6, 2),
(7, 1),
(8, 1)
) as x(
sale_orderid, client_channel_nameid
)
),
sale_order_item(
sale_order_itemid, sale_orderid,
order_date, selling_price
) as (
select
*
from
(
values
(
42219, 1, '2018-03-21 00:00:00' :: timestamp,
200
),
(
28948, 2, '2018-03-21 16:17:55' :: timestamp,
100
),
(
42220, 3, '2018-03-21 00:00:00' :: timestamp,
300
),
(
13194, 4, '2018-03-21 13:33:58' :: timestamp,
400
),
(
42839, 5, '2018-03-20 07:54:29' :: timestamp,
550
),
(
42840, 6, '2018-03-20 07:58:20' :: timestamp,
600
),
(
42086, 7, '2018-03-20 00:00:00' :: timestamp,
700
),
(
11691, 8, '2018-03-20 05:32:31' :: timestamp,
500
)
) as x(
sale_order_itemid, sale_orderid,
order_date, selling_price
)
)
select
client_channel_nameid,
sum(selling_price) filter (where to_char(order_date, 'dd-mm-yy') = '21-03-2018') as date_21,
sum(selling_price) filter (where to_char(order_date, 'dd-mm-yy') = '20-03-2018') as date_20
from
sale_order so
join sale_order_item soi on soi.sale_orderid = so.sale_orderid
group by
so.client_channel_nameid