Remove Rows That Sum Zero For A Given Key - sql

I have a query that will result in a customer bill being created on our SSRS 2008 R2 server. The SQL Server instance is also 2008 R2. The query is large and I don't want to post the entire thing for security reasons, etc.
What I need to do with the example data below, is to remove the two rows with 73.19 and -73.19 from the result set. So, if two rows have the same absolute value in the LineBalance column and their sum is 0 AND if they have the same value in the REF1 column, the should be removed from the result set. The line with REF1 = 14598 and a line balance of 281.47 should still be returned in the result set and the other two rows below with REF1 = 14598 should not be returned.
The point of this is to "hide" accounting errors and their correction from the customer. by "hide" I mean, not show it on the bill they get in the mail. What happened here is the customer was mistakenly billed 73.19 when they should have been billed 281.47. So, our AR dept. returned 73.19 to their account and charged them the correct amount of 281.47. As you can see they all have the same REF1 value.

I would add a field that would contain explicit flag telling you that a certain charge was a mistake/reversal of a mistake and then it is trivial to filter out such rows. Doing it on the fly could make your reports rather slow.
But, to solve the given problem as is we can do like this. The solution assumes that SysInvNum is unique.
Create a table with sample data
DECLARE #T TABLE (SysInvNum int, REF1 int, LineBalance money);
INSERT INTO #T (SysInvNum, REF1, LineBalance) VALUES (3344299, 14602, 558.83);
INSERT INTO #T (SysInvNum, REF1, LineBalance) VALUES (3344298, 14598, 281.47);
INSERT INTO #T (SysInvNum, REF1, LineBalance) VALUES (3344297, 14602, -95.98);
INSERT INTO #T (SysInvNum, REF1, LineBalance) VALUES (3344296, 14598, -73.19);
INSERT INTO #T (SysInvNum, REF1, LineBalance) VALUES (3341758, 14598, 73.19);
INSERT INTO #T (SysInvNum, REF1, LineBalance) VALUES (11, 100, 50.00);
INSERT INTO #T (SysInvNum, REF1, LineBalance) VALUES (12, 100, -50.00);
INSERT INTO #T (SysInvNum, REF1, LineBalance) VALUES (13, 100, 50.00);
INSERT INTO #T (SysInvNum, REF1, LineBalance) VALUES (21, 200, -50.00);
INSERT INTO #T (SysInvNum, REF1, LineBalance) VALUES (22, 200, -50.00);
INSERT INTO #T (SysInvNum, REF1, LineBalance) VALUES (23, 200, 50.00);
INSERT INTO #T (SysInvNum, REF1, LineBalance) VALUES (31, 300, -50.00);
INSERT INTO #T (SysInvNum, REF1, LineBalance) VALUES (32, 300, 50.00);
INSERT INTO #T (SysInvNum, REF1, LineBalance) VALUES (33, 300, -50.00);
INSERT INTO #T (SysInvNum, REF1, LineBalance) VALUES (34, 300, 50.00);
INSERT INTO #T (SysInvNum, REF1, LineBalance) VALUES (41, 400, 50.00);
INSERT INTO #T (SysInvNum, REF1, LineBalance) VALUES (42, 400, -50.00);
INSERT INTO #T (SysInvNum, REF1, LineBalance) VALUES (43, 400, 50.00);
INSERT INTO #T (SysInvNum, REF1, LineBalance) VALUES (44, 400, -50.00);
INSERT INTO #T (SysInvNum, REF1, LineBalance) VALUES (45, 400, 50.00);
I've added few more cases that have multiple mistakes.
Number and count rows
SELECT
SysInvNum
, REF1
, LineBalance
, ROW_NUMBER() OVER(PARTITION BY REF1, LineBalance ORDER BY SysInvNum) AS rn
, COUNT(*) OVER(PARTITION BY REF1, ABS(LineBalance)) AS cc1
FROM #T AS TT
This is the result set:
SysInvNum REF1 LineBalance rn cc1
11 100 50.00 1 3
12 100 -50.00 1 3
13 100 50.00 2 3
21 200 -50.00 1 3
23 200 50.00 1 3
22 200 -50.00 2 3
31 300 -50.00 1 4
32 300 50.00 1 4
33 300 -50.00 2 4
34 300 50.00 2 4
41 400 50.00 1 5
42 400 -50.00 1 5
43 400 50.00 2 5
44 400 -50.00 2 5
45 400 50.00 3 5
3341758 14598 73.19 1 2
3344296 14598 -73.19 1 2
3344298 14598 281.47 1 1
3344297 14602 -95.98 1 1
3344299 14602 558.83 1 1
You can see that those rows that have mistakes have count > 1. Also, pairs of mistakes have same row numbers. So, we need to remove/hide those rows that have count > 1 and those that have two same row numbers.
Determine rows to remove
WITH
CTE_rn
AS
(
SELECT
SysInvNum
, REF1
, LineBalance
, ROW_NUMBER() OVER(PARTITION BY REF1, LineBalance ORDER BY SysInvNum) AS rn
, COUNT(*) OVER(PARTITION BY REF1, ABS(LineBalance)) AS cc1
FROM #T AS TT
)
, CTE_ToRemove
AS
(
SELECT
SysInvNum
, REF1
, LineBalance
, COUNT(*) OVER(PARTITION BY REF1, rn) AS cc2
FROM CTE_rn
WHERE CTE_rn.cc1 > 1
)
SELECT *
FROM CTE_ToRemove
WHERE CTE_ToRemove.cc2 = 2
This is another intermediate result:
SysInvNum REF1 LineBalance cc2
12 100 -50.00 2
11 100 50.00 2
21 200 -50.00 2
23 200 50.00 2
32 300 50.00 2
31 300 -50.00 2
33 300 -50.00 2
34 300 50.00 2
42 400 -50.00 2
41 400 50.00 2
43 400 50.00 2
44 400 -50.00 2
3344296 14598 -73.19 2
3341758 14598 73.19 2
Now, we just put all this together.
Final query
WITH
CTE_rn
AS
(
SELECT
SysInvNum
, REF1
, LineBalance
, ROW_NUMBER() OVER(PARTITION BY REF1, LineBalance ORDER BY SysInvNum) AS rn
, COUNT(*) OVER(PARTITION BY REF1, ABS(LineBalance)) AS cc1
FROM #T AS TT
)
, CTE_ToRemove
AS
(
SELECT
SysInvNum
, REF1
, LineBalance
, COUNT(*) OVER(PARTITION BY REF1, rn) AS cc2
FROM CTE_rn
WHERE CTE_rn.cc1 > 1
)
SELECT *
FROM #T AS TT
WHERE
TT.SysInvNum NOT IN
(
SELECT CTE_ToRemove.SysInvNum
FROM CTE_ToRemove
WHERE CTE_ToRemove.cc2 = 2
)
ORDER BY SysInvNum;
Result:
SysInvNum REF1 LineBalance
13 100 50.00
22 200 -50.00
45 400 50.00
3344297 14602 -95.98
3344298 14598 281.47
3344299 14602 558.83
Note, that final result doesn't have any rows with REF = 300, because there were two corrected mistakes, that balanced each other completely.

Most AR/billing systems treat "credit memos" (the negative amount) similar to cash, in which case the -73.19 would get applied to the 73.19 LineBalance the same as if the customer had paid that amount, resulting in a $0 balance.
OPTION 1:
Do you handle cash receipts and applications in this sytem? If so, you may be able to pull data from those cash application tables to show the tie between SysInvNum 3344296 and 3341758.
OPTION 2:
I'm assuming that PayAdjust column is used to reduce the balance after a customer has paid, and that LineBalance is a calculated column that is Charges + PayAdjust.
Most of the time when this occurs, the AR department would be responsible for applying the credit memo to an open invoice, so that the PayAdjust column would net $0 between the 2 rows, and this would cause the LineBalance to also be $0 on each of the 2 rows. It may just be a training issue for the system that is being used.
This would cause the 3 rows in question to look like this, so you don't have an issue, you would just exclude the rows by adding where LineBalance <> 0 to your query since the AR department (which applied the credit to begin with and so knows the answer to this question) explicitly stated which LineBalance the credit applies to:
Option 2 Preferred data structure:
SysInvNum REF1 Charges PayAdjust LineBalance
----------- ----------- --------------------- --------------------- ---------------------
3344298 14598 281.47 0.00 281.47
3344296 14598 -73.19 73.19 0.00
3341758 14598 73.19 -73.19 0.00
OPTION 3:
Without having this data from Option 1 or 2, you have make many assumptions and run the risk of inadvertently hiding the wrong rows.
That being said, here is a query that attempts to do what you are asking, but I would highly recommend checking with the AR dept to see if they can update "PayAdjust" for these records instead.
I added several test cases of scenarios that could cause issues, but this may not cover all the bases.
This query will only hide rows where one distinct matching negative value is found for a positive value, for the same REF1 and same DueDate. It also makes sure that the original Charge invoice ID is prior to the credit since it can be assumed that a credit would not occur prior to the actual charge (Test case 6 shows both rows still because the credit has an SysInvNum that occurred before the charge). If more than once match is found per REF1, DueDate, and LineBalance, then it will not hide the corresponding charge and credit lines (test cases 2 & 4). Test case 3 sums in total to 0, but it still shows all 3 rows because the LineBalance values do not match exactly. These are all assumptions that I made to handle edge cases, so they can be adjusted as needed.
CREATE TABLE #SysInvTable (SysInvNum int not null primary key, REF1 int, Charges money, PayAdjust money, LineBalance as Charges + PayAdjust, DueDate date, REF2 int, Remark varchar(50), REM varchar(50));
INSERT INTO #SysInvTable(SysInvNum, REF1, Charges, PayAdjust, DueDate, Remark)
VALUES
--.....................................
--Your test case
(3344298, 14598, 281.47, 0, '2014-12-08','Your original test case. This one should stay.')
, (3344296, 14598, -73.19, 0, '2014-12-08',null)
, (3341758, 14598, 73.19, 0, '2014-12-08',null)
--.....................................
--Test case 2: How do you match these up?
, (2001, 2, 73.19, 0, '2015-01-06','Charge 2.1')
, (2002, 2, 73.19, 0, '2015-01-06','Charge 2.2')
, (2003, 2, 73.19, 0, '2015-01-06','Charge 2.3')
, (2004, 2, -73.19, 0, '2015-01-06','Credit for charge 2.3')
, (2005, 2, -73.19, 0, '2015-01-06','Credit for charge 2.1')
--.....................................
--Test case 3
, (3001, 3, 73.19, 0, '2015-01-06','Charge 3.1')
, (3002, 3, 73.19, 0, '2015-01-06','Charge 3.2')
, (3003, 3, -146.38, 0, '2015-01-06','Credit for charges 3.1 and 3.2')
--.....................................
--Test case 4: Do you hide 4001 or 4002?
, (4001, 4, 73.19, 0, '2015-01-06','Cable')
, (4002, 4, 73.19, 0, '2015-01-06','Internet')
, (4003, 4, -73.19, 0, '2015-01-06','Misc Credit')
--.....................................
--Test case 5: remove all lines except the first
, (5000, 5, 9.99, 0, '2015-01-06','Charge 5.0 (Should stay)')
, (5001, 5, 11.11, 0, '2015-01-06','Charge 5.1')
, (5002, 5, 22.22, 0, '2015-01-06','Charge 5.2')
, (5003, 5, 33.33, 0, '2015-01-06','Charge 5.3')
, (5004, 5, -11.11, 0, '2015-01-06','Credit for charge 5.1')
, (5005, 5, -33.33, 0, '2015-01-06','Credit for charge 5.3')
, (5006, 5, -22.22, 0, '2015-01-06','Credit for charge 5.2')
--.....................................
--Test case 6: credit occurs before charge, so keep both
, (6000, 6, -73.19, 0, '2015-01-06','Credit occurs before charge')
, (6001, 6, 73.19, 0, '2015-01-06','Charge 6.1')
;
SELECT i.*
FROM #SysInvTable i
WHERE i.SysInvNum not in
(
SELECT IngoreInvNum = case when c.N = 1 then max(t.SysInvNum) else min(t2.SysInvNum) end
FROM #SysInvTable t
INNER JOIN #SysInvTable t2
ON t.ref1 = t2.ref1
AND t.DueDate = t2.DueDate
CROSS APPLY (SELECT 1 AS N UNION ALL SELECT 2 as N) AS c --used to both both T and T2 SysInvNum's to exclude
WHERE 1=1
AND t.LineBalance > 0 AND t2.LineBalance < 0
AND t.SysInvNum < t2.SysInvNum --make sure the credit came in after the positive SysInvNum
AND t.LineBalance = t2.LineBalance * -1
GROUP BY t.REF1, t.DueDate, abs(t.LineBalance), c.n
HAVING Count(*) = 1
)
;
DROP TABLE #SysInvTable;

It's clunky, but this allows you to identify the matching negative offenders. You'll need to exclude the sysInvNum in both columns from your resultset
Create table #tmp( SysInvNum int,Ref1 int, LineBalance decimal(8,2))
insert into #tmp
values(3344299,14602,558.83)
,(3344298,14598,281.47)
,(3344297,14602,-95.98)
,(3344296,14598,-73.19)
,(3341758,14598,73.19)
--Select * From #tmp
Select *
INTO #t1
From #tmp
Where LineBalance < 0
--Select * From #t1
Select SysInvNum1 = #tmp.SysInvNum, SysInvNum2 = #T1.SysInvNum
INTO #T2
From #tmp
LEFT JOIN #t1 On #tmp.Ref1 = #T1.Ref1 and #tmp.LineBalance = -#T1.LineBalance
where #t1.SysInvNum is not null
Select * From
#tmp
Where SysInvNum not in(
Select SysInvNum1 from #t2
union Select SysInvNum2 from #t2
)
drop table #tmp
drop table #t1
drop table #t2

This solution works for SQL Server 2012 only, but decided to leave it here if someone has to do something similar in the future because it is pretty straight forward.
....
This should only remove two transactions for specific recepient if their sum is 0 and there are no other transactions between them (and the negative transaction happens after positive transaction). It is more conservative and safe.
There should be information about the order of transactions. Something like date column. Than you should order by this column instead of PRIMARY KEY column in PARTITION BY statement.
IF OBJECT_ID('Receipts', 'U') IS NOT NULL
DROP TABLE Receipts
CREATE TABLE Receipts
(
SysInvNum INT PRIMARY KEY IDENTITY(1,1),
REF1 INT,
LineBalance DECIMAL(10,2)
)
INSERT INTO Receipts
values
(14602,558.83),
(14598,281.47),
(14602,-95.98),
(14598,73.19),
(14598,-73.19),
(14598,73.19),
(14598,73.19),
(14598, 215.6),
(14598,73.19)
WITH ghosts AS
(
SELECT SysInvNum, REF1, LineBalance,
LAG(LineBalance, 1, 0) OVER (PARTITION BY REF1 ORDER BY SysInvNum) PreviousLineBalance,
LEAD(LineBalance, 1, 0) OVER (PARTITION BY REF1 ORDER BY SysInvNum) NextLineBalance
FROM Receipts
)
SELECT r.SysInvNum, r.REF1, r.LineBalance
FROM Receipts r
JOIN ghosts g On (r.SysInvNum = g.SysInvNum)
WHERE NOT (g.LineBalance + g.PreviousLineBalance = 0 AND g.LineBalance < 0)
AND NOT (g.LineBalance + g.NextLineBalance = 0 AND g.LineBalance > 0)
ORDER BY r.SysInvNum

Related

Insert grouped data

I am getting expected results from my query, I am using group by to group the data on the basis of different Ids.
The problem I am facing is that I have to insert this grouped data in the table called gstl_calculated_daily_fee, but when I pass the grouped result to variables called #total_mada_local_switch_high_value and #mada_range_id and insert them in the table then I get only the last result of the query in the table.
Sample result:
Fee range_id
1.23 1
1.22 2
2.33 3
I get only 2.33 and 1 after I insert but I have to insert the whole result in to the table.
Please suggest how can I insert the whole query result into the table. Below is the query:
DECLARE #total_mada_local_switch_high_value decimal(32,4) = 0.00;
DECLARE #mada_range_id int = 0;
select
#total_mada_local_switch_high_value = SUM(C.settlement_fees),
#mada_range_id = C.range_id
From
(
select
*
from
(
select
rowNumber = #previous_mada_switch_fee_volume_based_count + (ROW_NUMBER() OVER(PARTITION BY DATEPART(MONTH, x_datetime) ORDER BY x_datetime)),
tt.x_datetime
from gstl_trans_temp tt where (message_type_mapping = 0220) and card_type ='GEIDP1' and response_code IN(00,10,11) and tran_amount_req >= 5000
) A
CROSS APPLY
(
select
rtt.settlement_fees,
rtt.range_id
From gstl_mada_local_switch_fee_volume_based rtt
where A.rowNumber >= rtt.range_start
AND (A.rowNumber <= rtt.range_end OR rtt.range_end IS NULL)
) B
) C
group by CAST(C.x_datetime AS DATE),C.range_id
-- Insert Daily Volume
INSERT INTO
gstl_calculated_daily_fee(business_date,fee_type,fee_total,range_id)
VALUES
(#tlf_business_date,'MADA_SWITCH_FEE_LOCAL_CARD', #total_mada_local_switch_high_value, #mada_range_id)
I see no need for variables here. You can insert the aggregated results directly.
Sample data
create table Data
(
Range int,
Fee money
);
insert into Data (Range, Fee) values
(1, 1.00),
(1, 0.50),
(2, 3.00),
(3, 0.25),
(3, 0.50);
create table DataSum
(
Range int,
FeeSum money
);
Solution
insert into DataSum (Range, FeeSum)
select d.Range, sum(d.Fee)
from Data d
group by d.Range;
Fiddle to see things in action.

SQL Update Or Insert By Comparing Dates

I am trying to do the UPDATE or INSERT, but I am not sure if this is possible without using loop. Here is the example:
Says, I have this SQL below in which I joined two tables: tblCompany and tblOrders.
SELECT CompanyID, CompanyName, c.LastSaleDate, o.SalesOrderID, o.SalesPrice
, DATEADD(m, -6, GETDATE()) AS DateLast6MonthFromToday
FROM dbo.tblCompany c
CROSS APPLY (
SELECT TOP 1 SalesOrderID, SalesPrice
FROM dbo.tblOrders o
WHERE c.CompanyID = o.CompanyID
ORDER BY SalesOrderID DESC
) AS a
WHERE Type = 'End-User'
Sample Result:
CompanyID, SalesOrderID, SalesPrice, LastSalesDate, DateLast6MonthFromToday
101 10001 50 2/01/2016 10/20/2016
102 10002 80 12/01/2016 10/20/2016
103 10003 80 5/01/2016 10/20/2016
What I am trying to do is comparing the LastSalesDate and the DateLast6MonthFromToday. Condition is below:
If the LastSalesDate is lesser (earlier), then do the INSERT INTO tblOrders (CompanyID, Column1, Column2...) VALUES (CompanyIDFromQuery, Column1Value, Column2Value)
Else, do UPDATE tblOrders SET SalesPrice = 1111 WHERE SalesOrderID = a.SalesOrderID
As the above sample result, the query will only update SalesOrderID 10001 and 10003. And For Company 102, NO insert since the LastSaleDate is greater, then just do the UPDATE for the SalesOrderID.
I know it is probably can be done if I create a Cursor to loop through every record and do the comparison then Update or Insert, but I wonder if there is another way perform this without the loop since I have around 20K records.
Sorry for the confusion,
I don't know your tables structure and your data types. Also I know nothing
about duplicates and join ralationships between this 2 tables.
But I want only show how it works on next example:
use [your test db];
go
create table dbo.tblCompany
(
companyid int,
companyname varchar(max),
lastsaledate datetime,
[type] varchar(max)
);
create table dbo.tblOrders
(
CompanyID int,
SalesOrderID int,
SalesPrice float
);
insert into dbo.tblCompany
values
(1, 'Avito', '2016-01-01', 'End-User'),
(2, 'BMW', '2016-05-01', 'End-User'),
(3, 'PornHub', '2017-01-01', 'End-User')
insert into dbo.tblOrders
values
(1, 1, 500),
(1, 2, 700),
(1, 3, 900),
(2, 1, 500),
(2, 2, 700),
(2, 3, 900),
(3, 1, 500),
(3, 2, 700),
(3, 3, 900)
declare #column_1_value int = 5;
declare #column_2_value int = 777;
with cte as (
select
CompanyID,
SalesOrderID,
SalesPrice
from (
select
CompanyID,
SalesOrderID,
SalesPrice,
row_number() over(partition by CompanyID order by SalesOrderId desc) as rn
from
dbo.tblOrders
) t
where rn = 1
)
merge cte as target
using (select * from dbo.tblCompany where [type] = 'End-User') as source
on target.companyid = source.companyid
and source.lastsaledate >= dateadd(month, -6, getdate())
when matched
then update set target.salesprice = 1111
when not matched
then insert (
CompanyID,
SalesOrderID,
SalesPrice
)
values (
source.CompanyId,
#column_1_value,
#column_2_value
);
select * from dbo.tblOrders
If you will give me an information, then I can prepare target and source tables properly.

SQL Server: select newest rows who's sum matches a value

Here is a table...
ID QTY DATE CURRENT_STOCK
----------------------------------
1 1 Jan 30
2 1 Feb 30
3 2 Mar 30
4 6 Apr 30
5 8 May 30
6 21 Jun 30
I need to return the newest rows whose summed qty equal or exceed the current stock level, excluding any additional rows once this total has been reached, so I am expecting to see just these rows...
ID QTY DATE CURRENT_STOCK
----------------------------------
4 6 Apr 30
5 8 May 30
6 21 Jun 30
I am assuming I need a CTE (Common Table Expression) and have looked at this question but cannot see how to translate that to my requirement.
Help!?
Declare #YourTable table (ID int,QTY int,DATE varchar(25), CURRENT_STOCK int)
Insert Into #YourTable values
(1 ,1 ,'Jan' ,30),
(2 ,1 ,'Feb' ,30),
(3 ,2 ,'Mar' ,30),
(4 ,6 ,'Apr' ,30),
(5 ,8 ,'May' ,30),
(6 ,21 ,'Jun' ,30)
Select A.*
From #YourTable A
Where ID>= (
Select LastID=max(ID)
From #YourTable A
Cross Apply (Select RT = sum(Qty) from #YourTable where ID>=A.ID) B
Where B.RT>=CURRENT_STOCK
)
Returns
ID QTY DATE CURRENT_STOCK
4 6 Apr 30
5 8 May 30
6 21 Jun 30
One way to do it with your provided data set
if object_id('tempdb..#Test') is not null drop table #Test
create table #Test (ID int, QTY int, Date_Month nvarchar(5), CURRENT_STOCK int)
insert into #Test (ID, QTY, Date_Month, CURRENT_STOCK)
values
(1, 1, 'Jan', 30),
(2, 1, 'Feb', 30),
(3, 2, 'Mar', 30),
(4, 6, 'Apr', 30),
(5, 8, 'May', 30),
(6, 21, 'Jun', 30)
if object_id('tempdb..#Finish') is not null drop table #Finish
create table #Finish (ID int, QTY int, Date_Month nvarchar(5), CURRENT_STOCK int)
declare #rows int = (select MAX(ID) from #Test)
declare #stock int = (select MAX(CURRENT_STOCK) from #Test)
declare #i int = 1
declare #Sum int = 0
while #rows > #i
BEGIN
select #Sum = #Sum + QTY from #Test where ID = #rows
IF (#SUM >= #stock)
BEGIN
set #i = #rows + 1 -- to exit loop
END
insert into #Finish (ID, QTY, Date_Month, CURRENT_STOCK)
select ID, QTY, Date_Month, CURRENT_STOCK from #Test where ID = #rows
set #rows = #rows - 1
END
select * from #Finish
Setup Test Data
-- Setup test data
CREATE TABLE #Stock
([ID] int, [QTY] int, [DATE] varchar(3), [CURRENT_STOCK] int)
;
INSERT INTO #Stock
([ID], [QTY], [DATE], [CURRENT_STOCK])
VALUES
(1, 1, 'Jan', 30),
(2, 1, 'Feb', 30),
(3, 2, 'Mar', 30),
(4, 6, 'Apr', 30),
(5, 8, 'May', 30),
(6, 21, 'Jun', 30)
;
Solution for SQL Server 2012+
If you have a more recent version of SQL server which supports full window function syntax, you can do it look this:
-- Calculate a running total of qty by Id descending
;WITH stock AS (
SELECT *
-- This calculates the SUM over a 'window' of rows based on the first
-- row in the result set through the current row, as specified by the
-- ORDER BY clause
,SUM(qty) OVER(ORDER BY Id DESC
ROWS BETWEEN UNBOUNDED PRECEDING
AND CURRENT ROW) AS TotalQty
FROM #Stock
),
-- Identify first row in mininum set that matches or exceeds CURRENT_STOCK
first_in_set AS (
SELECT TOP 1 *
FROM stock
WHERE TotalQty >= CURRENT_STOCK
)
-- Fetch matching set
SELECT *
FROM #stock
WHERE Id >= (SELECT Id FROM first_in_set)
Solution for SQL Server 2008
For SQL Server 2008, which only has basic support for window functions, you can calculate the running total using CROSS APPLY:
-- Calculate a running total of qty by Id descending
;WITH stock AS (
SELECT *
-- This window function causes the results of this query
-- to be sorted in descending order by Id
,ROW_NUMBER() OVER(ORDER BY Id DESC) AS sort_order
FROM #Stock s1
-- CROSS APPLY 'applies' the query (or UDF) to every row in a result set
-- This CROSS APPLY query produces a 'running total'
CROSS APPLY (
SELECT SUM(Qty) AS TotalQty
FROM #Stock s2
WHERE s2.Id >= s1.id
) total_calc
WHERE TotalQty >= s1.CURRENT_STOCK
),
-- Identify first row in mininum set that matches or exceeds CURRENT_STOCK
first_in_set AS (
SELECT TOP 1 Id
FROM stock
WHERE sort_order = 1
)
-- Fetch matching set
SELECT *
FROM #stock
WHERE Id >= (SELECT Id
FROM first_in_set)

T-SQL Summation

I'm trying to create result set with 3 columns. Each column coming from the summation of 1 Column of Table A but grouped by different ID's. Here's an overview of what I wanted to do..
Table A
ID Val.1
1 4
1 5
1 6
2 7
2 8
2 9
3 10
3 11
3 12
I wanted to create something like..
ROW SUM.VAL.1 SUM.VAL.2 SUM.VAL.3
1 15 21 33
I understand that I can not get this using UNION, I was thinking of using CTE but not quite sure with the logic.
You need conditional Aggregation
select 1 as Row,
sum(case when ID = 1 then Val.1 end),
sum(case when ID = 2 then Val.1 end),
sum(case when ID = 3 then Val.1 end)
From yourtable
You may need dynamic cross tab or pivot if number of ID's are not static
DECLARE #col_list VARCHAR(8000)= Stuff((SELECT ',sum(case when ID = '+ Cast(ID AS VARCHAR(20))+ ' then [Val.1] end) as [val.'+Cast(ID AS VARCHAR(20))+']'
FROM Yourtable
GROUP BY ID
FOR xml path('')), 1, 1, ''),
#sql VARCHAR(8000)
exec('select 1 as Row,'+#col_list +'from Yourtable')
Live Demo
I think pivoting the data table will yield the desired result.
IF OBJECT_ID('tempdb..#TableA') IS NOT NULL
DROP TABLE #TableA
CREATE TABLE #TableA
(
RowNumber INT,
ID INT,
Value INT
)
INSERT #TableA VALUES (1, 1, 4)
INSERT #TableA VALUES (1, 1, 5)
INSERT #TableA VALUES (1, 1, 6)
INSERT #TableA VALUES (1, 2, 7)
INSERT #TableA VALUES (1, 2, 8)
INSERT #TableA VALUES (1, 2, 9)
INSERT #TableA VALUES (1, 3, 10)
INSERT #TableA VALUES (1, 3, 11)
INSERT #TableA VALUES (1, 3, 12)
-- https://msdn.microsoft.com/en-us/library/ms177410.aspx
SELECT RowNumber, [1] AS Sum1, [2] AS Sum2, [3] AS Sum3
FROM
(
SELECT RowNumber, ID, Value
FROM #TableA
) a
PIVOT
(
SUM(Value)
FOR ID IN ([1], [2], [3])
) AS p
This technique works if the ids you are seeking are constant, otherwise I imagine some dyanmic-sql would work as well if changing ids are needed.
https://msdn.microsoft.com/en-us/library/ms177410.aspx

Complicated grouping and time difference

So I have a log table that is putting in data like:
LogId SiteNumber Unit IDNumber LogCode EnteredDateTime ChangeMode HowEntered
----- ---------- ---- -------- ------- ---------------- ---------- -----------
851 1 16 - 0 23502 BDISCHSET 2011-11-12 11:48:08.890 Discharging Soon SERIES
866 1 16 - 0 NULL BDISCHRED 2011-11-12 21:45:11.657 Discharged SERIES
113 2 2001 - 0 12384 BDISCHSET 2011-10-28 09:27:08.773 Discharging Soon SERIES
125 2 2001 - 0 NULL BDISCHRED 2011-10-28 10:38:08.060 Discharged SERIES
119 2 2002 - 0 12394 BDISCHSET 2011-10-28 10:01:12.443 Discharging Soon SERIES
139 2 2002 - 0 NULL BDISCHRED 2011-10-28 14:01:11.120 Discharged SERIES
776 2 2002 - 0 12331 BDISCHSET 2011-11-10 09:08:09.443 Discharging Soon SERIES
783 2 2002 - 0 NULL BDISCHRED 2011-11-10 11:08:08.537 Discharged SERIES
What I need to do is group the records per person and calculate the amount of time it took to discharge.
For example:
Unit 2002 - 0 has two different people to group, it would be:
Person 12394 10/28/2011 from 10:01 to 14:01 = 4 hours to discharge
Person 12331 11/10/2011 from 9:08 to 11:08 = 2 hours to discharge
Any help would be greatly appreciated.
As others are pointing out in the comments, I don't think the design / architecture we're looking at is great. If you can guarantee that rows will ALWAYS be sequential, i.e. for every set of two rows, the first row is a Patient with change mode of Discharging Soon and the 2nd row is the same patient with a change mode of Discharged, then this should work.
;WITH OrderedTable AS
(
SELECT Unit
, IDNumber
, EnteredDateTime
, ROW_Number() OVER (Partition BY Unit ORDER BY Unit) RN
FROM #t -- YOUR TABLE NAME GOES HERE
)
SELECT t1.Unit
, t1.IDNumber
, t1.EnteredDateTime as TimeIn
, t2.EnteredDateTime as TimeOut
, DATEDIFF(hour, t1.EnteredDateTime, t2.EnteredDateTime ) TimeElapsedInHours
FROM OrderedTable t1
JOIN OrderedTable t2 ON t1.Unit = t2.Unit AND t2.RN = t1.RN + 1
WHERE t1.RN % 2 <> 0
But I want to be clear - I don't know how safe this solution is based on your actual data. However, it will give you the results you want, based on the sample data you provided.
Even if you can't use this exact approach because of your data model, I feel like there are a few things in it that you might could use to craft a solution. These will make for interesting reading if nothing else.
DATEDIFF function
Common Table Expressions
ROW_NUMBER function
Here is the test I used...
declare #t TABLE
(
LogId int,
SiteNumber int,
Unit varchar(50),
IDNumber int ,
LogCode varchar(50),
EnteredDateTime datetime,
ChangeMode varchar(50),
HowEntered varchar(50)
)
insert into #t values (851, 1, '16 - 0', 23502, 'BDISCHSET', '2011-11-12 11:48:08.890', 'Discharging Soon', 'SERIES')
insert into #t values (866, 1, '16 - 0 ', NULL, 'BDISCHRED', '2011-11-12 21:45:11.657', 'Discharged', 'SERIES')
insert into #t values (113, 2, '2001 - 0', 12384, 'BDISCHSET', '2011-10-28 09:27:08.773', 'Discharging Soon', 'SERIES')
insert into #t values (125, 2, '2001 - 0', NULL, 'BDISCHRED', '2011-10-28 10:38:08.060', 'Discharged', 'SERIES')
insert into #t values (119, 2, '2002 - 0', 12394, 'BDISCHSET', '2011-10-28 10:01:12.443', 'Discharging Soon', 'SERIES')
insert into #t values (139, 2, '2002 - 0', NULL, 'BDISCHRED', '2011-10-28 14:01:11.120', 'Discharged', 'SERIES')
insert into #t values (776, 2, '2002 - 0', 12331, 'BDISCHSET', '2011-11-10 09:08:09.443', 'Discharging Soon', 'SERIES')
insert into #t values (783, 2, '2002 - 0', NULL, 'BDISCHRED', '2011-11-10 11:08:08.537', 'Discharged', 'SERIES')
;WITH OrderedTable AS
(
SELECT Unit
, IDNumber
, EnteredDateTime
, ROW_Number() OVER (Partition BY Unit ORDER BY Unit) RN
FROM #t -- YOUR TABLE NAME GOES HERE
)
SELECT t1.Unit
, t1.IDNumber
, t1.EnteredDateTime as TimeIn
, t2.EnteredDateTime as TimeOut
, DATEDIFF(hour, t1.EnteredDateTime, t2.EnteredDateTime ) TimeElapsedInHours
FROM OrderedTable t1
JOIN OrderedTable t2 ON t1.Unit = t2.Unit AND t2.RN = t1.RN + 1
WHERE t1.RN % 2 <> 0