Cursor to update values in certain fashion - sql-server

In SQL Server 2008 R2, I have a table like this:
ID Dates Count
1 03-02-2014 2
2 04-02-2014 1
3 05-02-2014 NULL
4 06-02-2014 1
5 07-02-2014 3
6 08-02-2014 NULL
7 09-02-2014 2
8 10-02-2014 NULL
9 11-02-2014 1
10 12-02-2014 3
11 13-02-2014 NULL
12 14-02-2014 1
I have an INT variable having some value such as #XCount = 15.
My requirement is to update the count column with (#XCount - Count) such as the result of previous record will be subtracted by the Count value in the next record.
Result:
ID Dates Count
1 03-02-2014 13 (15-2)
2 04-02-2014 12 (13-1)
3 05-02-2014 12 (12-0)
4 06-02-2014 11 (12-1)
5 07-02-2014 8 (11-3)
6 08-02-2014 8 (8-0)
7 09-02-2014 6 (8-2)
8 10-02-2014 6 (6-0)
9 11-02-2014 5 (6-1)
10 12-02-2014 2 (5-3)
11 13-02-2014 2 (2-0)
12 14-02-2014 1 (2-1)
I'm reluctant to use cursors as a solution. Can somebody help me?

How about something like
DECLARE #XCount INT = 15
;WITH Vals AS(
SELECT ID, Dates, [Count] OriginalCount, #XCount - ISNULL([COUNT],0) NewCount
FROM Table1
WHERE ID = 1
UNION ALL
SELECT t.ID, t.Dates, t.[Count], v.NewCount - ISNULL(t.[Count],0)
FROM Table1 t INNER JOIN Vals v ON t.ID = v.ID + 1
)
SELECT *
FROM Vals
SQL Fiddle DEMO
Do note thought that this is a recursive query, and that sometimes (until the tech allows for it, such as SQL SERVER 2012 LAG or Running totals) old does work.

Related

Finding A Time When A Value Changed

I am still learning many new things about SQL such as PARTITION BY and CTEs. I am currently working on a query which I have cobbled together from a similar question I found online. However, I can not seem to get it to work as intended.
The problem is as follows -- I have been tasked to show rank promotions in an organization from the begining of 2022 to today. I am working with 2 primary tables, an EMPLOYEES table and a PERIODS table. This periods table captures a snapshot of any given employee each month - including their rank at the time. Each of these months is also assigned a PeriodID (e.g. Jan 2022 = PeriodID 131). Our EMPLOYEE table holds the employees current rank. These ranks are stored as an int (e.g. 1,2,3 with 1 being lowest rank). It is possible for an employee to rank up more than once in any given month.
I have simplified the used query as much as I can for the sake of this problem. Query follows as:
;WITH x AS
(
SELECT
e.EmployeeID, p.PeriodID, p.RankID,
rn = ROW_NUMBER() OVER (PARTITION BY e.EmployeeID ORDER BY p.PeriodID DESC)
FROM employees e
LEFT JOIN periods p on p.EmployeeID= e.EmployeeID
WHERE p.PeriodID <= 131 AND p.PeriodID >=118 --This is the time range mentioned above
),
rest AS (SELECT * FROM x WHERE rn > 1)
SELECT
main.EmployeeID,
PeriodID = MIN(
CASE
WHEN main.CurrentRankID = Rest.RankID
THEN rest.PeriodID ELSE main.PeriodID
END),
main.RankID, rest.RankID
FROM x AS main LEFT OUTER JOIN rest ON main.EmployeeID = rest.EmployeeID
AND rest.rn >1
LEFT JOIN periods p on p.EmployeeID = e.EmployeeID
WHERE main.rn = 1
AND NOT EXISTS
(
SELECT 1 FROM rest AS rest2
WHERE EmployeeID = rest.EmployeeID
AND rn < rest.rn
AND main.RankID <> rest.RankID
)
and p.PeriodID <= 131 AND p.PeriodID >=118
GROUP BY main.EmployeeID, main.PeriodID, main.RankID, rest.RankID
As mentioned before, this query was borrowed from a similar question and modified for my own use. I imagine the bones of the query is good and maybe I have messed up a variable somewhere but I can not seem to locate the problem line. The end goal is for the query to result in a table showing the EmployeeID, PeriodID, the rank they are being promoted from, and the rank they are being promoted to in the month the promotion was earned. Similar to the below.
EmployeeID
PeriodID
PerviousRankID
NewRank
123
131
1
2
123
133
2
3
Instead, my query is spitting out repeating previous/current ranks and the PeriodIDs seem to be static (such as what is shown below).
EmployeeID
PeriodID
PerviousRankID
NewRank
123
131
1
1
123
131
1
1
I am hoping someone with a greater knowledge base on these functions is able to quickly notice my mistake.
If we assume some example DML/DDL (it's really helpful to provide this with your question):
DECLARE #Employees TABLE (EmployeeID INT IDENTITY, Name VARCHAR(20), RankID INT);
DECLARE #Periods TABLE (PeriodID INT, EmployeeID INT, RankID INT);
INSERT INTO #Employees (Name, RankID) VALUES ('Jonathan', 10),('Christopher', 10),('James', 10),('Jean-Luc', 8);
INSERT INTO #Periods (PeriodID, EmployeeID, RankID) VALUES
(1,1,1),(2,1,1),(3,1,1),(4,1,8 ),(5,1,10),(6,1,10),
(1,2,1),(2,2,1),(3,2,1),(4,2,8 ),(5,2,8 ),(6,2,10),
(1,3,1),(2,3,1),(3,3,7),(4,3,10),(5,3,10),(6,3,10),
(1,4,1),(2,4,1),(3,4,1),(4,4,8 ),(5,4,9 ),(6,4,9 )
Then we can accomplish what I think you're looking for using a OUTER APPLY then aggregates the values based on the current-row values:
SELECT e.EmployeeID, e.Name, e.RankID AS CurrentRank, ap.PeriodID AS ThisPeriod, p.PeriodID AS LastRankChangePeriodID, p.RankID AS LastRankChangedFrom, ap.RankID - p.RankID AS LastRankChanged
FROM #Employees e
LEFT OUTER JOIN #Periods ap
ON e.EmployeeID = ap.EmployeeID
OUTER APPLY (
SELECT EmployeeID, MAX(PeriodID) AS PeriodID
FROM #Periods
WHERE EmployeeID = e.EmployeeID
AND RankID <> ap.RankID
AND PeriodID < ap.PeriodID
GROUP BY EmployeeID
) a
LEFT OUTER JOIN #Periods p
ON a.EmployeeID = p.EmployeeID
AND a.PeriodID = p.PeriodID
ORDER BY e.EmployeeID, ap.PeriodID DESC
Using the correlated subquery we get a view of the data which we can filter using the current-row values, and we aggregate that to return the period we're looking for (where it's before this period, and it's not the same rank). Then it's just a join back to the Periods table to get the values.
You used an LEFT JOIN, so I've preserved that using an OUTER APPLY. If you wanted to filter using it, it would be a CROSS APPLY instead.
EmployeeID
Name
CurrentRank
ThisPeriod
LastRankChangePeriodID
LastRankChangedFrom
LastRankChanged
1
Jonathan
10
6
4
8
2
1
Jonathan
10
5
4
8
2
1
Jonathan
10
4
3
1
7
1
Jonathan
10
3
1
Jonathan
10
2
1
Jonathan
10
1
2
Christopher
10
6
5
8
2
2
Christopher
10
5
3
1
7
2
Christopher
10
4
3
1
7
2
Christopher
10
3
2
Christopher
10
2
2
Christopher
10
1
3
James
10
6
3
7
3
3
James
10
5
3
7
3
3
James
10
4
3
7
3
3
James
10
3
2
1
6
3
James
10
2
3
James
10
1
4
Jean-Luc
8
6
5
9
-1
4
Jean-Luc
8
5
4
8
1
4
Jean-Luc
8
4
3
1
7
4
Jean-Luc
8
3
4
Jean-Luc
8
2
4
Jean-Luc
8
1
Now we can see what the previous change looked like for each period. Currently Jonathan is has RankID 10. Last time that was different was in PeriodID 4 when it was 8. The same was true for PeriodID 5. In PeriodID 4 he had RankID 8, and prior to that he had RankID 1. Before that his Rank hadn't changed.
Jean-Luc was actually demoted as his last change. I don't know if this is possible within your model.

SQL Server Rank Function without taking into account some flagged values

Here is my problem: I have a list of flagged values, I want to see where those values would be in the case they weren't flagged. But I don't want the other flagged values to influence the order.
Note: Flagged values are the ones with CurrentPlace 10000
ID Value CurrentPlace
------------------------
1 2 1
2 8 3
3 3 2
4 4 10000
5 5 10000
6 10 10000
Using:
select *
from
(select
id, value,
rank() over (order by Value asc) as Rank
from
tbl1) r
where
r.ID in (select id from tbl1 where CurrentPlace = 10000)
Desired output:
ID Value Rank
------------------
4 4 3
5 5 3
6 10 4
But I'm getting this instead:
ID Value Rank
------------------
4 4 3
5 5 4
6 10 6
Any help will be appreciated
Thank you guys
I've solved with
SELECT ID, Value, Rank
FROM tbl1 a
CROSS APPLY
(SELECT isnull(max(currentPlace),0) + 1 AS Rank FROM tbl1 WHERE value < a.value and currentPlace <> 10000) b
WHERE a.CurrentPlace = 10000
Please feel free to comment this out.

Top 9 Distinct records after using sub query for cumulative sum

I have SQL data table with following data structure
ID Hrly Hshed Dust_removal_to_done Dust_removal_done Update_datetime
2 ER MGS 4 4 2009-05-05
3 ER AQ 4 2 2009-05-05
4 SR ANDA 4 4 2009-05-05
5 ECR HOME 5 5 2009-05-05
6 NR GZB 5 5 2009-05-05
7 NR LDH 5 5 2009-05-05
8 NCR JHS 5 5 2009-05-05
9 NCR CNB 5 5 2009-06-05
10 SCR LGD 5 5 2009-06-05
11 SCR LGD 5 5 2009-05-05
the data is fed by users on daily basis.
Further I am using a stored procedure for cumulative sum of 'Dust_removal_done' as
ALTER PROCEDURE [dbo].[RAP_regular] as
SELECT Hshed, HRly, Dust_removal_to_done, Dust_removal_done, (SELECT SUM(Dust_removal_done) FROM TPHRAP_regular t2
where t2.Hshed = TPHRAP_regular.Hshed and t2.Update_datetime <= TPHRAP_regular.Update_datetime) as cumulative_dust_removal
FROM TPHRAP_regular
This stored procedure is giving me result as under
Hshed Hrly Dust_removal_to_done Dust_removal_done cumulative_dust_removal
MGS ER 4 4 4
AQ ER 4 2 2
ANDA SR 4 4 4
HOME ECR 5 5 5
GZB NR 5 5 5
LDH NR 5 5 5
JHS NCR 5 5 5
CNB NCR 5 5 5
LGD SCR 5 5 10
LGD SCR 5 5 5
This is working fine. Now the issue is that there are only 9 Hsheds and therefore I want to display only 9 latest records (unique Hshed along with cumulative column) in my grid view as final result so that no Hshed will repeate in the table. How to achieve this? please help.
You need to change your stored procedure(Has to be in it since you are discarding the date field in it).
You can use ROW_NUMBER() window function to filter only the latest records, like this:
SELECT Hshed,HRly,Dust_removal_to_done,Dust_removal_done,cumulative_dust_removal
FROM(
SELECT Hshed, HRly, Dust_removal_to_done, Dust_removal_done,
(SELECT SUM(Dust_removal_done) FROM TPHRAP_regular t2
where t2.Hshed = TPHRAP_regular.Hshed
and t2.Update_datetime <= TPHRAP_regular.Update_datetime) as cumulative_dust_removal,
ROW_NUMBER() OVER (PARTITION BY Hshed ORDER BY Update_datetime DESC) as rnk
FROM TPHRAP_regular)
WHERE rnk = 1
EDIT: You should also use SUM() OVER(..) for cumulative sum , no need to select from the table twice:
SELECT t.Hshed,
t.HRly,
t.Dust_removal_to_done,
t.Dust_removal_done,
t.cumulative_dust_removal
FROM (SELECT Hshed,
HRly,
Dust_removal_to_done,
Dust_removal_done,
SUM(Dust_removal_done) OVER(PARTITION BY Hshed ORDER BY Update_datetime) as cumulative_dust_removal,
ROW_NUMBER() OVER(PARTITION BY Hshed ORDER BY Update_datetime DESC) as rnk
FROM TPHRAP_regular) t
WHERE t.rnk = 1

Get Unmatched records from the 3 table

I have 3 tables. The first table 'Status_Mapping' has following columns
Status_original Status_Site
accepted Call Verified
duplicate Duplicate Leads
dq DQ
'Lead_transaction' has the columns:
Lead_transaction_id Rate Status
11 0.01 accepted
12 0.02 accepted
13 0.01 newstatus
'Lead_Instance' table:
Lead_Instance_id Lead_transaction_id product_id affiliate_id
1 11 6 10
2 12 7 11
3 13 6 10
What I want to do is get the count(lead_instance_id) and sum(rate) for status which are not present in status_mapping table and should display status as "other", with product_id = 6 and affiliate_id = 10 My End result should be like
Total Sum Status
1 0.01 Other
you can start with this query:
select count(distinct a.Lead_Instance_id), sum(b.Rate)
from
Lead_Instance as a
inner join
Lead_transaction as b
on (a.Lead_transaction_id = b.Lead_transaction_id)
where
b.Status not in (select distinct Status_original from Status_Mapping)
and a.product_id = 6
and a.affiliate_id = 10

Displaying the top two of summed values by group

I have a query named "QueryTotalGrades" which has three fields (Group, StudentID and Mark). each studentID has more than one mark. What I want to do is to create another query that conduct the following :
1- Sum mark for each studentID as a sumOfMark (Descending order)
2- Display the top 2 of sumOfMarks per group.
Example: let say that the "QueryTotalGrades" has the following values.
I'm using Microsoft access 2013
Group StudentID Mark
1 1 8
1 1 7
1 1 8
1 2 7
1 2 7
1 2 7
1 3 9
1 3 9
1 3 9
2 4 5
2 4 7
2 4 5
2 5 7
2 5 7
2 5 7
2 6 6
2 6 6
2 6 6
3 7 8
3 7 7
3 7 8
3 8 7
3 8 7
3 8 7
3 9 10
3 9 10
3 9 10
,so the output that I want should be as following
Group StudentID SumOfMark
1 3 27
1 1 23
2 5 21
2 6 18
3 9 30
3 7 23
I have tried many solutions, but no avail. HELP
A little longwinded but:
select
t1.[Group], t1.StudentID, t1.SumOfMark
from
(select [Group], StudentID, sum(Mark) as SumOfMark
from QueryTotalGrades
group by [Group], StudentID) as t1
where
(select count(*) from
(select [Group], StudentID, sum(Mark) as SumOfMark
from QueryTotalGrades
group by [Group], StudentID) as t2
where
t2.[Group] = t1.[Group] and
t2.SumOfMark >= t1.SumOfMark) <= 2
order by
t1.[Group], t1.SumOfMark desc
You can play with it here: SQL Fiddle
Query
;with cte as
(
select rn=row_number() over
(
partition by [Group]
order by sum(Mark) desc
),
[Group],StudentID,
sum(Mark) as SumOfMark
from student
group by [Group],StudentID
)
select [Group],StudentId,SumOfMark from cte where rn in (1,2);
fiddle demo

Resources