SQL select case when and group - postgresql

I have this table
I want to group by age with case and count the gender type
This case:
age <= 20 then 'Group <= 20'
age between 21-40 then 'Group 21-40'
age between 41-60 then 'Group 41-60'
age > 60 then 'Group > 60'
I've tried this code but I get an error:
%%sql
select customer_id, birthdate, extract('year' from current_date) - extract('year' from birthdate ) age
case when age <= 20 then 'Group <= 20'
when age between 21 and 40 then 'Group 21 - 40'
when age between 41 and 60 then 'Group 41 - 60'
else 'Group > 60' end gender
from dim_customer
group by 1
Any solution? Thanks in advance.
BTW: I use this code in Python

Alias columns are not used in SELECT but it's used at GROUP BY and ORDER BY clause.
-- PostgreSQL
SELECT t.*
, CASE WHEN age <= 20 THEN 'Group <= 20'
WHEN age BETWEEN 21 AND 40 THEN 'Group 21 - 40'
WHEN age BETWEEN 41 AND 60 THEN 'Group 41 - 60'
ELSE 'Group > 60' END gender
FROM (SELECT customer_id, birthdate
, extract('year' from current_date) - extract('year' from birthdate ) age
from dim_customer) t
Please check from url https://dbfiddle.uk/?rdbms=postgres_11&fiddle=38c6ba05adc779aed3063e490bcc6376
N.B.: Use date_part('year', current_timestamp :: DATE) - date_part('year', birthdate) for age calculation.
Count gender and use alias at group by section
SELECT CASE WHEN age <= 20 THEN 'Group <= 20'
WHEN age BETWEEN 21 AND 40 THEN 'Group 21 - 40'
WHEN age BETWEEN 41 AND 60 THEN 'Group 41 - 60'
ELSE 'Group > 60' END gender
, COUNT(t.gender) count_gender
, COUNT(CASE WHEN t.gender = 'M' THEN 1 END) gen_male
, COUNT(CASE WHEN t.gender = 'F' THEN 1 END) gen_female
FROM (SELECT customer_id, birthdate, gender
, extract('year' from current_date) - extract('year' from birthdate ) age
, date_part('year', current_timestamp :: DATE) - date_part('year', birthdate) age1
from dim_customer) t
GROUP BY CASE WHEN age <= 20 THEN 'Group <= 20'
WHEN age BETWEEN 21 AND 40 THEN 'Group 21 - 40'
WHEN age BETWEEN 41 AND 60 THEN 'Group 41 - 60'
ELSE 'Group > 60' END
Please check from url https://dbfiddle.uk/?rdbms=postgres_11&fiddle=ad8af9dd9b82ede20452377615ca7fde

Related

Get Data Week Wise in SQL Server

I have a Table with columns ProductId, DateofPurchase, Quantity.
I want a report in which week it belongs to.
Suppose if I give March Month I can get the quantity for the march month.
But I want as below if I give date as parameter.
Here Quantity available for March month on 23/03/2018 is 100
Material Code Week1 Week2 Week3 Week4
12475 - - - 100
The logic is 1-7 first week, 8-15 second week, 16-23 third week, 24-30 fourth week
#Sasi, this can get you started. YOu will need to use CTE to build a template table that describes what happens yearly. Then using your table with inner join you can link it up and do a pivot to group the weeks.
Let me know if you need any tweaking.
DECLARE #StartDate DATE='20180101'
DECLARE #EndDate DATE='20180901'
DECLARE #Dates TABLE(
Workdate DATE Primary Key
)
DECLARE #tbl TABLE(ProductId INT, DateofPurchase DATE, Quantity INT);
INSERT INTO #tbl
SELECT 12475, '20180623', 100
;WITH Dates AS(
SELECT Workdate=#StartDate,WorkMonth=DATENAME(MONTH,#StartDate),WorkYear=YEAR(#StartDate), WorkWeek=datename(wk, #StartDate )
UNION ALL
SELECT CurrDate=DateAdd(WEEK,1,Workdate),WorkMonth=DATENAME(MONTH,DateAdd(WEEK,1,Workdate)),YEAR(DateAdd(WEEK,1,Workdate)),datename(wk, DateAdd(WEEK,1,Workdate)) FROM Dates D WHERE Workdate<#EndDate ---AND (DATENAME(MONTH,D.Workdate))=(DATENAME(MONTH,D.Workdate))
)
SELECT *
FROM
(
SELECT
sal.ProductId,
GroupWeek='Week'+
CASE
WHEN WorkWeek BETWEEN 1 AND 7 THEN '1'
WHEN WorkWeek BETWEEN 8 AND 15 THEN '2'
WHEN WorkWeek BETWEEN 16 AND 23 THEN '3'
WHEN WorkWeek BETWEEN 24 AND 30 THEN '4'
WHEN WorkWeek BETWEEN 31 AND 37 THEN '5'
WHEN WorkWeek BETWEEN 38 AND 42 THEN '6'
END,
Quantity
FROM
Dates D
JOIN #tbl sal on
sal.DateofPurchase between D.Workdate and DateAdd(DAY,6,Workdate)
)T
PIVOT
(
SUM(Quantity) FOR GroupWeek IN (Week1, Week2, Week3, Week4, Week5, Week6, Week7, Week8, Week9, Week10, Week11, Week12, Week13, Week14, Week15, Week16, Week17, Week18, Week19, Week20, Week21, Week22, Week23, Week24, Week25, Week26, Week27, Week28, Week29, Week30, Week31, Week32, Week33, Week34, Week35, Week36, Week37, Week38, Week39, Week40, Week41, Week42, Week43, Week44, Week45, Week46, Week47, Week48, Week49, Week50, Week51, Week52
/*add as many as you need*/)
)p
--ORDER BY
--1
option (maxrecursion 0)
Sample Data :
DECLARE #Products TABLE(Id INT PRIMARY KEY,
ProductName NVARCHAR(50))
DECLARE #Orders TABLE(ProductId INT,
DateofPurchase DATETIME,
Quantity BIGINT)
INSERT INTO #Products(Id,ProductName)
VALUES(1,N'Product1'),
(2,N'Product2')
INSERT INTO #Orders( ProductId ,DateofPurchase ,Quantity)
VALUES (1,'2018-01-01',130),
(1,'2018-01-09',140),
(1,'2018-01-16',150),
(1,'2018-01-24',160),
(2,'2018-01-01',30),
(2,'2018-01-09',40),
(2,'2018-01-16',50),
(2,'2018-01-24',60)
Query :
SELECT P.Id,
P.ProductName,
Orders.MonthName,
Orders.Week1,
Orders.Week2,
Orders.Week3,
Orders.Week4
FROM #Products AS P
INNER JOIN (SELECT O.ProductId,
SUM((CASE WHEN DATEPART(DAY,O.DateofPurchase) BETWEEN 1 AND 7 THEN O.Quantity ELSE 0 END)) AS Week1,
SUM((CASE WHEN DATEPART(DAY,O.DateofPurchase) BETWEEN 8 AND 15 THEN O.Quantity ELSE 0 END)) AS Week2,
SUM((CASE WHEN DATEPART(DAY,O.DateofPurchase) BETWEEN 16 AND 23 THEN O.Quantity ELSE 0 END)) AS Week3,
SUM((CASE WHEN DATEPART(DAY,O.DateofPurchase) >= 24 THEN O.Quantity ELSE 0 END)) AS Week4,
DATENAME(MONTH,O.DateofPurchase) AS MonthName
FROM #Orders AS O
GROUP BY O.ProductId,DATENAME(MONTH,O.DateofPurchase)) AS Orders ON P.Id = Orders.ProductId
Result :
-----------------------------------------------------------------------
| Id | ProductName | MonthNumber | Week1 | Week2 | Week3 | Week4 |
-----------------------------------------------------------------------
| 1 | Product1 | January | 130 | 140 | 150 | 160 |
| 2 | Product2 | January | 30 | 40 | 50 | 60 |
-----------------------------------------------------------------------

ORDER BY the Items Within a Case Statement

Is there another way I can sort the results by the Age Group appropriately? As a work-around, I placed a character before each age group to display in chronological order - but if the letter is not there, then it does not display in the order I am expecting. Here is the T-SQL:
WITH AgeData
AS ( SELECT DATEDIFF(YEAR, birthDate, GETDATE()) - CASE WHEN GETDATE() < DATEADD(YEAR , DATEDIFF(YEAR, birthDate, GETDATE()), birthDate )
THEN 1
ELSE 0
END AS [Age]
FROM dbo.Customers ) ,
GroupAge
AS ( SELECT [Age] ,
CASE WHEN AGE < 4 THEN 'a0 - 3'
WHEN AGE BETWEEN 4 AND 8 THEN 'b4 - 8'
WHEN AGE BETWEEN 9 AND 12 THEN 'c9 - 12'
WHEN AGE BETWEEN 13 AND 17 THEN 'd13 - 17'
WHEN AGE BETWEEN 18 AND 22 THEN 'e18 - 22'
WHEN AGE BETWEEN 23 AND 26 THEN 'f23 - 26'
WHEN AGE BETWEEN 27 AND 33 THEN 'g27 - 33'
WHEN AGE BETWEEN 34 AND 40 THEN 'h34 - 40'
WHEN AGE BETWEEN 41 AND 50 THEN 'i41 - 50'
WHEN AGE BETWEEN 51 AND 60 THEN 'j51 - 60'
WHEN AGE BETWEEN 61 AND 65 THEN 'k61 - 65'
WHEN AGE BETWEEN 66 AND 74 THEN 'l66 - 74'
WHEN AGE > 75 THEN 'm75+'
ELSE 'nInvalid Birthdate'
END AS [AgeGroups]
FROM AgeData
)
SELECT COUNT(*) AS [AgeGroupCount] ,
[AgeGroups]
FROM GroupAge
GROUP BY GroupAge.[AgeGroups]
ORDER BY GroupAge.[AgeGroups];
Without the character such as 'a', 'b', 'c', etc... my result set looks like:
If possible, I'd like to sort correctly without the work-around of using a letter.
You could add one more column to order item
WITH AgeData
AS (
SELECT DATEDIFF(YEAR, birthDate, GETDATE()) -
CASE
WHEN GETDATE() < DATEADD(YEAR , DATEDIFF(YEAR, birthDate, GETDATE()), birthDate )
THEN 1
ELSE 0
END AS [Age]
FROM dbo.Customers ) ,
GroupAge
AS ( SELECT [Age] ,
CASE WHEN AGE < 4 THEN '0 - 3'
WHEN AGE BETWEEN 4 AND 8 THEN '4 - 8'
WHEN AGE BETWEEN 9 AND 12 THEN '9 - 12'
WHEN AGE BETWEEN 13 AND 17 THEN '13 - 17'
WHEN AGE BETWEEN 18 AND 22 THEN '18 - 22'
WHEN AGE BETWEEN 23 AND 26 THEN '23 - 26'
WHEN AGE BETWEEN 27 AND 33 THEN '27 - 33'
WHEN AGE BETWEEN 34 AND 40 THEN '34 - 40'
WHEN AGE BETWEEN 41 AND 50 THEN '41 - 50'
WHEN AGE BETWEEN 51 AND 60 THEN '51 - 60'
WHEN AGE BETWEEN 61 AND 65 THEN '61 - 65'
WHEN AGE BETWEEN 66 AND 74 THEN 'l66 - 74'
WHEN AGE > 75 THEN 'm75+'
ELSE 'nInvalid Birthdate'
END AS [AgeGroups],
CASE WHEN AGE < 4 THEN 1
WHEN AGE BETWEEN 4 AND 8 THEN 2
WHEN AGE BETWEEN 9 AND 12 THEN 3
WHEN AGE BETWEEN 13 AND 17 THEN 4
WHEN AGE BETWEEN 18 AND 22 THEN 5
WHEN AGE BETWEEN 23 AND 26 THEN 6
WHEN AGE BETWEEN 27 AND 33 THEN 7
WHEN AGE BETWEEN 34 AND 40 THEN 8
WHEN AGE BETWEEN 41 AND 50 THEN 9
WHEN AGE BETWEEN 51 AND 60 THEN 10
WHEN AGE BETWEEN 61 AND 65 THEN 11
WHEN AGE BETWEEN 66 AND 74 THEN 12
WHEN AGE > 75 THEN 13
ELSE 14
END AS [AgeGroupId]
FROM AgeData
)
SELECT COUNT(*) AS [AgeGroupCount] ,
[AgeGroups]
FROM GroupAge
GROUP BY GroupAge.[AgeGroups],[AgeGroupId]
ORDER BY GroupAge.[AgeGroupId]
Another solution: Use a temp table that contains group information
DECLARE #GroupAge AS TABLE
(
GroupID int,
StartAge int,
EndAge int,
GroupName AS CONCAT(StartAge, '-', EndAge)
)
INSERT INTO #GroupAge
(
GroupID,
StartAge,
EndAge
)
VALUES (1,0,3) -- insert all groups you need
;WITH AgeData
AS (
SELECT DATEDIFF(YEAR, birthDate, GETDATE()) -
CASE
WHEN GETDATE() < DATEADD(YEAR , DATEDIFF(YEAR, birthDate, GETDATE()), birthDate )
THEN 1
ELSE 0
END AS [Age]
FROM dbo.Customers )
SELECT COUNT(*) AS [AgeGroupCount] ,
ga.GroupName
FROM AgeData a
INNER JOIN #GroupAge ga ON ( a.Age BETWEEN ga.StartAge AND ga.EndAge)
GROUP BY ga.GroupID, ga.GroupName
Order By ga.GroupID
In your ORDER BY clause, just add the following line:
IIF([AgeGroups] = 'Invalid Birthdate', 999, CAST(LEFT([AgeGroups], CHARINDEX('-', REPLACE([AgeGroups], '+', '-'))- 1) AS INT))
This is full working example:
DECLARE #DataSource TABLE
(
[AgeGroups] VARCHAR(18)
);
INSERT INTO #DataSource
VALUES ('0-3')
,('13-17')
,('18-22')
,('23-26')
,('27-33')
,('34-40')
,('4-8')
,('41-50')
,('51-60')
,('61-65')
,('66-74')
,('75+')
,('9-12')
,('Invalid Birthdate');
SELECT *
,IIF([AgeGroups] = 'Invalid Birthdate', 999, CAST(LEFT([AgeGroups], CHARINDEX('-', REPLACE([AgeGroups], '+', '-'))- 1) AS INT))
FROM #DataSource
ORDER BY IIF
(
[AgeGroups] = 'Invalid Birthdate'
,999
,CAST(LEFT([AgeGroups], CHARINDEX('-', REPLACE([AgeGroups], '+', '-'))- 1) AS INT)
);
The idea is to get the start number for each range, covert it to number and sort by it. We just need to add extra check for the Invalid Birthdate string and replace the + with - for the 75+ value.

Number of entries between dates

I have a table with the following structure: -
day, id
2016-03-13, 123
2016-03-13, 123
2016-03-13, 231
2016-03-14, 231
2016-03-14, 231
2016-03-15, 129
And I'd like to build a table that looks like: -
id, d1, d7, d14
123, 1, 1, 1
231, 1, 2, 2
129, 1, 1, 1
Essentially for a given id, list the number of days which have an entry within a time window. So if id 123 has 10 entries within the last 14 days - d14 would be 10.
So far I have: -
SELECT
day,
id
FROM
events
WHERE
datediff (DAY, day, getdate()) <= 7
GROUP BY
day,
id
This query will do:
SELECT
id,
COUNT(DISTINCT CASE WHEN current_date - day <= 1 THEN 1 END) d1,
COUNT(DISTINCT CASE WHEN current_date - day <= 7 THEN 1 END) d7,
COUNT(DISTINCT CASE WHEN current_date - day <= 14 THEN 1 END) d14
FROM
events
GROUP BY
id
ORDER BY
id
Or, since PostgreSQL 9.4, slightly more concise:
SELECT
id,
COUNT(DISTINCT day) FILTER (WHERE current_date - day <= 1) d1,
COUNT(DISTINCT day) FILTER (WHERE current_date - day <= 7) d7,
COUNT(DISTINCT day) FILTER (WHERE current_date - day <= 14) d14
FROM
events
GROUP BY
id
ORDER BY
id
try this:
SELECT id
, count(case when DAY = getdate() then 1 else null end) as d1
, count(case when DAY + 7 >= getdate() then 1 else null end) as d7
, count(case when DAY + 14 >= getdate() then 1 else null end) as d14
FROM events
WHERE DAY between DAY >= getdate() - 14
--or if you can have day > today ... and DAY between getdate() - 14 and getdate()
GROUP By id

Categorizing Data with SQL Statement to Import Data

This is for SQL Server 2008 R2.
I have a table PEOPLE with a columns NAME and AGE. I am importing with a SQL Job to another table LEADS with the same columns NAME and AGE, and with an additional column, AGEGROUP where I want that
Ages < 20 go into Group A
Ages 21-39 go into Group B
Ages 40-59 go into Group C
Ages >= 60 go into Group D
How would that SQL Statement be?
Thanks.
This insert would do what you want (your logic would have missed anyone that was 20, so I put them in Group A)
INSERT INTO LEADS (NAME, AGE, AGEGROUP)
SELECT
NAME
, AGE
, CASE
WHEN AGE <= 20 THEN 'A'
WHEN AGE > 20 AND AGE < 40 THEN 'B'
WHEN AGE >= 40 AND AGE < 60 THEN 'C'
WHEN AGE >= 60 THEN 'D'
END AS AGEGROUP
FROM PEOPLE
INSERT INTO LEADS (Name , Age , AgeGroup)
SELECT Name
,Age
,CASE WHEN Age <= 20 THEN 'A'
WHEN Age > 20 AND Ages < 40 THEN 'B'
WHEN Age >= 40 AND Ages < 60 THEN 'C'
WHEN Age >=60 THEN 'D'
END
FROM People

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