SQL Server: update multiple rows with one statement with calculation - sql-server

I don't know is this possible in one SQL statement to update the refundamt?
I have these three rows initially, using insert statement:
NO | TRANAMT | REFUNDAMT
1 | 100 | 0
2 | 200 | 0
3 | 300 | 0
If refund is 350, the refundamt will be updated as follow, the refundamt cannot be more then the tranamt:
NO | TRANAMT | REFUNDAMT
1 | 100 | 100
2 | 200 | 200
3 | 300 | 50
When refund again with 50, the refundamt will be updated as follow
NO | TRANAMT | REFUNDAMT
1 | 100 | 100
2 | 200 | 200
3 | 300 | 100
I think this is impossible to update refundamt using one sql statement. How about multiple satement? Not hoping to use store procedure. Can I use select update?

I think this works. It's a single statement. Not the prettiest. And I'd imagine the real Transactions table has more columns (e.g. an account number):
declare #RefundAmt int
set #RefundAmt = 350
; with Refunds as (
select
top 1
NO,
TRANAMT,
CASE WHEN TRANAMT-REFUNDAMT < #RefundAmt THEN TRANAMT ELSE REFUNDAMT + #RefundAmt END as REFUNDAMT,
CASE WHEN TRANAMT-REFUNDAMT < #RefundAmt THEN #RefundAmt - (TRANAMT-REFUNDAMT) ELSE 0 END as Remaining
from
dbo.Transactions
where REFUNDAMT < TRANAMT ORDER BY NO
union all
select
t2.NO,
t2.TRANAMT,
CASE WHEN t2.TRANAMT-t2.REFUNDAMT < t1.Remaining THEN t2.TRANAMT ELSE t2.REFUNDAMT + t1.Remaining END as REFUNDAMT,
CASE WHEN t2.TRANAMT-t2.REFUNDAMT < t1.Remaining THEN t1.Remaining - (t2.TRANAMT-t2.REFUNDAMT) ELSE 0 END as Remaining
from
Refunds t1
inner join
dbo.Transactions t2
on
t1.NO = t2.NO - 1 and
t1.Remaining > 0
)
update t set REFUNDAMT = r.REFUNDAMT
from
dbo.Transactions t
inner join
Refunds r
on
t.NO = r.NO

Bit messy, but here's what I came up with:
Code to set up your base tables...
-- setup code
CREATE TABLE #tmp (
[NO] int identity(1,1),
[TRAN] money,
[REFUND] money
)
INSERT #tmp VALUES (100, 0)
INSERT #tmp VALUES (200, 0)
INSERT #tmp VALUES (300, 0)
Code to add your refund amount...
DECLARE #amt money SET #amt = 50
UPDATE u
SET REFUND =
CASE WHEN u.[REFUND] = u.[TRAN] THEN u.[REFUND] --'already full - do nothing'
WHEN cumulativepretran >= aiming_for_ref THEN u.[REFUND] --'after all - do nothing'
WHEN cumulativetran + cumulativeref > ((totalref + #amt) - u.[TRAN]) THEN (totalref + #amt + u.[REFUND]) - (cumulativeref) --'calc'
ELSE u.[TRAN] END -- 'fill-er-up'
FROM #tmp u
INNER JOIN (SELECT *,
ISNULL((select sum([REFUND]) FROM #tmp WHERE [NO] <= t.[NO]), 0) 'cumulativeref',
ISNULL((select sum([TRAN]) FROM #tmp WHERE [NO] <= t.[NO]), 0) 'cumulativetran',
ISNULL((select sum([REFUND]) FROM #tmp WHERE [NO] < t.[NO]), 0) 'cumulativepreref',
ISNULL((select sum([TRAN]) FROM #tmp WHERE [NO] < t.[NO]), 0) 'cumulativepretran',
ISNULL((select sum([REFUND]) FROM #tmp), 0) 'totalref',
ISNULL((select sum([REFUND]) FROM #tmp), 0) + #amt 'aiming_for_ref'
FROM #tmp t) as m ON m.[NO] = u.[NO]

Something like...: (needs work, but gives a conceptual idea)
Declare #pRefund Money
SET #pRefund = 350
WHILE #pRefund > 0
BEGIN
/** SELECT Transaction Amount of min primary key where recorded refundamt is less than tranamt
Apply matching refund
Reduce #pRefund by amount applied
If #pRefund is now less then next highest TransAMT Then apply whats left otherwise loop again
refund has now been all applied and loop will exit */
END

Related

Denomination distribution calculation

Some background story: Company A gives out vouchers to winners of a challenge. The SQL that I am currently writing needs to decide the required voucher denomination that sums to the value awarded to a person. I have a table that stores the denominations available for vouchers, depending on the country and currency.
In the example below, a particular person is awarded with €80 worth of vouchers.
The query below displays results of a lookup table for voucher denominations available for a particular country.
SELECT * FROM tblDenominationScheme WHERE CountryCode IN ('AT', 'US')
Result:
No. | CountryCode | VoucherName | VoucherValue
-------------------------------------------------
1 | AT | €50 Shop A | 50
2 | AT | €25 Shop A | 25
3 | AT | €15 Shop A | 15
4 | AT | €10 Shop A | 10
5 | US | $50 Store B | 50
6 | US | $10 Store B | 10
7 | US | $5 Store B | 5
My current SQL is as below to determine the required voucher denominations for €80 voucher:
DECLARE #CountryCode1 VARCHAR(2) = 'AT'
DECLARE #ChallengerID INT = 1172
DECLARE #RoundedAmount1 INT = 80
DECLARE #Vouchers INT
DECLARE #AmountAwarded INT = 0
SET #AmountAwarded = #RoundedAmount1
DROP TABLE IF EXISTS #tempVoucher
CREATE TABLE #tempVoucher
(
CountryCode VARCHAR(2),
ChallengerID INT,
AmountAwarded INT,
Vouchers INT,
)
WHILE (#RoundedAmount1 > 0)
BEGIN
SET #Vouchers = 0
SELECT TOP 1 #Vouchers = VoucherValue FROM tblDenominationScheme WHERE CountryCode = #CountryCode1 AND VoucherValue <= #RoundedAmount1 ORDER BY VoucherValue DESC
IF (#Vouchers > 0)
BEGIN
SET #RoundedAmount1 = #RoundedAmount1 - #Vouchers
END
ELSE
BEGIN
SELECT TOP 1 #Vouchers = VoucherValue FROM tblDenominationScheme WHERE CountryCode = #CountryCode1 ORDER BY VoucherValue
SET #RoundedAmount1 = #RoundedAmount1 - #RoundedAmount1
END
INSERT INTO #tempVoucher VALUES (#CountryCode1,#ChallengerID, #AmountAwarded, #Vouchers)
END
SELECT * FROM #tempVoucher
Result from the SQL above:
No. | CountryCode | ChallengerID | AmountAwarded | Vouchers
--------------------------------------------------------------
1 | AT | 1172 | 80 | 50
2 | AT | 1172 | 80 | 25
3 | AT | 1172 | 80 | 10
NOTE: The value in AmountAwarded column will be the same for all 3 rows. The amount in the Vouchers column for the 3 rows should sum up to 80.
The result above is obviously incorrect, because if you sum up the values in the Vouchers column, it gives you 85, which is 5 more than the AmountAwarded
Expected result (or at least closest):
No. | CountryCode | ChallengerID | AmountAwarded | Vouchers
--------------------------------------------------------------
1 | AT | 1172 | 80 | 50
2 | AT | 1172 | 80 | 10
3 | AT | 1172 | 80 | 10
4 | AT | 1172 | 80 | 10
Anyone able to help?
This might be an expensive query, but gets you different options to deliver up to 7 vouchers to get you the expected result. This, however, will generate a huge amount of reads if the rows increase or the amount of vouchers can be greater.
DECLARE #CountryCode1 VARCHAR(2) = 'AT'
DECLARE #RoundedAmount1 INT = 80;
WITH cteDenominations AS(
SELECT No, VoucherValue
FROM tblDenominationScheme
WHERE CountryCode = #CountryCode1
UNION ALL
SELECT 10000, 0
),
ctePermutations AS(
SELECT a.No AS a_No,
a.VoucherValue AS a_Value,
b.No AS b_No,
b.VoucherValue AS b_Value,
c.No AS c_No,
c.VoucherValue AS c_Value,
d.No AS d_No,
d.VoucherValue AS d_Value,
e.No AS e_No,
e.VoucherValue AS e_Value,
f.No AS f_No,
f.VoucherValue AS f_Value,
g.No AS g_No,
g.VoucherValue AS g_Value,
ROW_NUMBER() OVER(ORDER BY a.No, b.No, c.No, d.No) Permutation
FROM cteDenominations a
JOIN cteDenominations b ON a.VoucherValue >= b.VoucherValue
JOIN cteDenominations c ON b.VoucherValue >= c.VoucherValue
JOIN cteDenominations d ON c.VoucherValue >= d.VoucherValue
JOIN cteDenominations e ON d.VoucherValue >= e.VoucherValue
JOIN cteDenominations f ON e.VoucherValue >= f.VoucherValue
JOIN cteDenominations g ON f.VoucherValue >= g.VoucherValue
WHERE #RoundedAmount1 = a.VoucherValue
+ b.VoucherValue
+ c.VoucherValue
+ d.VoucherValue
+ e.VoucherValue
+ f.VoucherValue
+ g.VoucherValue
)
SELECT Permutation,
u.No,
u.VoucherValue
FROM ctePermutations
CROSS APPLY (VALUES(a_No, a_Value),
(b_No, b_Value),
(c_No, c_Value),
(d_No, d_Value),
(e_No, e_Value),
(f_No, f_Value),
(g_No, g_Value))u(No, VoucherValue)
WHERE VoucherValue > 0
AND Permutation = 1 --Remove this to get all possibilities
;
Looks like you need to solve a equation:
80 = n1*v1 + k2*n2...
where v1,v2 ... are values which you store in database
And you need to find n1, n2 ... , which are in {0, N}
There is no way how to implement it in SQL. Except - over all possible values, but it's not the smarter way.
Also, see this info:
https://math.stackexchange.com/questions/431367/solving-a-first-order-diophantine-equation-with-many-terms
Logic
Find the largest amount (that is less than or equal to starting amount) vouchers of 1 denomination can make.
Subtract this value from starting amount to get remainder,
Find the largest amount (that is less than or equal to remainder) a number of vouchers of 1 smaller denomination can make.
Subtract this value from previous remainder.
Go back to step 3
Features:
Handles multiple best combinations.
Small number of combinations are searched.
On my laptop: 100 runs take about 3 seconds
Notes
Performance may be improved by saving output of VoucherCombinations to a table variable and then using it in subsequent CTEs.
Code:
DECLARE #Vouchers TABLE( CountryCode CHAR( 2 ), VoucherValue DECIMAL( 10, 2 ))
INSERT INTO #Vouchers VALUES( 'AT', 50 ), ( 'AT', 40 ), ( 'AT', 25 ), ( 'AT', 20 ), ( 'AT', 15 ), ( 'AT', 10 ), ( 'US', 50 ), ( 'US', 10 ), ( 'US', 5 );
-- Small number table
-- Limits maximum count of Vouchers of a given denomination.
DECLARE #Numbers TABLE( Num INT )
INSERT INTO #Numbers VALUES( 1 ), ( 2 ), ( 3 ), ( 4 ), ( 5 ), ( 6 ), ( 7 ), ( 8 ), ( 9 ), ( 10 )
DECLARE #TargetAmount DECIMAL( 10, 2 ) = 60;
DECLARE #CountryCode CHAR( 2 ) = 'AT';
;WITH VoucherCombinations
AS (
-- Anchor
SELECT ROW_NUMBER() OVER( ORDER BY VoucherValue DESC ) AS ParentGroupID,
ROW_NUMBER() OVER( ORDER BY VoucherValue DESC ) AS SubGroupID,
1 AS IterationID,
VoucherValue, Num AS VoucherCumulativeCount,
CAST( VoucherValue * Num AS DECIMAL( 10, 2 )) AS TotalDenominationValue,
CAST( #TargetAmount - ( VoucherValue * Num ) AS DECIMAL( 10, 2 )) AS Remainder
FROM #Vouchers
-- Find the largest amount a given Voucher denomination can produce that is less than or equal to #TargetAmount
INNER JOIN #Numbers ON ( VoucherValue * Num ) <= #TargetAmount AND #TargetAmount - ( VoucherValue * Num ) < VoucherValue
WHERE CountryCode = #CountryCode
UNION ALL
-- Recursive query
SELECT SubGroupID,
SubGroupID * 10 + ROW_NUMBER() OVER( ORDER BY V.VoucherValue DESC ) AS SubGroupID,
IterationID + 1,
V.VoucherValue, VoucherCumulativeCount + N.Num AS VoucherCount,
CAST( V.VoucherValue * N.Num AS DECIMAL( 10, 2 )) AS TotalDenominationValue,
CAST( Remainder - ( V.VoucherValue * N.Num ) AS DECIMAL( 10, 2 )) AS Remainder
FROM VoucherCombinations AS VP
-- For each denomination look at the smaller denominations
INNER JOIN #Vouchers AS V ON VP.VoucherValue > V.VoucherValue
INNER JOIN #Numbers AS N ON V.VoucherValue * N.Num <= Remainder AND Remainder - ( V.VoucherValue * N.Num ) < V.VoucherValue
WHERE CountryCode = #CountryCode
),
-- Discard invalid combinations i.e. remainder is not 0
VoucherPoolValid AS(
SELECT *, DENSE_RANK() OVER( ORDER BY VoucherCumulativeCount ASC ) AS BestCombos
FROM VoucherCombinations
WHERE Remainder = 0
),
-- Find best combinations i.e. smallest number of Vouchers; Note: logic supports having more than 1 best combination
VoucherPoolBestCombos AS(
SELECT *, ROW_NUMBER() OVER( ORDER BY BestCombos ASC ) AS ComboID
FROM VoucherPoolValid
WHERE BestCombos = 1
),
-- Return all denominations for each combination
VoucherPoolAllDetails AS(
SELECT *
FROM VoucherPoolBestCombos
UNION ALL
SELECT Parent.*, BestCombos, ComboID
FROM VoucherPoolAllDetails AS Child
INNER JOIN VoucherCombinations AS Parent ON Child.ParentGroupID = Parent.SubGroupID
WHERE Child.SubGroupID <> Child.ParentGroupID
)
SELECT * FROM VoucherPoolAllDetails
ORDER BY ComboID

Reverse order of a XML Column in SQL Server

In a SQL Server table, I have a XML column where status are happened (first is oldest, last current status).
I have to write a stored procedure that returns the statuses: newest first, oldest last.
This is what I wrote:
ALTER PROCEDURE [dbo].[GetDeliveryStatus]
#invoiceID nvarchar(255)
AS
BEGIN
SET NOCOUNT ON;
DECLARE #xml xml
SET #xml = (SELECT statusXML
FROM Purchase
WHERE invoiceID = #invoiceID )
SELECT
t.n.value('text()[1]', 'nvarchar(50)') as DeliveryStatus
FROM
#xml.nodes('/statuses/status') as t(n)
ORDER BY
DeliveryStatus DESC
END
Example of value in the statusXML column:
<statuses>
<status>A</status>
<status>B</status>
<status>A</status>
<status>B</status>
<status>C</status>
</statuses>
I want the procedure to return:
C
B
A
B
A
with ORDER BY .... DESC it return ALPHABETIC reversed (C B B A A)
How should I correct my procedure ?
Create a sequence for the nodes based on the existing order then reverse it.
WITH [x] AS (
SELECT
t.n.value('text()[1]', 'nvarchar(50)') as DeliveryStatus
,ROW_NUMBER() OVER (ORDER BY t.n.value('..', 'NVARCHAR(100)')) AS [Order]
FROM
#xml.nodes('/statuses/status') as t(n)
)
SELECT
DeliveryStatus
FROM [x]
ORDER BY [x].[Order] DESC
... results ...
DeliveryStatus
C
B
A
B
A
There is no need to declare a variable first. You can (and you should!) read the needed values from your table column directly. Best was an inline table valued function (rather than a SP just to read something...)
Better performance
inlineable
You can query many InvoiceIDs at once
set-based
Try this (I drop the mock-table at the end - carefull with real data!):
CREATE TABLE Purchase(ID INT IDENTITY,statusXML XML, InvocieID INT, OtherValues VARCHAR(100));
INSERT INTO Purchase VALUES('<statuses>
<status>A</status>
<status>B</status>
<status>A</status>
<status>B</status>
<status>C</status>
</statuses>',100,'Other values of your row');
GO
WITH NumberedStatus AS
(
SELECT ID
,InvocieID
, ROW_NUMBER() OVER(ORDER BY (SELECT NULL)) AS Nr
,stat.value('.','nvarchar(max)') AS [Status]
,OtherValues
FROM Purchase
CROSS APPLY statusXML.nodes('/statuses/status') AS A(stat)
WHERE InvocieID=100
)
SELECT *
FROM NumberedStatus
ORDER BY Nr DESC
GO
--Clean-Up
--DROP TABLE Purchase;
The result
+---+-----+---+---+--------------------------+
| 1 | 100 | 5 | C | Other values of your row |
+---+-----+---+---+--------------------------+
| 1 | 100 | 4 | B | Other values of your row |
+---+-----+---+---+--------------------------+
| 1 | 100 | 3 | A | Other values of your row |
+---+-----+---+---+--------------------------+
| 1 | 100 | 2 | B | Other values of your row |
+---+-----+---+---+--------------------------+
| 1 | 100 | 1 | A | Other values of your row |
+---+-----+---+---+--------------------------+

How to retrieve unique records having unique values in two columns from a table in SQL Server

I want to query a table where I need the result that contains unique values from two columns together. For e.g.
Table
EnquiryId | EquipmentId | Price
-----------+--------------+-------
1 | E20 | 10
1 | E50 | 40
1 | E60 | 20
2 | E30 | 90
2 | E20 | 10
2 | E90 | 10
3 | E90 | 10
3 | E60 | 10
For each EnquiryId, EquipmentId will be unique in the table. Now I want a result where I can get something like this
EnquiryId | EquipmentId | Price
-----------+--------------+-------
1 | E20 | 10
2 | E30 | 90
3 | E90 | 10
In the result each enquiryId present in the table should be displayed uniquely.
If suppose I have 3 EquipmentIds "E20,E50,E60" for EnquiryId "1".. Any random EquipmentId should be displayed from these three values only.
Any help would be appreciated. Thank you in advance.
QUERY
;WITH cte AS
(
SELECT *,
ROW_NUMBER() OVER
(PARTITION BY enquiryID
ORDER BY enquiryID ) AS RN
FROM tbl
)
SELECT enquiryID,equipmentID,Price
FROM cte
WHERE RN=1
FIND FIDDLE HERE
The following code must help you..
Sorry that I ended up in a lengthy solution only. Run it in your SSMS and see the result.
Declare #tab table (EnquiryId int, EquipmentId varchar(10),Price int)
Insert into #tab values
(1,'E20',10),
(1,'E50',40),
(1,'E60',20),
(2,'E30',90),
(2,'E20',10),
(2,'E90',10),
(3,'E90',10),
(3,'E60',10)
----------------------------------------------
Declare #s int = 1
Declare #e int,#z varchar(10)
Declare #Equipment table (EquipmentId varchar(10),ind int)
Insert into #Equipment (EquipmentId) Select Distinct EquipmentId From #tab
Declare #Enquiry table (id int identity(1,1),EnquiryId int,EquipmentId varchar(10))
Insert into #Enquiry (EnquiryId) Select Distinct EnquiryId From #tab
Set #e = ##ROWCOUNT
While #s <= #e
begin
Select Top 1 #z = T.EquipmentId
From #tab T
Join #Enquiry E On T.EnquiryId = E.EnquiryId
Join #Equipment Eq On Eq.EquipmentId = T.EquipmentId
Where E.id = #s
And Eq.ind is Null
Order by NEWID()
update #Enquiry
Set EquipmentId = #z
Where id = #s
update #Equipment
Set ind = 1
Where EquipmentId = #z
Set #s = #s + 1
End
Select T.EnquiryId,T.EquipmentId,T.Price
From #tab T
left join #Enquiry E on T.EnquiryId = E.EnquiryId
Where T.EquipmentId = E.EquipmentId
You can use GROUP BY (Typical way) to remove duplicate value.
Basic steps are:
Alter table & Add Identity Column.
Group by columns which can be dupicate.
Delete those record.
Check here Remove Duplicate Rows from a Table in SQL Server

Sequential SQL inserts when triggered by CROSS APPLY

This process has several steps which are reflected in various tables of a database:
Production --> UPDATE to the inventory table using something like
UPDATE STOR SET
STOR.BLOC1 = T.BLOC1,
STOR.BLOC2 = T.BLOC2,
STOR.BLOC3 = T.BLOC3,
STOR.PRODUCTION = T.PROD,
STOR.DELTA = T.DELTA
FROM BLDG B INNER JOIN STOR S
ON S.B_ID = B.B_ID
CROSS APPLY dbo.INVENTORIZE(B.B_ID) AS T;
The above feeds a log table with a TRIGGER like this:
CREATE TRIGGER trgrCYCLE
ON STOR
FOR UPDATE
AS
INSERT INTO dbo.INVT
(TS, BLDG, PROD, ACT, VAL)
SELECT CURRENT_TIMESTAMP, B_ID, PRODUCTION,
CASE WHEN DELTA < 0 THEN 'SELL' ELSE 'BUY' END,
DELTA
FROM inserted WHERE COALESCE(DELTA,0) <> 0
And finally, every update should INSERT a row into a financials table which I added to the TRIGGER above:
INSERT INTO dbo.FINS
(COMPANY, TS, COST2, BAL)
SELECT CORP, CURRENT_TIMESTAMP, COST,
((SELECT TOP 1 BAL FROM FINS WHERE COMPANY = CORP ORDER BY TS DESC)- COST)
FROM inserted WHERE COALESCE(COST,0) <> 0
The problem is with this line:
((SELECT TOP 1 BAL FROM FINS WHERE COMPANY = CORP ORDER BY TS DESC)- COST)
which is meant to calculate the latest balance of an account. But because the CROSS APPLY treats all the INSERTS as a batch, the calculation is done off of the same last record and I get an incorrect balance figure. Example:
COST BALANCE
----------------
1,000 <-- initial balance
-150 850
-220 780 <-- should be 630
What would be the way to solve that? A trigger on the FINS table instead for the balance calculation?
Understanding existing logic in your query
UPDATE statement will fire a trigger only once for a set or batch satisfying join conditions, Inserted statement will have all the records that are being updated. This is because of BATCH processing not because of CROSS APPLY but because of UPDATE.
In this query of yours
SELECT CORP, CURRENT_TIMESTAMP, COST,
((SELECT TOP 1 BAL FROM FINS WHERE COMPANY = CORP ORDER BY TS DESC)- COST)
FROM inserted WHERE COALESCE(COST,0) <> 0
For each CORP from an Outer query, same BAL will be returned.
(SELECT TOP 1 BAL FROM FINS WHERE COMPANY = CORP ORDER BY TS DESC)
That being said, your inner query will be replaced by 1000(value you used in your example) every time CORP = 'XYZ'
SELECT CORP, CURRENT_TIMESTAMP, COST, (1000- COST)
FROM inserted WHERE COALESCE(COST,0) <> 0
Now your inserted statement has all the records that are being inserted. So every record's cost will be subtracted by 1000. Hence you are getting unexpected result.
Suggested solution
As per my understanding, you want to calculate some cumulative frequency kind of thing. Or last running total
Data Preparation for problem statement. Used my dummy data to give you an idea.
--Sort data based on timestamp in desc order
SELECT PK_LoginId AS Bal, FK_RoleId AS Cost, AddedDate AS TS
, ROW_NUMBER() OVER (ORDER BY AddedDate DESC) AS Rno
INTO ##tmp
FROM dbo.M_Login WHERE AddedDate IS NOT NULL
--Check how data looks
SELECT Bal, Cost, Rno, TS FROM ##tmp
--Considering ##tmp as your inserted table,
--I just added Row_Number to apply Top 1 Order by desc logic
+-----+------+-----+-------------------------+
| Bal | Cost | Rno | TS |
+-----+------+-----+-------------------------+
| 172 | 10 | 1 | 2012-12-05 08:16:28.767 |
| 171 | 10 | 2 | 2012-12-04 14:36:36.483 |
| 169 | 12 | 3 | 2012-12-04 14:34:36.173 |
| 168 | 12 | 4 | 2012-12-04 14:33:37.127 |
| 167 | 10 | 5 | 2012-12-04 14:31:21.593 |
| 166 | 15 | 6 | 2012-12-04 14:30:36.360 |
+-----+------+-----+-------------------------+
Alternative logic for subtracting cost from last running balance.
--Start a recursive query to subtract balance based on cost
;WITH cte(Bal, Cost, Rno)
AS
(
SELECT t.Bal, 0, t.Rno FROM ##tmp t WHERE t.Rno = 1
UNION ALL
SELECT c.Bal - t.Cost, t.Cost, t.Rno FROM ##tmp t
INNER JOIN cte c ON t.RNo - 1 = c.Rno
)
SELECT * INTO ##Fin FROM cte;
SELECT * FROM ##Fin
Output
+-----+------+-----+
| Bal | Cost | Rno |
+-----+------+-----+
| 172 | 0 | 1 |
| 162 | 10 | 2 |
| 150 | 12 | 3 |
| 138 | 12 | 4 |
| 128 | 10 | 5 |
| 113 | 15 | 6 |
+-----+------+-----+
You have to tweet your columns little bit to get this functionality into your trigger.
I think you can try a trigger on the Fins.
You can use IDENT_CURRENT('Table')) to take the last primary key from the table and make a select.
I think it's better than "select top 1".
To to take the last balance value, set a variable last_bal = select bal from FINS where primary_key = Ident_Current("FINS")
well
first sql is a game where it work with groups or rather "set" so always you have think about that.
if you work with a simple item is correct, it maybe be better approach
declare #myinsert table(id int identity(1,1), company VArchar(35), ts datetime, cost2 smallmoney, bal smallmoney)
insert into #myinsert(company,ts, cost2, bal)
SELECT CORP, CURRENT_TIMESTAMP, COST,
FROM inserted WHERE COALESCE(COST,0) <> 0
declare #current int
select #current = min(id) from #myinsert
while exists(select * from #myinsert where id = #current)
begin
INSERT INTO dbo.FINS
(COMPANY, TS, COST2, BAL)
SELECT COMPANY, CURRENT_TIMESTAMP, COST,
((SELECT TOP 1 BAL FROM FINS WHERE COMPANY = my.COMPANY ORDER BY TS DESC)- COST)
from #myinsert my where id = #current
select #current = min(id) from #myinsert where id > #current
end
i am not giving you exact query .For a moment forget trigger.Because you are unable to test your query .
I suggest to use Output clause .This will atleast help you to construct proper query and test it.
this query is running ok,(if you can use merge then that is best).
Declare #t table
(
BLOC1,BLOC2,BLOC3 ,PRODUCTION ,DELTA --whatever column is require here
)
UPDATE STOR SET
STOR.BLOC1 = T.BLOC1,
STOR.BLOC2 = T.BLOC2,
STOR.BLOC3 = T.BLOC3,
STOR.PRODUCTION = T.PROD,
STOR.DELTA = T.DELTA
Output inserted.BLOC1 ,inserted.BLOC2, and so on into #t
FROM BLDG B INNER JOIN STOR S
ON S.B_ID = B.B_ID
CROSS APPLY dbo.INVENTORIZE(B.B_ID) AS T;
now you have inserted value in table variable #t
SELECT CORP, CURRENT_TIMESTAMP, COST,
BAL,Row_Number() over(partition by company order by TS desc) RN
FROM #t inner join FINS on COMPANY = CORP
WHERE COALESCE(COST,0) <> 0
Verify this query till here.Think of optimizing or trigger later on.
I think i gave good suggestion.and I guess subtraction is not a problem.I am telling to put everything in output clause and analyze the query and test it.
you can use CTE inside trigger also but how will you test it.
;With CTE as
(
SELECT CORP, CURRENT_TIMESTAMP, COST,BAL
ROW_NUMBER()over(ORDER BY TS DESC )rn
FROM inserted
inner join FINS on COMPANY = CORP
WHERE COALESCE(COST,0) <> 0
)
select * from CTE --check this what you are getting
Something like that, Isn't complete.
CREATE TRIGGER trgrCYCLE
ON STOR
FOR UPDATE
AS
begin
declare #last_bal int
declare #company varchar(50)
declare #ts --type
declare #cost int
declare #bal --type
--etc whatever you need
select #company = company, #ts= ts , #cost = cost , #bal = bal from INSERTED
--others selects and sets
set #last_bal = select bal from dbo.FINS where you_primary_key = IDENT_CURRENT('FINS'))
set #last_bal = #last_bal - #cost
Insert INTO FINS (company, ts, cost2, bal) VALUES (#company, #ts, #cost, #last_bal) where --your conditions
end
If, similar to #Shantanu's method, you could associate a sequence with inserted, the virtual table associated with the trigger you could do this by subtracting all the COSTs that come before the current record.
This could be accomplished by adding a rowversion to STOR, which will be updated automatically with each delete.
Then instead of:
((SELECT TOP 1 BAL FROM FINS WHERE COMPANY = CORP ORDER BY TS DESC)- COST)
from inserted ...
make the rowversion RV, and:
(SELECT SUM(X.B) FROM
(SELECT TOP 1 BAL B
FROM FINS
WHERE COMPANY = CORP
ORDER BY TS DESC
UNION
SELECT -COST B
FROM inserted ii
WHERE ii.RV >= i.RV AND ii.CORP = i.CORP
) AS X)
FROM inserted i WHERE COALESCE(COST,0) <> 0
Should do what you want. You could conceivably do this with a timestamp that was more find-grained than CURRENT_TIMESTAMP which, I believe, goes down only to seconds but that requires you update it in the UPDATE statement. The rowversion may cause problems with your STOR insert statements.

SQL Server : SELECT and CASE

I am trying to select data based on some parameters passed to my stored procedure. I have problems with the age, I am trying to do something like this:
If my stored procedure parameter #Age = 1 then I select age between 15 to 18, #Age = 2 then 19 - 25..., apparently this is incorrect, anyone can help. Thanks.:
SELECT
User
FROM
[Member] m
WHERE
((m.Gender = #Gender) or #Gender IS NULL)
and ((DATEDIFF(hour,m.DOB,GETDATE())/8766) Between
CASE
WHEN #Age = 1 THEN (SELECT DATEDIFF(hour, m.DOB, GETDATE())/8766 WHERE (SELECT DATEDIFF(hour, m.DOB, GETDATE())/8766) between 15 and 18)
WHEN #Age = 2 THEN (SELECT DATEDIFF(hour,m.DOB,GETDATE())/8766 WHERE (SELECT DATEDIFF(hour,m.DOB,GETDATE())/8766) between 19 and 25)
END)
I think this is what you are after (probably with some superfluous parenthesis):
Select
[User]
From
[Member] m
Where (
(m.Gender = #Gender) or
#Gender Is Null
) And (
(#Age = 1 And DateDiff(hour, m.Dob, GetDate())/8766 Between 15 and 18) Or
(#Age = 2 And DateDiff(hour, m.Dob, GetDate())/8766 Between 19 and 25)
)
If you've got a lot of clauses, it might be easier to read as (assuming a MemberID Primary Key)
Select
[User]
From
[Member] m
Inner Join (
Select
MemberID,
DateDiff(hour, m.Dob, GetDate())/8766 As Years
From
[Member]
) As y
On m.MemberID = y.MemberID
Where (
(m.Gender = #Gender) or
#Gender Is Null
) And (
(#Age = 1 And y.Year Between 15 and 18) Or
(#Age = 2 And y.Year Between 19 and 25)
)
Even better, you could add the ranges to a separate table called AgeRanges
+-------+------------+----------+
| AgeID | StartYears | EndYears |
+-------+------------+----------+
| 1 | 15 | 18 |
| 2 | 19 | 25 |
| ... | ... | ... |
+-------+------------+----------+
Select
[User]
From
[Member] m
Inner Join
[AgeRanges] a
On DateDiff(hour, m.Dob, GetDate())/8766 Between a.StartYears and a.EndYears And
a.AgeID = #Age
You could also make DateDiff(hour, m.Dob, GetDate())/8766 a computed column on your members table to simplify things (and make indexing possible if performance became an issue).

Resources