How to design T-SQL query to calculate sum in one pass? - tsql

I am trying to develop a T-SQL query which will do the following:
ROUND(100 * A / B, 1)
Simple in concept, but it's tricky because of possible B=0 denominator and also because of A and B variables. What I expect is a percent value like 93.2 (given in this format without %). Or even 932 would be acceptable since I could convert it later.
But instead, I'm currently getting 151, which is the number of records.
A = CASE WHEN A.MFG IS NULL AND A.MFG2 IS NULL AND A.QC IS NULL AND A.QC2 IS NULL THEN 1 ELSE 0 END
B = CASE WHEN [Date_Completed] IS NOT NULL THEN 1 ELSE 0 END
My current logic only divides A/B if B is not equal to zero. Can you please help me fix this? p.s. all fields above are from the same table A.
I tried:
SELECT CASE WHEN t.VarB<>0 THEN ROUND(100 * t.VarA / t.VarB, 1)
ELSE 0 /* or whatever you'd want to return in this case */
END
FROM (SELECT CASE WHEN A.MFG IS NULL AND A.MFG2 IS NULL AND A.QC IS NULL AND A.QC2 IS NULL THEN 1
ELSE 0
END AS VarA,
CASE WHEN [Date_Completed] IS NOT NULL THEN 1
ELSE 0
END AS VarB
FROM EXCEL.Batch_Records A) t
But I got 33000 rows returned instead of just one, where each row = 100 or 0.
Good idea, Conrad! I tested your solution and it works if I just want that one value. But what I didn't tell you was that there are additional values I need returned from same query. When I tried adding in the other value calculations, I got syntax errors. So here is my current query. How should htis be rewritten please?
select
SUM(CASE WHEN A.DATE_RECEIVED IS NOT NULL THEN 1 ELSE 0 END) AS NUM_RECEIVED,
SUM(CASE WHEN [Date_Completed] IS NOT NULL THEN 1 ELSE 0 END) AS NUM_COMPLETE_OF_OPENED,
SUM(CASE WHEN A.DATE_COMPLETED IS NOT NULL THEN 1 ELSE 0 END) AS NUM_COMPLETED_IN_MONTH,
SUM(CASE WHEN A.MFG IS NULL AND A.MFG2 IS NULL AND A.QC IS NULL AND A.QC2 IS NULL THEN 1 ELSE 0 END) AS NUM_WITHOUT_ERROR,
round(100 * a/b , 1)
from
(select
sum(CASE
WHEN A.MFG IS NULL AND A.MFG2 IS NULL AND A.QC IS NULL AND A.QC2 IS NULL THEN
1.0
ELSE 0.0 END) A,
sum(CASE WHEN [Date_Completed] IS NOT NULL THEN
1.0 ELSE 0.0 END) B
FROM EXCEL.Batch_Records a
LEFT JOIN EXCEL.QC_CODES d ON a.Part_Number = d.CODE_ID
WHERE (a.[Group] = #GROUP or #GROUP = '' OR #GROUP IS NULL) AND A.Date_Received >= #STARTDATE AND A.Date_Received <= #ENDDATE
Conrad correctly advised me that #TEMP1 was an empty table. But now I populated it and successfully designed this query with his help:
SET #STARTDATE = '1/1/11'
SET #ENDDATE = '1/31/11'
SET #GROUP = 'INTERMEDIATES_FISH'
--SET #TABLE_TITLE = 'BATCH RECORD SUCCESS RATE'
--SET #DEPT = 'QC'
IF EXISTS(SELECT * FROM TEMPDB.INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME LIKE '#TEMP1%')
DROP TABLE #TEMP1
--CREATE TABLE #TEMP1 ( MFG int , MFG2 int , QC int, QC2 INT , [Group] NVARCHAR(MAX), [Date_Completed] datetime, Date_Received datetime)
SELECT
MFG, MFG2, QC, QC2, [GROUP], [DATE_COMPLETED], [DATE_RECEIVED]
INTO #TEMP1
FROM EXCEL.Batch_Records a
WHERE (a.[Group] = #GROUP or #GROUP = '' OR #GROUP IS NULL) AND A.Date_Received >= #STARTDATE AND A.Date_Received <= #ENDDATE
------------------------------------------
;WITH CTE AS
(
SELECT
CASE
WHEN A.MFG IS NULL AND A.MFG2 IS NULL AND A.QC IS NULL AND A.QC2 IS NULL THEN
1.0
ELSE 0.0 END A,
CASE WHEN [Date_Completed] IS NOT NULL THEN 1.0 ELSE 0.0 END B,
CASE WHEN A.Date_Received IS NOT NULL THEN 1 ELSE 0 END NUM_RECEIVED,
CASE WHEN [Date_Completed] IS NOT NULL THEN 1 ELSE 0 END NUM_COMPLETE_OF_OPENED,
CASE WHEN A.DATE_COMPLETED IS NOT NULL THEN 1 ELSE 0 END NUM_COMPLETED_IN_MONTH,
CASE WHEN A.MFG IS NULL AND A.MFG2 IS NULL AND A.QC IS NULL AND A.QC2 IS NULL THEN 1 ELSE 0 END AS NUM_WITHOUT_ERROR
FROM
#TEMP1 a
--WHERE (a.[Group] = #GROUP or #GROUP = '' OR #GROUP IS NULL) AND A.Date_Received >= #STARTDATE AND A.Date_Received <= #ENDDATE
)
select
round(100 * SUM(A)/SUM(b) , 1) ,
SUM(NUM_RECEIVED) NUM_RECEIVED,
SUM(NUM_COMPLETE_OF_OPENED) NUM_COMPLETE_OF_OPENED,
SUM(NUM_COMPLETED_IN_MONTH) NUM_COMPLETED_IN_MONTH,
SUM(NUM_WITHOUT_ERROR) NUM_WITHOUT_ERROR
FROM CTE

Basically you need to use SUM() to get the sum. You should also use 1.0 and 0.0 so you get decimal values.
You should also do the SUM before the Division
UPDATE
Since you're adding in a number of SUM(CASE statements its probably more readable to move the CASE statments out to a CTE.
CREATE TABLE #Batch_Records (
MFG int ,
MFG2 int ,
QC int,
QC2 INT ,
[Group] int,
[Date_Completed] datetime,
Date_Received datetime)
INSERT INTO #Batch_Records (MFG , MFG2 , QC , QC2 , [Group] , [Date_Completed] , Date_Received )
VALUES (1,null,null,null,1,'1/4/2011','2/4/2011'),
(null,null,null,null,1,'2/2/2011','3/4/2011'),
(1,null,null,null,1,'3/6/2011','4/3/2011'),
(null,null,null,null,1,NULL,'5/4/2011'),
(1,null,null,null,1,'5/4/2011','6/6/2011'),
(1,null,null,null,1,NULL,'7/4/2011')
DECLARE #GROUP int
DECLARE #STARTDATE DateTime
DECLARE #ENDDATE DateTime
SET #GROUP = 1
SET #STARTDATE = '1/1/2001'
SET #ENDDATE = '1/1/2012'
;WITH CTE AS
(
SELECT
CASE
WHEN A.MFG IS NULL AND A.MFG2 IS NULL AND A.QC IS NULL AND A.QC2 IS NULL THEN
1.0
ELSE 0.0 END A,
CASE WHEN [Date_Completed] IS NOT NULL THEN
1.0 ELSE 0.0 END B,
CASE WHEN A.Date_Received IS NOT NULL THEN 1 ELSE 0 END NUM_RECEIVED,
CASE WHEN [Date_Completed] IS NOT NULL THEN 1 ELSE 0 END NUM_COMPLETE_OF_OPENED,
CASE WHEN A.DATE_COMPLETED IS NOT NULL THEN 1 ELSE 0 END NUM_COMPLETED_IN_MONTH,
CASE WHEN A.MFG IS NULL AND A.MFG2 IS NULL AND A.QC IS NULL AND A.QC2 IS NULL THEN 1 ELSE 0 END AS NUM_WITHOUT_ERROR
FROM
#Batch_Records a
WHERE
(a.[Group] = #GROUP or #GROUP = '' OR #GROUP IS NULL)
AND A.Date_Received >= #STARTDATE AND A.Date_Received <= #ENDDATE
)
select
round(100 * SUM(A)/SUM(b) , 1) ,
SUM(NUM_RECEIVED) NUM_RECEIVED,
SUM(NUM_COMPLETE_OF_OPENED) NUM_COMPLETE_OF_OPENED,
SUM(NUM_COMPLETED_IN_MONTH) NUM_COMPLETED_IN_MONTH,
SUM(NUM_WITHOUT_ERROR) NUM_WITHOUT_ERROR
FROM CTE
DROP TABLE #Batch_Records

Related

Pivot and aggregate 1..n duplicate records Postgres psql

I have a table tbl_action like this:
game_id
action
action_datetime
1
start
2022-04-05T10:30+00
1
attack
2022-04-05T10:45+00
1
defend
2022-04-05T11:30+00
1
attack
2022-04-05T11:45+00
1
defend
2022-04-05T12:00+00
1
stop
2022-04-05T12:10+00
create table if not exists tblaction;
insert into "tblaction" (game_id, action_name, action_time) values (1,'start','2022-04-05T10:30+00'),
(2,'attack','2022-04-05T10:45+00'),
(3,'defend','2022-04-05T11:30+00'),
(4,'attack','2022-04-05T11:45+00'),
(5,'defend','2022-04-05T12:00+00'),
(6,'stop','2022-04-05T12:10+00');
I want to pivot it like this:
game_id
start
attack1
defend1
...
attackn
defendn
stop
1
2022-04-05T10:30+00
2022-04-05T10:45+00
2022-04-05T11:30+00
...
2022-04-05T11:45+00
2022-04-05T12:00+00
2022-04-05T12:10+00
My question is how to aggregate actions 1..n instead of aggregating all of each unique action_name into one number. I am using Postgres.
This MySQL sample code works when each action_name occurs <2 times but I would like something that works 1..n. I don't want to remove the duplicates. I am not limited to MySQL I can use the latest versions of Postgres.
SELECT
game_id,
MAX( CASE WHEN action_name = 'start' THEN action_time ELSE NULL END ) AS "start",
MIN( CASE WHEN action_name = 'attack' THEN action_time ELSE NULL END ) AS "attack",
MIN( CASE WHEN action_name = 'defend' THEN action_time ELSE NULL END ) AS "defend",
MAX( CASE WHEN action_name = 'attack' THEN action_time ELSE NULL END ) AS "attack",
MAX( CASE WHEN action_name = 'defend' THEN action_time ELSE NULL END ) AS "defend",
MAX( CASE WHEN action_name = 'stop' THEN action_time ELSE NULL END ) AS "stop"
FROM
tblaction
GROUP BY
game_id
ORDER BY
game_id ASC;
I read the Postgres tablefunc docs and tried \crosstabview in pgadmin4 and get the error \crosstabview: query result contains multiple data values for row "1", column "attack"
SELECT game_id, action_name, action_time FROM tblaction \crosstabview
You can try to use subquery with ROW_NUMER window function which help you make row number by game_id, action_name columns, then you can use condition aggregate function for that rn
SELECT
game_id,
MAX(CASE WHEN action_name = 'start' THEN action_time END ) AS "start",
MAX(CASE WHEN action_name = 'attack' AND rn = 1 THEN action_time END ) AS "attack1",
MAX(CASE WHEN action_name = 'defend' AND rn = 1 THEN action_time END ) AS "defend1",
MAX(CASE WHEN action_name = 'attack' AND rn = 2 THEN action_time END ) AS "attack",
MAX(CASE WHEN action_name = 'defend' AND rn = 2 THEN action_time END ) AS "defend",
MAX(CASE WHEN action_name = 'stop' THEN action_time END ) AS "stop"
FROM
(
SELECT *,ROW_NUMBER() OVER(PARTITION BY game_id,action_name ORDER BY action_time) rn
FROM tblaction
) t1
GROUP BY
game_id
ORDER BY
game_id ASC;
sqlfiddle
I suggest that it will be simpler and more flexible with GROUP_CONCAT.
NB The question said to work on mySQL or Postgres. See https://dbfiddle.uk/?rdbms=postgres_10&fiddle=8719aedb7ab5736caf79fe86e76d6f40 for a version for Postgres using STRING_AGG( ~ ,',') instead of GROUP_CONCAT, otherwise identical
create table tblaction (
game_id int,
action_name varchar(20),
action_time varchar(25)
);
insert into tblaction
(game_id, action_name, action_time) values
(1,'start','2022-04-05T10:30+00'),
(1,'attack','2022-04-05T10:45+00'),
(1,'defend','2022-04-05T11:30+00'),
(1,'attack','2022-04-05T11:45+00'),
(1,'defend','2022-04-05T12:00+00'),
(1,'stop','2022-04-05T12:10+00');
✓
✓
SELECT
game_id,
GROUP_CONCAT(case when action_name='start' then action_time end) start,
GROUP_CONCAT(case when action_name='stop' then action_time end) stop,
COUNT(action_time) "number",
GROUP_CONCAT(case when action_name='attack' then action_time end) "attacks",
GROUP_CONCAT(case when action_name='defend' then action_time end) "defends"
FROM tblaction
GROUP BY
game_id;
game_id | start | stop | number | attacks | defends
------: | :------------------ | :------------------ | -----: | :-------------------------------------- | :--------------------------------------
1 | 2022-04-05T10:30+00 | 2022-04-05T12:10+00 | 6 | 2022-04-05T10:45+00,2022-04-05T11:45+00 | 2022-04-05T11:30+00,2022-04-05T12:00+00
db<>fiddle here

Multiple case in update postgres

I need to update 2 columns in table with same conditions. I know, that each of them would take a lot of time. How can I concatenate 2 updates into 1, which can be faster?
-- first update
update t1
set col1 =
case when cc1 is not NULL and cc1 <> 0 then 'A'
when cc2 is not NULL and cc2 <> 0 then 'B'
when cc3 is not NULL and cc3 <> 0 then 'C'
else null
end;
-- with same cond
update t1
set col2 =
case when cc1 is not NULL and cc1 <> 0 then 'qwe rty'
when cc2 is not NULL and cc2 <> 0 then 'qzaz wsx'
when cc3 is not NULL and cc3 <> 0 then 'zxcv asdf'
else 'pl ok'
end;
-- my effort to concatenate, dont work
update t1
set (col1, col2) =
(select c1, c2 from
(select case when t2.cc1 is not NULL and t2.cc1 <> 0 then 'A' as c1, 'qwe rty' as c2
when t2.cc2 is not NULL and t2.cc2 <> 0 then ('B', 'qaz wsx')
when t2.cc3 is not NULL and t2.cc3 <> 0 then ('C', ' zxcv asdf')
else (null, 'pl ok')
end
from t1 as t2 where t1.key_column1 = t2.key_column1 and t1.key_column2 = t2.key_column2 and t1.key_column3 = t2.key_column3) f)
;
This is the way I would do it.
WITH cte AS (SELECT * FROM
(VALUES(1, 'A', 'qwe rty'),(2, 'B', 'qaz wsx'),(3, 'C', 'zxcv asdf'),(4, NULL, 'pl ok')) v (id,c1,c2))
UPDATE so_demo
SET col1 = cte.c1, col2 = cte.c2
FROM cte WHERE cte.id = CASE WHEN COALESCE(cc1, 0) <> 0 THEN 1
WHEN COALESCE(cc2, 0) <> 0 THEN 2
WHEN COALESCE(cc3, 0) <> 0 THEN 3
ELSE 4 END;
By way of explanation, I have put the possible values into a cte assigning them an id in addition to the values. I can then put the case statement in the where clause generating the necessary id. Note the use of COALESCE to make the WHENs simpler to read.
One way is to use arrays.
UPDATE t1
SET (col1,
col2) = (SELECT x[1],
x[2]
FROM (SELECT CASE
WHEN cc1 IS NOT NULL
AND cc1 <> 0 THEN
ARRAY['A',
'qwe rty']
WHEN cc2 IS NOT NULL
AND cc2 <> 0 THEN
ARRAY['B',
'qzaz wsx']
...
ELSE
ARRAY[NULL,
'pl ok']
END) AS x
(x));
But in terms of runtime optimization the gain compared to just UPDATE ... SET col1 = CASE ..., col2 = CASE ... should be neglectable, if any.

T-SQL Different Comparison in WHERE Clause Using CASE

I wanted to do different comparison expression in where clause, but it keep prompt me invalid syntax. The reason i want to use this because i wanted to enforce the evaluation according to the Type. If i use ((t1.Amount <= #valueB) OR (t1.Type IN (1) AND t1.Amount > 0 AND #valueB < 0)) , it seems to be not expression i trying to impose into my query. If first evaluation success, i would not want the following to execute, however OR in sql is not short-circuit operator.
DECLARE #name AS VARCHAR(200) = 'super'
DECLARE #valueA AS INT = 1 -- Can be 1 or 2
DECLARE #valueB AS DECIMAL = 0.5
SELECT * FROM TABLE_1 t1 WITH(NOLOCK)
WHERE t1.Name = #name
AND
CASE WHEN t1.Type = 1 THEN (t1.Amount > 0 AND #valueB < 0)
ELSE (t1.Amount <= #valueB)
END
You can't use it that way.
TRY THIS
SELECT * FROM TABLE_1 t1 WITH(NOLOCK)
WHERE t1.Name = #name AND
((t1.Type = 1 AND t1.Amount > 0 AND #valueB < 0)
OR (t1.Amount <= #valueB))
If could elaborate further what you are trying to achieve , I'll be able to help.
Zohar Peled's comment rightly points out that CASE is an expression in T-SQL, it is not a flow-of-control statement.
What you are trying to do is:
IF <expression_A> = <value_A>
THEN <apply_condition_B>
ELSE <apply_condition_C>
To achieve the following in SQL, you'd need to write it out using Boolean logic using the AND and OR operators as:
(
( <expression_A> = <value_A> AND <apply_condition_B> )
OR ( <expression_A> is not <value_A> AND <apply_condition_C> )
)
The first AND that comes before <apply_condition_B> acts like the THEN keyword, while the combination of the OR and the second AND act like the ELSE keyword.
Taking your query into consideration, we get the following mapping:
<expression_A> = t1.Type
<value_A> = 1
<apply_condition_B> = (t1.Amount > 0 AND #valueB < 0)
<apply_condition_C> = (t1.Amount <= #valueB)
Putting that into query form would give us:
WHERE t1.Name = #name
AND
(
( t1.Type = 1 AND (t1.Amount > 0 AND #valueB < 0) )
OR ( t1.Type != 1 AND (t1.Amount <= #valueB) )
)
-- Note - this is not fully complete, as it assumes t1.Type is not NULL
The above is a simple rewrite of your intended logic. However, do note that SQL operates on three-valued logic which means that t1.Type can be 1, it can be a value other than 1, or it can be NULL. The above code fragment handles only two of these 3 possibilities. To correctly handle t1.Type being NULL you can use the COALESCE() function to return a specified value (zero, below) in case it is NULL:
WHERE t1.Name = #name
AND
(
( t1.Type = 1 AND (t1.Amount > 0 AND #valueB < 0) )
OR ( COALESCE(t1.Type, 0) != 1 AND (t1.Amount <= #valueB) )
)
Tip: Before using this sort of construct directly in your actual query, I'd suggest you play around with it using simpler sample data to understand how it works.
Try this
DECLARE #name AS VARCHAR(200) = 'super'
DECLARE #valueA AS INT = 1 -- Can be 1 or 2
DECLARE #valueB AS DECIMAL = 0.5
SELECT *
FROM TABLE_1 t1 WITH (NOLOCK)
WHERE t1.NAME = #name
AND (t1.Type = 1 AND (t1.Amount > 0 AND #valueB < 0)
OR t1.Type <> 1 AND (t1.Amount <= #valueB))
GO
OR Use Dynamic SQL if you feel like it
DECLARE #name AS VARCHAR(200) = 'super'
DECLARE #valueA AS INT = 1 -- Can be 1 or 2
DECLARE #valueB AS DECIMAL = 0.5
DECLARE #SQL VARCHAR(MAX)
DECLARE #Type INT
SELECT TOP 1 #Type = t1.Type
FROM TABLE_1 t1 WITH (NOLOCK)
WHERE t1.NAME = #name
SET #SQL = 'SELECT *
FROM TABLE_1 t1 WITH (NOLOCK)
WHERE t1.NAME = #name'
SET #SQL = CASE
WHEN #Type = 1
THEN #SQL + CHAR(13)+CHAR(10) + ' AND (t1.Amount > 0 AND #valueB < 0)'
ELSE #SQL + CHAR(13)+CHAR(10) + ' AND (t1.Amount <= #valueB)'
END
--PRINT #SQL
EXEC (#SQL)
GO

Using parameters of Stored Procedure with Join's "ON" area

I have parameters like these
declare #Phl1_descr varchar(50)
SET #Phl1_descr = 'Greece'
declare #Phl2_descr varchar(50)
SET #Phl2_descr = 'Coffee & Beverages'
I want to join two tables with the above parameters (if they are not null), so I tried to do something like below in the "ON" keyword of my JOIN
ON
(CASE WHEN LEN(#Phl1_descr) > 0 THEN A.Phl1_descr ELSE B.Phl1_descr END) = B.Phl1_descr AND
(CASE WHEN LEN(#Phl2_descr) > 0 THEN A.Phl2_descr ELSE B.Phl2_descr END) = B.Phl2_descr
However if I send one of the parameters like as '', it doesn't work. Any simpler idea?
Is it posible to use simpler solution? Like:
IF #Phl1_descr IS NOT NULL AND #Phl2_descr IS NOT NULL
BEGIN
SELECT *
FROM Table1 as A
LEFT JOIN Table2 as B on A.Phl1_descr=B.Phl1_descr and A.Phl2_descr=B.Phl2_descr
END
ELSE IF #Phl1_descr IS NOT NULL AND #Phl2_descr IS NULL
BEGIN
SELECT *
FROM Table1 as A
LEFT JOIN Table2 as B on A.Phl1_descr=B.Phl1_descr
END
ELSE IF #Phl1_descr IS NULL AND #Phl2_descr IS NOT NULL
BEGIN
SELECT *
FROM Table1 as A
LEFT JOIN Table2 as B on A.Phl2_descr=B.Phl2_descr
END
So you will get a simpler execution plans and simpler logic.
You can also use ... CASE WHEN #Phl1_descr IS NULL THEN ... to check NULL values
Interesting but
B.Phl1_descr = B.Phl1_descr
not working but
ISNULL(B.Phl1_descr,'-1') = ISNULL(B.Phl1_descr,'-1')
works,
So just a simple change in the below code work it out
(CASE WHEN LEN(#Phl1_descr) > 1 THEN A.Phl1_descr ELSE ISNULL(B.Phl1_descr,'-1') END) = ISNULL(B.Phl1_descr,'-1') AND
(CASE WHEN LEN(#Phl2_descr) > 1 THEN A.Phl2_descr ELSE ISNULL(B.Phl2_descr,'-1') END) = ISNULL(B.Phl2_descr,'-1') AND

SQL Running Subtraction and Deviation

-- Just a brief of business scenario is table has been created for a good receipt.
-- So here we have good expected line with PurchaseOrder(PO) in first few line.
-- And then we receive each expected line physically and that time these quantity may be different
-- due to business case like quantity may damage and short quantity like that.
-- So we maintain a status for that eg: OK, Damage, also we have to calculate short quantity
-- based on total of expected quantity of each item and total of received line.
if object_id('DEV..Temp','U') is not null
drop table Temp
CREATE TABLE Temp
(
ID INT IDENTITY(1,1) PRIMARY KEY CLUSTERED,
Item VARCHAR(32),
PO VARCHAR(32) NULL,
ExpectedQty INT NULL,
ReceivedQty INT NULL,
[STATUS] VARCHAR(32) NULL,
BoxName VARCHAR(32) NULL
)
-- Please see first few line with PO data will be the expected lines,
-- and then rest line will be received line
INSERT INTO TEMP (Item,PO,ExpectedQty,ReceivedQty,[STATUS],BoxName)
SELECT 'ITEM01','PO-01','30',NULL,NULL,NULL UNION ALL
SELECT 'ITEM01','PO-02','20',NULL,NULL,NULL UNION ALL
SELECT 'ITEM02','PO-01','40',NULL,NULL,NULL UNION ALL
SELECT 'ITEM03','PO-01','50',NULL,NULL,NULL UNION ALL
SELECT 'ITEM03','PO-02','30',NULL,NULL,NULL UNION ALL
SELECT 'ITEM03','PO-03','20',NULL,NULL,NULL UNION ALL
SELECT 'ITEM04','PO-01','30',NULL,NULL,NULL UNION ALL
SELECT 'ITEM01',NULL,NULL,'20','OK','box01' UNION ALL
SELECT 'ITEM01',NULL,NULL,'25','OK','box02' UNION ALL
SELECT 'ITEM01',NULL,NULL,'5','DAMAGE','box03' UNION ALL
SELECT 'ITEM02',NULL,NULL,'38','OK','box04' UNION ALL
SELECT 'ITEM02',NULL,NULL,'2','DAMAGE','box05' UNION ALL
SELECT 'ITEM03',NULL,NULL,'30','OK','box06' UNION ALL
SELECT 'ITEM03',NULL,NULL,'30','OK','box07' UNION ALL
SELECT 'ITEM03',NULL,NULL,'10','DAMAGE','box09' UNION ALL
SELECT 'ITEM04',NULL,NULL,'25','OK','box10'
-- Below Table is my expected result based on above data.
-- I need to show those data following way.
-- So I appreciate if you can give me an appropriate query for it.
-- Note: first row is blank and it is actually my table header. :)
-- Conditions : any of row, we cant have ReceivedQty, DamageQty and ShortQty
-- values more than ExpectedQty value. Item03 has this scenario
-- Query should run in SQL 2000 DB
SELECT ''as'ITEM', ''as'PO#', ''as'ExpectedQty',''as'ReceivedQty',''as'DamageQty' ,''as'ShortQty' UNION ALL
SELECT 'ITEM01','PO-01','30','30','0' ,'0' UNION ALL
SELECT 'ITEM01','PO-02','20','15','5' ,'0' UNION ALL
SELECT 'ITEM02','PO-01','40','38','2' ,'0' UNION ALL
SELECT 'ITEM03','PO-01','50','50','0' ,'0' UNION ALL
SELECT 'ITEM03','PO-02','30','20','10' ,'10' UNION ALL
SELECT 'ITEM03','PO-03','20','0','0','20' UNION ALL
SELECT 'ITEM04','PO-01','30','25','0' ,'5'
Using this solution as a starting point, I've eventually ended up with this:
SELECT
Item,
PO,
ExpectedQty,
ReceivedQty = CASE
WHEN RemainderQty >= 0 THEN ExpectedQty
WHEN RemainderQty < -ExpectedQty THEN 0
ELSE RemainderQty + ExpectedQty
END,
DamageQty = CASE
WHEN RemainderQty >=0 OR ExpectedQty < -TotalRemainderQty THEN 0
WHEN RemainderQty < -ExpectedQty AND TotalRemainderQty > 0 THEN ExpectedQty
WHEN RemainderQty < -ExpectedQty AND TotalRemainderQty < -DamagedQty THEN ExpectedQty + TotalRemainderQty
WHEN RemainderQty > -DamagedQty THEN -RemainderQty
ELSE DamagedQty
END,
ShortQty = CASE
WHEN TotalRemainderQty >= 0 THEN 0
WHEN TotalRemainderQty < -ExpectedQty THEN ExpectedQty
ELSE -TotalRemainderQty
END
FROM (
SELECT
a.Item,
a.PO,
a.ExpectedQty,
b.DamagedQty,
RemainderQty = b.ReceivedQty - a.RunningTotalQty,
TotalRemainderQty = b.ReceivedQty + b.DamagedQty - a.RunningTotalQty
FROM (
SELECT
a.Item,
a.PO,
a.ExpectedQty,
RunningTotalQty = SUM(a2.ExpectedQty)
FROM (SELECT Item, PO, ExpectedQty FROM Temp WHERE STATUS IS NULL) AS a
INNER JOIN (SELECT Item, PO, ExpectedQty FROM Temp WHERE STATUS IS NULL) AS a2
ON a.Item = a2.Item AND a.PO >= a2.PO
GROUP BY
a.Item,
a.PO,
a.ExpectedQty
) a
LEFT JOIN (
SELECT
Item,
ReceivedQty = SUM(CASE STATUS WHEN 'OK' THEN ReceivedQty ELSE 0 END),
DamagedQty = SUM(CASE STATUS WHEN 'DAMAGE' THEN ReceivedQty ELSE 0 END)
FROM Temp
GROUP BY Item
) b ON a.Item = b.Item
) s;