Query to solve a rolling issue - sql

I am seeking guidance on how to solve the following issue?
My data look like:
id
Total
Allowed
Left
1
815
150
655
2
815
15
650
3
815
135
515
4
815
1380
-864
5
815
109
-974
6
815
10
-984
"Left" column is derived from difference of Total and Allowed and then it rolls to next row with Left - Allowed to set value of Left in next rows.
I want to create a new column of Final where if Left value is greater than Allowed, then set the value of Final as Allowed; however, where Left value is less than Allowed, set the value Final for all such rows to 0 except for first row where Left value was less than 0 and set it to the value of last positive Left value. Output should be like this:
id
Total
Allowed
Left
Final
1
815
150
655
150
2
815
15
650
15
3
815
135
515
135
4
815
1380
-864
515
5
815
109
-974
0
6
815
10
-984
0
The way I tried to do is:
Update table
set Final =
Case when Left >= Allowed then Left End
However, i do not understand how to do the remaining part. I am new to this site and let me know if more details are required to help provide solution to me.

You can use an updatable CTE
WITH t AS (
SELECT *,
f = CASE WHEN [Left] > Allowed THEN Allowed
WHEN [Left] > 0 THEN [Left]
WHEN LAG([Left]) OVER (ORDER BY id) > 0 THEN LAG([Left]) OVER (ORDER BY id)
ELSE 0 END
FROM YourTable
)
UPDATE t
SET Final = f;
db<>fiddle

Related

How to Count the number of occurrences for a group of rows and remove duplicated rows?

I have a report where I need to find duplicated groups of rows defined by Column named Cut No and each time this group of rows repeated I have to count the number of its occurrence as 1,2,3 until any column data changes as follows:
Cut No Length BBS No BM Quantity Occurrences
1 3300 2453-04-ST-RF-HA-FO-0411-02 39 2
1 3200 2453-04-ST-RF-HA-FO-0411-02 952 1
1 2125 2453-04-ST-RF-HA-FO-0411-02 77 1 N
2 3300 2453-04-ST-RF-HA-FO-0411-02 39 2
2 3200 2453-04-ST-RF-HA-FO-0411-02 952 1
2 2125 2453-04-ST-RF-HA-FO-0411-02 77 1 N + 1
3 3300 2453-04-ST-RF-HA-FO-0411-02 77 1
3 3200 2453-04-ST-RF-HA-FO-0411-02 952 1
3 2125 **2453-04-ST-RF-HA-FO-0412-02** 77 1 N + 1
The problem is that all codes works on a row level but in this case I am working on changing group of rows defined by cut No and the other four columns should be unique,
When I use Remove Duplicates using the Cut No, it says No Duplicates
And I use Remove Duplicates without using the Cut No, I get the following
Length BBS No BM Quantity
3300 2453-04-ST-RF-HA-FO-0411-02 39 2
3200 2453-04-ST-RF-HA-FO-0411-02 952 1
2125 2453-04-ST-RF-HA-FO-0411-02 77 1
2125 **2453-04-ST-RF-HA-FO-0412-02** 77 1
It gives unique rows But What I want Unique Group of Rows As Explained Above
So how I can do this When each group of rows matched with the next, the Number of Occurrences Increased by 1
Thanks your reply
Moheb Labib
Use subtotals. Select your table and Data -> Subtotal and selec the colums you want subtotals of:
If (and only if) I understand you well, then maybe a quick solution to your problem would be simpy convert each group of rows into a single row having three times more columns e.g. in an additional sheet. Then you may simply work on rows instead of groups of rows.

Convert This SQL Query to ANSI SQL

I would like to convert this SQL query into ANSI SQL. I am having trouble wrapping my head around the logic of this query.
I use Snowflake Data Warehouse, but it does not understand this query because of the 'delete' statement right before join, so I am trying to break it down. From my understanding the row number column is giving me the order from 1 to N based on timestamp and placing it in C. Then C is joined against itself on the rows other than the first row (based on id) and placed in C1. Then C1 is deleted from the overall data, which leaves only the first row.
I may be understanding the logic incorrectly, but I am not used to seeing the 'delete' statement right before a join. Let me know if I got the logic right, or point me in the right direction.
This query was copy/pasted from THIS stackoverflow question which has the exact situation I am trying to solve, but on a much larger scale.
with C as
(
select ID,
row_number() over(order by DT) as rn
from YourTable
)
delete C1
from C as C1
inner join C as C2
on C1.rn = C2.rn-1 and
C1.ID = C2.ID
The specific problem I am trying to solve is this. Let's assume I have this table. I need to partition the rows by primary key combinations (primKey 1 & 2) while maintaining timestamp order.
ID primKey1 primKey2 checkVar1 checkVar2 theTimestamp
100 1 2 302 423 2001-07-13
101 3 6 506 236 2005-10-25
100 1 2 302 423 2002-08-15
101 3 6 506 236 2008-12-05
101 3 6 300 100 2010-06-10
100 1 2 407 309 2005-09-05
100 1 2 302 423 2012-05-09
100 1 2 302 423 2003-07-24
Once the rows are partitioned and the timestamp is ordered within each partition, I need to delete the duplicate checkVar combination (checkVar 1 & 2) rows until the next change. Thus leaving me with the earliest unique row. The rows with asterisks are the ones which need to be removed since they are duplicates.
ID primKey1 primKey2 checkVar1 checkVar2 theTimestamp
100 1 2 302 423 2001-07-13
*100 1 2 302 423 2002-08-15
*100 1 2 302 423 2003-07-24
100 1 2 407 309 2005-09-05
100 1 2 302 423 2012-05-09
101 3 6 506 236 2005-10-25
*101 3 6 506 236 2008-12-05
101 3 6 300 100 2010-06-10
This is the final result. As you can see for ID=100, even though the 1st and 3rd record are the same, the checkVar combination changed in between, which is fine. I am only removing the duplicates until the values change.
ID primKey1 primKey2 checkVar1 checkVar2 theTimestamp
100 1 2 302 423 2001-07-13
100 1 2 407 309 2005-09-05
100 1 2 302 423 2012-05-09
101 3 6 506 236 2005-10-25
101 3 6 300 100 2010-06-10
If you want to keep the earliest row for each id, then you can use:
delete from yourtable yt
where yt.dt > (select min(yt2.dt)
from yourtable yt
where yt2.id = yd.id
);
Your query would not do this, if that is your intent.

Stuck with Trigger on insert and Update

Thank you for looking on this question. I am using MSSQL Server Express.
I am trying to write a trigger which will update client's Eligibility based on their age and their income. I wrote the part with ages, but I am completely stuck with Eligibility part. I have statement which can select Clients who are not eligible based on their income, periodicity(month/year), household size
Select ClientID,c.HshldSize,c.MonthlyYearly,c.AnnualHshldIncome,i.SeniorMo,
StatusID,i.HshldSize
from Clients c
join IncomeEligibility i on c.HshldSize = i.HshldSize
where c.HshldSize= i.HshldSize and c.AnnualHshldIncome >= i.SeniorMo
and StatusID in (1,2)
and c.CategCode = 'SR' and MonthlyYearly ='month'
This select show all clients who not eligible
Example of respond
ClientID HshldSize MonthlyYearly AnnualHshldIncome SeniorMo StatusID HshldSize
28 1 month 1095 977 2 1
51 1 month 1253 977 1 1
63 1 month 1300 977 1 1
73 1 month 1200 977 1 1
96 1 month 1300 977 1 1
101 1 month 1255 977 1 1
160 2 month 1800 1513 1 2
IncomeEligibility Looks like this
HshldSize AKGuidline WomanChildYr WomanChildMo SeniorYr SeniorMo PFDYr PFDMo
1 9020 16687 1391 11726 977 878 73
2 13970 25845 2154 18161 1513 1756 146
3 18920 35002 2917 24596 2050 2634 219
4 23870 44160 3680 31031 2586 3512 292
5 28820 53317 4443 37466 3122 4390 365
6 33770 62475 5206 43901 3658 5268 439
7 38720 71632 5969 50336 4195 6146 512
8 43670 80790 6733 56771 4731 7024 585
9 48620 89947 7496 63206 5267 7902 658
10 53570 99105 8259 69641 5803 8780 731
11 58520 108262 9022 76076 6340 9658 804
12 63470 117420 9785 82511 6876 10536 878
13 68420 126577 10548 88946 7412 11414 951
14 73370 135735 11311 95381 7948 12292 1024
15 78320 144892 12074 101816 8485 13170 1097
16 83270 154050 12838 108251 9021 14048 1170
17 88220 163207 13601 114686 9557 14926 1243
18 93170 172365 14364 121121 10093 15804 1317
19 98120 181522 15127 127556 10630 16682 1390
20 103070 190680 15890 133991 11166 17560 1463
Trigger should set StatusID =5 on the clientrow if they are not eligible .
So far I have this trigger
create trigger tr_EligebilityCheck
on dbo.Clients
FOR INSERT,UPDATE
as
/*Check if Senior not eligible by age*/
If (select CategCode from inserted )='SR'
declare
#DOB date
SET #DOB = (select dob from inserted)
if DATEDIFF(YEAR,#DOB,GETDATE())<60
BEGIN
Update Clients
set StatusID = 5
From Clients c, inserted i
where c.CategCode = 'SR' and i.ClientID = C.ClientID
END
/*Check if Children eligebel by age*/
If (select CategCode from inserted )='CH'
declare
#DOBCH date
SET #DOBCH = (select dob from inserted)
if DATEDIFF(YEAR,#DOBCH,GETDATE()) >=6
BEGIN
Update Clients
set StatusID = 5
From Clients c, inserted i
where c.CategCode ='CH' and i.ClientID = C.ClientID
END
but have no idea how to add checking by Income, Please help if you have an idea how to do this. Also looks like my Trigger doesnot work throwing error when I am rtying insert new reccourd
Msg 512, Level 16, State 1, Procedure tr_EligebilityCheck, Line 6
Subquery returned more than 1 value. This is not permitted when the subquery follows =, !=, <, <= , >, >= or when the subquery is used as an expression.
Thank you!
You have the select statement prepared if that's the only way of identifying who's not eligible. So you just need to update the ones that fall into that criteria. Do it with a subquery (I did not SQLFiddle). You can put the update in whatever position it needs to be in the trigger.
UPDATE Clients
SET
theColName = 'value'
WHERE
ClientID IN (Select ClientID
from Clients c
join IncomeEligibility i
on c.HshldSize = i.HshldSize
where c.HshldSize= i.HshldSize
and c.AnnualHshldIncome >= i.SeniorMo
and StatusID in (1,2)
and c.CategCode = 'SR'
and MonthlyYearly ='month')
The error is about this line If (select CategCode from inserted )='SR' the select CategCode query is returning multiple values so an equals sign is the wrong operator. Although I would put 'SR' in a variable but try this approach.
if #var IN (select CategCode from inserted)
The statement looks for the value IN a resultset, not to see if it's equals to multiple values. Look at your query results and see if it's returning multiple CategCodes, that's what the error is saying.

Adding values that don't exist in SELECT statement output

I am not sure how to ask this, due to my lack of experience, so please bear with me.
I am using a SELECT statement to extract pending applications (i.e. no completed date) and counting their totals (plus some other counting). There are certain project types that I need to gather from, 8 of them, where only 4 of them currently have a pending application. I still need to show the other project types with a 0 in the count column. If I try to union with a query that will pull all data, pending or not, I get duplicate rows (minus the count columns) for the 4 project types with currently pending applications. If I left join to the project type table, I get a null row. Neither are what I want. Does anyone have any suggestions?
Output of the union:
General Construction 0 0 90 0
General Coverage 0 0 90 0
General Coverage 1 740 90 90
General Renewal 0 0 90 0
Individual Construction 0 0 90 0
Individual Coverage 0 0 180 0
Individual Renewal 0 0 90 0
Individual Renewal 2 1027 90 180
Approval 0 0 90 0
Approval 22 565 90 1980
Other - Renewal 0 0 90 0
Other - Renewal 21 1119 90 1890
Output without the necessary '0' rows:
General Coverage 1 740 90 90
Individual Renewal 2 1027 90 180
Other - Renewal 21 1119 90 1890
Approval 22 566 90 1980
Output that I would like to see is:
General Construction 0 0 90 0
General Coverage 1 740 90 90
General Renewal 0 0 90 0
Individual Construction 0 0 90 0
Individual Coverage 0 0 180 0
Individual Renewal 2 1027 90 180
Approval 22 565 90 1980
Other - Renewal 21 1119 90 1890
Please let me know what else I can provide to help you help me.
You don't provide names of your columns or anything like that.
What you need is a driver table with all valid values. You can then use left outer join to get the result set you want:
select driver.val, coalesce(q.col2, 0), coalesce(q.col3, 0), coalesce(q.col4, 0)
from (select 'General Construction' as val from dual union all
select 'General Coverage' as val from dual union all
select 'General Renewal' as val from dual union all
select 'Individual Construction' as val from dual union all
select 'Individual Coverage' as val from dual union all
select 'Individual Renewal' as val from dual union all
select 'Approval' as val from dual union all
select 'Other - Renewal' as val from dual
) driver left outer join
(query without 0 rows) q
on driver.val = q.col1
If NULLs are okay instead of 0s, then the coalesce() is not needed.

Calculating difference from previous record

May I ask for your help with the following please ?
I am trying to calculate a change from one record to the next in my results. It will probably help if I show you my current query and results ...
SELECT A.AuditDate, COUNT(A.NickName) as [TAccounts],
SUM(IIF((A.CurrGBP > 100 OR A.CurrUSD > 100), 1, 0)) as [Funded]
FROM Audits A
GROUP BY A.AuditDate;
The query gives me these results ...
AuditDate D/M/Y TAccounts Funded
--------------------------------------------
30/12/2011 506 285
04/01/2012 514 287
05/01/2012 514 288
06/01/2012 516 288
09/01/2012 520 289
10/01/2012 522 289
11/01/2012 523 290
12/01/2012 524 290
13/01/2012 526 291
17/01/2012 531 292
18/01/2012 532 292
19/01/2012 533 293
20/01/2012 537 295
Ideally, the results I would like to get, would be similar to the following ...
AuditDate D/M/Y TAccounts TChange Funded FChange
------------------------------------------------------------------------
30/12/2011 506 0 285 0
04/01/2012 514 8 287 2
05/01/2012 514 0 288 1
06/01/2012 516 2 288 0
09/01/2012 520 4 289 1
10/01/2012 522 2 289 0
11/01/2012 523 1 290 1
12/01/2012 524 1 290 0
13/01/2012 526 2 291 1
17/01/2012 531 5 292 1
18/01/2012 532 1 292 0
19/01/2012 533 1 293 1
20/01/2012 537 4 295 2
Looking at the row for '17/01/2012', 'TChange' has a value of 5 as the 'TAccounts' has increased from previous 526 to 531. And the 'FChange' would be based on the 'Funded' field. I guess something to be aware of is the fact that the previous row to this example, is dated '13/01/2012'. What I mean is, there are some days where I have no data (for example over weekends).
I think I need to use a SubQuery but I am really struggling to figure out where to start. Could you show me how to get the results I need please ?
I am using MS Access 2010
Many thanks for your time.
Johnny.
Here is one approach you could try...
SELECT B.AuditDate,B.TAccounts,
B.TAccount -
(SELECT Count(NickName) FROM Audits WHERE AuditDate=B.PrevAuditDate) as TChange,
B.Funded -
(SELECT Count(*) FROM Audits WHERE AuditDate=B.PrevAuditDate AND (CurrGBP > 100 OR CurrUSD > 100)) as FChange
FROM (
SELECT A.AuditDate,
(SELECT Count(NickName) FROM Audits WHERE AuditDate=A.AuditDate) as TAccounts,
(SELECT Count(*) FROM Audits WHERE (CurrGBP > 100 OR CurrUSD > 100)) as Funded,
(SELECT Max(AuditDate) FROM Audits WHERE AuditDate<A.AuditDate) as PrevAuditDate
FROM
(SELECT DISTINCT AuditDate FROM Audits) AS A) AS B
Instead of using a Group By I've used subquerys to get both TAccounts and Funded, as well as the Previous Audit Date, which is then used on the main SELECT statement to get TAccounts and Funded again but this time for the previous date, so that any required calculation can be done against them.
But I would imagine this may be slow to process
It's a shame MS never made this type of thing simple in Access, how many rows are you working with on your report?
If it's under 65K then I would suggest dumping the data on to an Excel spreadsheet and using a simple formula to calculate the different between rows.
You can try something like the following (sql is untested and will require some changes)
SELECT
A.AuditDate,
A.TAccounts,
A.TAccounts - B.TAccounts AS TChange,
A.Funded,
A.Funded - B.Funded AS FChange
FROM
( SELECT
ROW_NUMBER() OVER (ORDER BY AuditDate DESC) AS ROW,
AuditDate,
COUNT(NickName) as [TAccounts],
SUM(IIF((CurrGBP > 100 OR CurrUSD > 100), 1, 0)) as [Funded]
FROM Audits
GROUP BY AuditDate
) A
INNER JOIN
( SELECT
ROW_NUMBER() OVER (ORDER BY AuditDate DESC) AS ROW,
AuditDate,
COUNT(NickName) as [TAccounts],
SUM(IIF((CurrGBP > 100 OR CurrUSD > 100), 1, 0)) as [Funded]
FROM Audits
GROUP BY AuditDate
) B ON B.ROW = A.ROW + 1