TSQL: How to apply Condition to sub grouping - tsql

Image I have the following table with multiple codes for a single person for different periods (id is the primary key)
id code Name Start Finish
325 1353 Bob NULL 2012-07-03 16:21:16.067
1742 1353 Bob 2012-07-03 16:21:16.067 2012-08-03 15:56:29.897
1803 1353 Bob 2012-08-03 15:56:29.897 NULL
17 575 Bob NULL NULL
270 834 Bob NULL 2012-07-20 15:51:19.913
1780 834 Bob 2012-07-20 15:51:19.913 2012-07-26 16:26:54.413
1789 834 Bob 2012-07-26 16:26:54.413 2012-08-21 15:36:58.940
1830 834 Bob 2012-08-21 15:36:58.940 2012-08-24 14:26:05.890
1835 834 Bob 2012-08-24 14:26:05.890 2012-08-30 12:01:05.313
1838 123 Bob 2012-08-30 12:01:05.313 2012-09-05 09:29:02.497
1844 900 Bob 2012-09-05 09:29:02.497 NULL
What I want to do update the table such that the code is take from the latest person.
id code Name Start Finish
325 900 Bob NULL 2012-07-03 16:21:16.067
1742 900 Bob 2012-07-03 16:21:16.067 2012-08-03 15:56:29.897
1803 900 Bob 2012-08-03 15:56:29.897 NULL
17 900 Bob NULL NULL
270 900 Bob NULL 2012-07-20 15:51:19.913
1780 900 Bob 2012-07-20 15:51:19.913 2012-07-26 16:26:54.413
1789 900 Bob 2012-07-26 16:26:54.413 2012-08-21 15:36:58.940
1830 900 Bob 2012-08-21 15:36:58.940 2012-08-24 14:26:05.890
1835 900 Bob 2012-08-24 14:26:05.890 2012-08-30 12:01:05.313
1838 900 Bob 2012-08-30 12:01:05.313 2012-09-05 09:29:02.497
1844 900 Bob 2012-09-05 09:29:02.497 NULL
Latest person is defined as the person with the latest (max?) Start AND (Finish IS NULL or Finish >= GetDate()) WITHIN the Group of people of same Name AND Code
In the above example that is where id = 1844 (with the groups of Bob it's got the latest Start and the Finish is Null)
I pretty sure this is possible with a single statement but I can see how to define 'Latest Person' such that I can join it back to get rows I want to update
Edit: Please note that I cannot rely on the ordering of the Id column only the date columns.

Something like this will do:
update this set code = (
select top (1) that.code from table1 that
where that.name = this.name -- match on name
and (that.Finish is null or that.Finish >= getdate()) -- filter for current rows only
order by that.Start desc, that.id desc -- rank by start, break ties with id
)
from table1 this
I hope your table is well indexed, and/or not too big, because this is expensive to do in one step.
Alternate form, using OUTER APPLY, and more easily extensible:
update this set code = that.code
from table1 this
outer apply (
select top (1) that.code from table1 that
where that.name = this.name -- match on name
and (that.Finish is null or that.Finish >= getdate()) -- filter for current rows
order by that.Start desc, that.id desc -- rank by start, break ties with id
) that
Alternate method using windowing functions, without a join:
update this set code = _latest_code
from (
-- identify the latest code per name
select *, _latest_code = max(
case
when (finish is null or finish >= getdate())
and _row_number = 1
then code else null
end
) over (partition by name)
from (
-- identify the latest row per name
select *, _row_number = row_number() over (
partition by name order by
case when finish is null or finish >= getdate() then 0 else 1 end
, start desc, id desc)
from table1
) this
) this

Related

Window Function For Consecutive Dates

I want to know how many users were active for 3 consecutive days on any given day.
e.g on 2022-11-03, 1 user (user_id = 111) was active 3 days in a row. Could someone please advise what kind of window function(?) would be needed?
This is my dataset:
user_id
active_date
111
2022-11-01
111
2022-11-02
111
2022-11-03
222
2022-11-01
333
2022-11-01
333
2022-11-09
333
2022-11-10
333
2022-11-11
If you are confident there are no duplicate user_id + active_date rows in the source data, then you can use two LAG functions like this:
SELECT user_id,
active_date,
CASE WHEN DATEADD(day, -1, active_date) = LAG(active_date, 1) OVER (PARTITION BY user_id ORDER BY active_date)
AND DATEADD(day, -2, active_date) = LAG(active_date, 2) OVER (PARTITION BY user_id ORDER BY active_date)
THEN 'Yes'
ELSE 'No'
END AS rowof3
FROM your_table
ORDER BY user_id, active_date;
If there might be duplication, use this FROM clause instead:
FROM (SELECT DISTINCT user_id, active_date :: DATE FROM your_table)

postgresql select rows from same table twice

I want to compare deposit for each person in the table.
and return all the rows where the deposit field is decreased.
Here is what I have done so far;
The customer table is;
person_id employee_id deposit ts
101 201 44 2021-09-30 10:12:19+00
100 200 45 2021-09-30 10:12:19+00
101 201 47 2021-09-30 09:12:19+00
100 200 21 2021-09-29 10:12:19+00
104 203 54 2021-09-27 10:12:19+00
and as a result I want is;
person_id employee_id deposit ts
101 201 44 2021-09-30 10:12:19+00
SELECT person_id,
employee_id,
deposit,
ts,
lag(deposit) over client_window as pre_deposit,
lag(ts) over client_window as pre_ts
FROM customer
WINDOW client_window as (partition by person_id order by ts)
ORDER BY person_id , ts
so it returns table with the following results;
person_id employee_id deposit ts pre_deposit pre_ts
101 201 44 2021-09-30 10:12:19+00 47 2021-09-30 09:12:19+00
100 200 45 2021-09-30 10:12:19+00 21 2021-09-29 10:12:19+00
101 201 47 2021-09-30 09:12:19+00 null null
100 200 21 2021-09-29 10:12:19+00 null null
104 203 54 2021-09-27 10:12:19+00 null null
SELECT person_id,
employee_id,
deposit,
ts,
lag(deposit) over client_window as pre_deposit,
lag(ts) over client_window as pre_ts
FROM customer
WINDOW client_window as (partition by person_id order by ts)
WHERE pre_deposit > deposit //this returns column not found for pre_deposit
ORDER BY person_id , ts
so far somehow I need to select the same table again to be able to apply this condition;
where pre_deposit > deposit
what does it make sense here?
union? outer-join? left-join? right-join?
Use your query as a subquery and filter the results:
SELECT person_id, employee_id, deposit, ts
FROM (
SELECT *, lag(deposit) over client_window as pre_deposit
FROM customer
WINDOW client_window as (partition by person_id order by ts)
) t
WHERE deposit < pre_deposit
ORDER BY person_id, ts;
See the demo.

INNER JOIN change SUM result

CREATE TABLE beauty.customer_payments
(
customer_id integer,
date date,
amount numeric(10,2),
CONSTRAINT customer_payments_customer_id_fkey FOREIGN KEY (customer_id)
REFERENCES beauty.customers (customer_id) MATCH SIMPLE
ON UPDATE NO ACTION
ON DELETE NO ACTION
)
CREATE TABLE beauty.sales
(
product_id integer,
customer_id integer,
sell_date date NOT NULL,
qty integer NOT NULL,
sell_price numeric(10,2) NOT NULL,
expiry_date date NOT NULL,
CONSTRAINT sales_customer_id_fkey FOREIGN KEY (customer_id)
REFERENCES beauty.customers (customer_id) MATCH SIMPLE
ON UPDATE NO ACTION
ON DELETE NO ACTION,
CONSTRAINT sales_product_id_fkey FOREIGN KEY (product_id)
REFERENCES beauty.products (product_id) MATCH SIMPLE
ON UPDATE NO ACTION
ON DELETE NO ACTION
)
Balance of payments beauty.customer_payments for customer_id=6 is 0
SELECT * FROM beauty.customer_payments
WHERE customer_id=6;
customer_id
date
amount
6
2020-11-14
75.00
6
2020-11-14
-75.00
SELECT * FROM beauty.sales
WHERE customer_id=6;
product_id
customer_id
sell_date
qty
sell_price
expiry_date
76
6
2020-11-14
1
75.00
2022-03-03
83
6
2020-11-14
1
10.00
2022-06-23
85
6
2020-11-14
1
10.00
2022-06-23
44
6
2020-11-14
1
12.00
2022-06-23
41
6
2020-11-14
1
15.00
2022-03-26
96
6
2020-11-14
1
75.00
2022-03-15
28
6
2020-11-14
1
4.00
2022-01-22
33
6
2020-11-14
1
4.00
2023-01-23
37
6
2020-11-14
1
4.00
2023-01-23
40
6
2020-11-14
1
4.00
2023-08-13
(10 rows)
SELECT customer_id, SUM(qty * sell_price) AS purchased
FROM beauty.sales
WHERE customer_id=6
GROUP BY customer_id;
customer_id
purchased
6
213.00
SELECT s.customer_id,
SUM(qty * sell_price) AS purchased,
SUM(cp.amount) AS paid,
SUM(qty * sell_price - cp.amount) AS balance
FROM beauty.sales s
INNER JOIN beauty.customer_payments cp
ON cp.customer_id = s.customer_id
WHERE s.customer_id=6
GROUP BY s.customer_id;
customer_id
purchased
paid
balance
6
1065.00
0.00
1065.00
Please advise WHY after adding JOIN (INNER, LEFT, RIGHT) calculation goes wrong and how to solve this multi-calculation issue?
As I see similar question all of them based on not cross-tables calculations like SUM(qty * sell_price - cp.amount)
Delete payments
`
DELETE FROM beauty.customer_payments
WHERE customer_id=6;`
Add new ZERO payment
INSERT INTO beauty.customer_payments(
customer_id, date, amount)
VALUES (6, '2020-11-17', 0);
customer_id
purchased
paid
balance
6
213.00
0.00
213.00
Add payment 10
INSERT INTO beauty.customer_payments(
customer_id, date, amount)
VALUES (6, '2020-11-17', 10);
SELECT * FROM beauty.customer_payments WHERE customer_id=6;
customer_id
date
amount
6
2020-11-17
0.00
6
2020-11-17
10.00
SELECT s.customer_id,
.......
INNER JOIN beauty.customer_payments cp
.......
customer_id
purchased
paid
balance
6
426.00
100.00
326.00
Correct payment with negative amount
INSERT INTO beauty.customer_payments(
customer_id, date, amount)
VALUES (6, '2020-11-17', -10);
SELECT * FROM beauty.customer_payments WHERE customer_id=6;
customer_id
date
amount
6
2020-11-17
0.00
6
2020-11-17
10.00
6
2020-11-17
-10.00
SELECT s.customer_id,
.......
INNER JOIN beauty.customer_payments cp
.......
customer_id
purchased
paid
balance
6
639.00
0.00
639.00
What is this `INNER JOIN' calculate?
In general, you probably wouldn't want to match each payment to each sale (unless there's an additional identifier matching specific payments to specific sales, not just matching each to a customer).
If a customer has 2 sales for $10 and $15, and two payments for $9 and $14, your join is going to match each payment to each sale for that customer, creating something like
Sale
Payment
$10
$9
$10
$14
$15
$9
$15
$14
So the sum of the sales after the join will be $50, not $25 (as you might be expecting). I think the above answers your question about why the join doesn't do what you expect.
The exact query you want might be a little different (do you want all customers even if they have no sales? is it possible for customers to have payments if they don't have a sale?), but in general I'd expect something like the following to work. There are multiple ways of doing this, but I think the following is easy to understand since it aggregates the data into one payment row per customer and one sales row per customer before joining them.
SELECT
s.customer_id,
s.purchased,
cp.amount as paid,
s.purchased - cp.amount as balance
FROM
(SELECT s.customer_id,
SUM(s.qty * s.sell_price) AS purchased
FROM beauty.sales s
GROUP BY s.customer_id) s
LEFT OUTER JOIN
(SELECT cp.customer_id,
SUM(cp.amount) AS amount
FROM beauty.customer_payments cp
GROUP BY cp.customer_id) cp
ON s.customer_id = cp.customer_id
WHERE s.customer_id = 6

GROUP BY name and ORDER BY point & time MYSQLi

I'm new to this forum and I hope to find my solution about my problem.
I have this table :
name time points car date
Daniel | 55s | 210 | red |20/01/2018
Daniel | 45s | 250 | green |21/01/2018
Julie | 54s | 220 | red |19/01/2018
Julie | 33s | 150 | yellow|22/01/2018
and I wish to sort it like this
name time points car date
Daniel | 45s | 250 |green |21/01/2018
Julie | 54s | 220 |red |19/01/2018
first sorting by points, than sorting by time and group by name (optional the count)
I use this
SELECT NAME, MAX(POINTS) POINTS, MAX(TIME) TIME, MAX(CAR) CAR, MAX(DATE) DATE
FROM ( SELECT A.* FROM test A LEFT OUTER JOIN test B ON A.NAME=B.NAME AND
A.POINTS<B.POINTS AND A.TIME>B.TIME WHERE B.NAME IS NULL ) as sub GROUP BY NAME
and I get this :
name POINTS TIME CAR DATE
Daniel 250 45 green 2018-01-21
Julie 220 54 yellow 2018-01-22
Julie should have car=red & 2018-01-19
For Daniel it looks good
how can I get thise values (car & date) ?
thanks
Nico
You could give this a shot. It contains the table against itself and gets only records with the highest points and lowest time.
SELECT NAME, MAX(POINTS) POINTS, MAX(TIME) TIME
FROM
(
SELECT A.* FROM test A
LEFT OUTER JOIN test B ON A.NAME=B.NAME AND A.POINTS<B.POINTS AND A.TIME>B.TIME
WHERE B.NAME IS NULL
) GROUP BY NAME
For additional, try this:
SELECT * FROM
(
SELECT OUTERTEST.*,
#row_num := IF(#prev_value=OUTERTEST.name,#row_num+1,1) AS RowNumber,
#prev_value := OUTERTEST.name
FROM (SELECT * FROM TEST ORDER BY NAME, TEST.POINTS DESC, TEST.TIME ASC) OUTERTEST, (SELECT #row_num := 1, #prev_value := '') x
) A
WHERE A.ROWNUMBER=1
I did more test with this table
id name time points
1 Daniel 55 1140
2 Judie 54 1144
3 Judie 33 1028
4 Daniel 45 1180
5 Judie 53 1148
I apply this request
SELECT NAME, MAX(POINTS) POINTS, sub.TIME FROM (SELECT * FROM Testpoint ORDER BY POINTS DESC, TIME ASC) AS sub
GROUP BY sub.name
I have the max point for each name but the time is not the right one
name POINTS time
Daniel 1180 55
Judie 1148 54
Judie should have 53 for time and not 54
What I did wrong?
thankyou
Nico

SQL Query to calculate remaining running balances based on a given conditions

I have a stock transaction table like this:
StockID Item TransDate TranType BatchNo Qty Price
10001 ABC 01-Apr-2012 IN 71001000 200 750.0
10002 ABC 02-Apr-2012 OUT 100
10003 ABC 03-Apr-2012 IN 71001001 50 700.0
10004 ABC 04-Apr-2012 IN 71001002 75 800.0
10005 ABC 10-Apr-2012 OUT 125
10006 XYZ 05-Apr-2012 IN 71001003 150 350.0
10007 XYZ 05-Apr-2012 OUT 120
10008 XYZ 15-Apr-2012 OUT 10
10009 XYZ 20-Apr-2012 IN 71001004 90 340.0
10010 PQR 06-Apr-2012 IN 71001005 50 510.0
10011 PQR 15-Apr-2012 IN 71001006 60 505.0
10012 MNO 01-Apr-2012 IN 71001007 76 410.0
10013 MNO 11-Apr-2012 OUT 76
Each of my IN transactions has price associated to it and a batch number (lot number). Now I would like to calculate the remaining quantity by First In First Out (FIFO) rule, meaning the first in should be adjusted with first out. After adjusting the quantities the remaining balances are to be calculated against each IN transaction for the same item as shown below:
StockID Item TransDate TranType BatchNo Qty Price RemainingQty
10001 ABC 01-Apr-2012 IN 71001000 200 750.0 0
10002 ABC 02-Apr-2012 OUT 100
10003 ABC 03-Apr-2012 IN 71001001 50 700.0 25
10004 ABC 04-Apr-2012 IN 71001002 75 800.0 75
10005 ABC 10-Apr-2012 OUT 125
10006 XYZ 05-Apr-2012 IN 71001003 150 350.0 20
10007 XYZ 05-Apr-2012 OUT 120
10008 XYZ 15-Apr-2012 OUT 10
10009 XYZ 20-Apr-2012 IN 71001004 90 340.0 90
10010 PQR 06-Apr-2012 IN 71001005 50 510.0 50
10011 PQR 15-Apr-2012 IN 71001006 60 505.0 60
10012 MNO 01-Apr-2012 IN 71001007 76 410.0 0
10013 MNO 11-Apr-2012 OUT 76
As we can see from the above table for item ABC, after adjusting (125 + 100) OUT qty against the IN qty (100 + 50 + 75) using FIFO the quantity remaining for the batch 71001000 is 0, 71001001 is 25 and for batch 71001002 is 75. From the remaining quantity the value can be derived.
Please help me to achieve this using any of the methods (either cursor based or CTE or JOINS, etc)
Thanks in advance for the help.
One of the users of StockOverflow suggested this answer:
SELECT 10001 as stockid,'ABC' as item,'01-Apr-2012' as transdate,'IN' as trantype, 71001000 as batchno, 200 as qty, 750.0 as price INTO #sample
UNION ALL SELECT 10002 ,'ABC','02-Apr-2012','OUT', NULL ,100,NULL
UNION ALL SELECT 10003 ,'ABC','03-Apr-2012','IN', 71001001, 50 , 700.0
UNION ALL SELECT 10004 ,'ABC','04-Apr-2012','IN', 71001002, 75 , 800.0
UNION ALL SELECT 10005 ,'ABC','10-Apr-2012','OUT', NULL ,125,NULL
UNION ALL SELECT 10006 ,'XYZ','05-Apr-2012','IN', 71001003, 150 , 350.0
UNION ALL SELECT 10007 ,'XYZ','05-Apr-2012','OUT', NULL , 120 ,NULL
UNION ALL SELECT 10008 ,'XYZ','15-Apr-2012','OUT', NULL , 10 ,NULL
UNION ALL SELECT 10009 ,'XYZ','20-Apr-2012','IN', 71001004, 90 , 340.0
UNION ALL SELECT 10010 ,'PQR','06-Apr-2012','IN', 71001005, 50 , 510.0
UNION ALL SELECT 10011 ,'PQR','15-Apr-2012','IN', 71001006, 60 , 505.0
UNION ALL SELECT 10012 ,'MNO','01-Apr-2012','IN', 71001007, 76 , 410.0
UNION ALL SELECT 10013 ,'MNO','11-Apr-2012','OUT', NULL ,76 ,NULL
;WITH remaining AS
(
SELECT *,
CASE
WHEN trantype = 'IN' THEN 1
ELSE -1
END * qty AS stock_shift,
ROW_NUMBER() OVER(PARTITION BY item ORDER BY transdate) AS row,
CASE
WHEN trantype = 'OUT' THEN NULL
ELSE ROW_NUMBER()OVER(PARTITION BY item, CASE WHEN trantype = 'IN' THEN 0 ELSE 1 END ORDER BY transdate)
END AS in_row,
SUM(CASE WHEN trantype = 'OUT' THEN qty END) OVER(PARTITION BY item) AS total_out
FROM #sample
)
,remaining2 AS
(
SELECT r1.item,
r1.stockid,
MAX(r1.transdate) AS transdate,
MAX(r1.trantype) AS trantype,
MAX(r1.batchno) AS batchno,
MAX(r1.qty) AS qty,
MAX(r1.price) AS price,
MAX(r1.total_out) AS total_out,
MAX(r1.in_row) AS in_row,
CASE
WHEN MAX(r1.trantype) = 'OUT' THEN NULL
WHEN SUM(CASE WHEN r1.trantype = 'IN' THEN r2.qty ELSE 0 END) - MAX(r1.total_out) < 0 THEN SUM(CASE WHEN r1.trantype = 'IN' THEN r2.qty ELSE 0 END)
- MAX(r1.total_out)
ELSE 0
END AS running_in
FROM remaining r1
LEFT OUTER JOIN remaining r2
ON r2.row <= r1.row
AND r2.item = r1.item
GROUP BY
r1.item,
r1.stockid
)
SELECT r2.item,
r2.stockid,
MAX(r2.transdate) AS transdate,
MAX(r2.trantype) AS trantype,
MAX(r2.batchno) AS batchno,
MAX(r2.qty) AS qty,
MAX(r2.price) AS price,
MAX(CASE WHEN r2.trantype = 'OUT' THEN NULL ELSE ISNULL(r2.qty + r3.running_in, 0) END) AS remaining_stock
FROM remaining2 r2
LEFT OUTER JOIN remaining2 r3
ON r2.in_row - 1 = r3.in_row
AND r2.item = r3.item
GROUP BY
r2.item,
r2.stockid
This sql is having a problem and the result is attached here The records for which the value are not matching are indicated in yellow color. Kindly help to solve the problem.
I think this should do the trick?
SELECT 10001 as stockid,'ABC' as item,'01-Apr-2012' as transdate,'IN' as trantype, 71001000 as batchno, 200 as qty, 750.0 as price INTO #sample
UNION ALL SELECT 10002 ,'ABC','02-Apr-2012','OUT', NULL ,100,NULL
UNION ALL SELECT 10003 ,'ABC','03-Apr-2012','IN', 71001001, 50 , 700.0
UNION ALL SELECT 10004 ,'ABC','04-Apr-2012','IN', 71001002, 75 , 800.0
UNION ALL SELECT 10005 ,'ABC','10-Apr-2012','OUT', NULL ,125,NULL
UNION ALL SELECT 10006 ,'XYZ','05-Apr-2012','IN', 71001003, 150 , 350.0
UNION ALL SELECT 10007 ,'XYZ','05-Apr-2012','OUT', NULL , 120 ,NULL
UNION ALL SELECT 10008 ,'XYZ','15-Apr-2012','OUT', NULL , 10 ,NULL
UNION ALL SELECT 10009 ,'XYZ','20-Apr-2012','IN', 71001004, 90 , 340.0
UNION ALL SELECT 10010 ,'PQR','06-Apr-2012','IN', 71001005, 50 , 510.0
UNION ALL SELECT 10011 ,'PQR','15-Apr-2012','IN', 71001006, 60 , 505.0
UNION ALL SELECT 10012 ,'MNO','01-Apr-2012','IN', 71001007, 76 , 410.0
UNION ALL SELECT 10013,'MNO','11-Apr-2012','OUT', NULL ,76 ,NULL
;with remaining_stock as
(
SELECT *
,CASE WHEN trantype = 'IN' THEN 1 ELSE -1 END * qty AS stock_shift
,row_number() OVER (PARTITION BY item ORDER BY transdate) as row
,CASE WHEN trantype = 'OUT' THEN NULL ELSE
row_number()OVER (PARTITION BY item,CASE WHEN trantype = 'IN' THEN 0 ELSE 1 END ORDER BY transdate) END as in_row
,CASE WHEN trantype = 'IN' THEN NULL ELSE
row_number()OVER (PARTITION BY item,CASE WHEN trantype = 'OUT' THEN 0 ELSE 1 END ORDER BY transdate) END as out_row
,ISNULL(SUM(CASE WHEN trantype = 'OUT' THEN qty END) OVER (PARTITION BY item),0) AS total_out
,ISNULL(SUM(CASE WHEN trantype = 'IN' THEN qty END) OVER (PARTITION BY item),0) AS total_in
FROM #sample
)
,remaining_stock2 AS
(
SELECT
r1.item
,r1.stockid
,MAX(r1.transdate) as transdate
,MAX(r1.trantype) as trantype
,MAX(r1.batchno) as batchno
,MAX(r1.qty) as qty
,MAX(r1.price) as price
,MAX(r1.total_in) as total_in
,MAX(r1.total_out) as total_out
,SUM(r2.qty) as running_in
FROM remaining_stock r1
LEFT OUTER JOIN remaining_stock r2 on r2.in_row <= r1.in_row
AND r2.item = r1.item
GROUP BY
r1.item
,r1.stockid
)
SELECT
item
,stockid
,transdate
,trantype
,batchno
,qty
,price
,CASE WHEN trantype = 'OUT' THEN NULL
WHEN total_out >= running_in THEN 0
WHEN (running_in - total_out) < qty THEN (running_in - total_out)
WHEN (running_in - total_out) >= qty THEN qty
END as remaining_stocks
FROM remaining_stock2
Your question isn't very clear to me on how the FIFO logic is to be applied. I'm going to assume that you want to associate each IN record against the next OUT record if one exists. To achieve this you need to join the table on itself like the following
select
t1.BatchNo,
isnull(t1.Qty,0) as 'IN Qty',
isnull(t2.Qty,0) as 'OUT Qty',
isnull(t1.Qty,0) - isnull(t2.Qty,0) as 'Remaining Qty'
from
tbl_test t1
left join tbl_test t2
on t2.StockID = (t1.StockID + 1)
and t2.TranType = 'OUT'
where
t1.TranType = 'IN'
The results will show you the following for the first 5 records for ABC from your question.
BatchNo | IN Qty | OUT Qty | Remaining Qty
71001000 | 200 | 100 | 100
71001001 | 50 | 0 | 50
71001002 | 75 | 125 | -50
The left join works on the assumption that the StockID for each IN record is always one less number than the associated OUT record. I personally think your data model needs improving.
OUT records should have a BatchNo assigned or a reference to the
StockID of the associated IN record
add a timestamp field for sequential ordering
add a DateTime field for handling IN/OUT occuring on same day