Cursor to auto insert Roll Numbers in student table - sql-server

How to go about auto populating student's roll number column in ascending order from 1,2,3... and so on on assign button click in the form ?
TABLE
How the cursor will be invoked?
I'm using stored procedures for all database operations.
SAMPLE CODE
declare #studID int
declare rollCursor CURSOR FOR
select * from TESTING
OPEN rollCursor

If you want a single StudentId, just write in your procedure
-- Where #StudendId will be parameter to your stored procedure
SELECT * FROM TESTING
WHERE StudId = #StudendId
Here is how you can use CURSOR. But note, CURSOR have performance issues. So use it rarely.
DECLARE #StudId INT
DECLARE #FName VARCHAR(50)
DECLARE #ROLL INT
-- Here you declare which all columns you need to loop in Cursor
DECLARE rollCursor CURSOR FOR
select * from TESTING
WHERE StudId = #StudendId
ORDER BY StudId;
OPEN rollCursor
-- Loop starts from here
FETCH NEXT FROM rollCursor
INTO #StudId,#FName,#ROLL
WHILE ##FETCH_STATUS = 0
BEGIN
-- Select studentid one by one from the table
SELECT * FROM TESTING
WHERE StudId = #StudId
-- Fetches next record and increments the loop
FETCH NEXT FROM rollCursor
INTO #StudId,#FName,#ROLL
END
CLOSE rollCursor;
DEALLOCATE rollCursor;
EDIT : 1 (Getting Row number for table)
If you need the result according to the roll then use the below query
-- This will bring you the records with roll number in ascending order
-- If you want in descending order just change ASC to DESC
SELECT studid,Fname,ROW_NUMBER() OVER(ORDER BY roll ASC) roll
FROM StudId
EDIT : 2 (Create identity field)
You need to set roll number column as Identity field, ie a column with an integer value which automatically increments value on new insertions.
Once you set the roll number column as Identity field, try with the below inserts
INSERT INTO TESTING(StudId,Fname)VALUES(10,'A')
INSERT INTO TESTING(StudId,Fname)VALUES(10,'A')
You will not select or include the roll number column in insert. It will automatically increment.
Click here to view more on identity field

Related

Concurrency issue for SQL server transactions for a table

I have inherited a system where there is a table X where points are allocated to users. Below is an example of how this looks like -
When a row is added the NewBalance is the actual total balance for the user.
How this is done is in a transaction (I am attaching values which are dynamically passed):
BEGIN
DECLARE #userId uniqueidentifier = 'DA04C99F-575A-434F-BD69-05F2C111360E'
DECLARE #oldBalance int
DECLARE #newPoints int = 25
SELECT TOP 1 #oldBalance = NewBalance FROM X WHERE UserId = ORDER BY [Date]
DESC
INSERT INTO X (UserId, Date, Value, NewBalance) VALUES (#userId,
SYSDATETIMEOFFSET(), #newPoints, #oldBalance + #newPoints)
END
The above piece of code can be called from multiple modules to add points each of which are running in different isolation levels. It is also possible that this gets called concurrently from 2 different modules - so we end up with something like this -
Obviously 2 different transactions read the same row while fetching the initial #oldBalance and then each added to it resulting in the problem.
We are thinking of making all modules that call this piece of code to run under Serializable isolation level. But we are having trouble replicating the problem in lower environments and so there is no realistic way to test.
Also, from what I understand irrespective of isolation level the SELECT TOP 1 will always place a shared lock on the row so it can be read by other transactions.
Any tips or reading material to solve the problem would be appreciated.
Depending on how much changeability you have over the table structure itself, you could do as others have suggested restrict it to a single row to show only the current row and then use a lock on the table.
You could also implement a counter type for each of the unique records so the current iteration of data inserted is always sequential because you're checking if it's been updated since you started your update.
I have added a simple way of doing the update using a procedure rather than just inserting. I am not aware of your application or database, but if you are able to read the output and store the balance to add you are able to implement a retry until the values match and it enables the insert.
CREATE TABLE X (UserId UniqueIdentifier, Date DATETIME, Value INT, NewBalance INT)
INSERT INTO X VALUES (NEWID(), GETUTCDATE(),10,10)
SELECT * FROM X
BEGIN
DECLARE #userId uniqueidentifier = '5396C445-8AC1-4B46-8E25-A416059D7976'
DECLARE #oldBalance int
DECLARE #newPoints int = 25
SELECT TOP 1 #oldBalance = NewBalance FROM X WHERE UserId = #userId ORDER BY [Date] DESC
EXEC dbo.usp_Update_X #userId, #oldBalance, #newPoints
END
CREATE PROCEDURE dbo.usp_Update_X
(
#UserID UniqueIdentifier
,#OldBalance INT
,#newPoints INT
)
AS
IF #OldBalance = (SELECT TOP 1 NewBalance FROM X WHERE UserId = #UserID ORDER BY Date DESC)
BEGIN
INSERT INTO X (UserId, Date, Value, NewBalance) VALUES (#userId,
SYSDATETIMEOFFSET(), #newPoints, #oldBalance + #newPoints)
Print 'Inserted Successfully'
END
ELSE
Print 'Changed - Get Balance Again'
GO
Once something like this has been implemented, you could then periodically check to ensure the values go in the correct order
SELECT UserId, Date, NewBalance, Value, SUM(Value) OVER (Partition By UserId ORDER BY Date ASC) AS NewBalanceCheck
FROM X

Pulling Values From Temp Table After An Iteration

I'm querying for the total sizes of recent data in certain databases.
I create a table containing the DBs to be queried then iterate over it to get the DB names and total number of times to run the iteration.
I then create a temptable where the needed data will be inserted into.
I run the iteration to grab the information and push it into the temptable for each database.
After the iteration finishes I'm not able to pull the values from this newly created table.
I wrote a little comment next to each portion of code explaining what I'm trying to do and what I expect to happen.
/*check if the #databases table is already present and then drop it*/
IF OBJECT_ID('tempdb..#databases', 'U') IS NOT NULL
begin
drop table #databases;
end
select ArtifactID into #databases from edds.eddsdbo.[Case]
where name like '%Review%'
/*Once this first statement has been run there will now be a
number column that is associated with the artificatID. Each database has an area that is
titled [EDDS'artifactID']. So if the artifactID = 1111111 then the DB would
be accessed at [EDDS1111111]*/
declare #runs int = 1; /*this will track the number of times iterated
over the result set*/
declare #max int = 0; /*this will be the limit*/
declare #databasename sysname='' /*this will allow the population of each
database name*/
/*check if your temp table exists and drop if necessary*/
IF OBJECT_ID('tempdb..#temptable', 'U') IS NOT NULL
begin
drop table #temptable;
end
/*create the temp table as outside the loop*/
create table #temptable(
fileSize dec,
extractedTextSize dec
)
while #runs<=#max
begin
select #max=count(*) from #databases;
/*the #max is now the number of databases inserted in to this table*/
/*This select statement pulls the information that will be placed
into the temptable. This second statment should be inside the loop. One time
for each DB that appeared in the first query's results.*/
/*begin the loop by assigning your database name, I don't know what the
column is called so I have just called it databasename for now*/
select top 1 #databasename = ArtifactID from #databases;
/*generate your sql using the #databasename variable, if you want to make
the database and table names dynamic too then you can use the same formula*/
insert into #temptable
select SUM(fileSize)/1024/1024/1024, SUM(extractedTextSize)/1024/1024
FROM [EDDS'+cast(#databasename as nvarchar(128))+'].[EDDSDBO].[Document] ed
where ed.CreatedDate >= (select CONVERT(varchar,dateadd(d,-
(day(getdate())),getdate()),106))'
/*remove that row from the databases table so the row won't be redone
This will take the #max and lessen it by one*/
delete from #databases where ArtifactID=#databasename;
/* Once the #max is less than 1 then end the loop*/
end
/* Query the final values in the temp table after the iteration is complete*/
select filesize+extractedTextSize as Gigs from #temptable
When that final select statement runs to pull values from #temptable the response is a single gigs column(as expected) but the table itself is blank.
Something is happening to clear the data out of the table and I'm stuck.
I'm not sure if my error is in syntax or a general error of logic but any help would be greatly appreciated.
Made a few tweaks to formatting but main issue is your loop would never run.
You have #runs <= #max, but #max = 1 and #runs = 0 at start so it will never loop
To fix this you can do a couple different things but I set the #max before loop, and in the loop just added 1 to #runs each loop, since you know how many you need #max before loop runs, and just add it to number of runs and do your compare.
But one NOTE there are much better ways to do this then the way you have. Put identity on your #databases table, and in your loop just do where databaseID = loopCount (then you dont have to delete from the table)
--check if the #databases table is already present and then drop it
IF OBJECT_ID('tempdb..#databases', 'U') IS NOT NULL
drop table #databases;
--Once this first statement has been run there will now be a number column that is associated with the artificatID. Each database has an area that is
-- titled [EDDS'artifactID']. So if the artifactID = 1111111 then the DB would be accessed at [EDDS1111111]
select ArtifactID
INTO #databases
FROM edds.eddsdbo.[Case]
where name like '%Review%'
-- set to 0 to start
DECLARE #runs int = 0;
--this will be the limit
DECLARE #max int = 0;
--this will allow the population of each database name
DECLARE #databasename sysname = ''
--check if your temp table exists and drop if necessary
IF OBJECT_ID('tempdb..#temptable', 'U') IS NOT NULL
drop table #temptable;
--create the temp table as outside the loop
create table #temptable(
fileSize dec,
extractedTextSize dec
)
-- ***********************************************
-- Need to set the value your looping on before you get to your loop, also so if you dont have any you wont do your loop
-- ***********************************************
--the #max is now the number of databases inserted in to this table
select #max = COUNT(*)
FROM #databases;
while #runs <= #max
BEGIN
/*This select statement pulls the information that will be placed
into the temptable. This second statment should be inside the loop. One time
for each DB that appeared in the first query's results.*/
/*begin the loop by assigning your database name, I don't know what the
column is called so I have just called it databasename for now*/
select top 1 #databasename = ArtifactID from #databases;
/*generate your sql using the #databasename variable, if you want to make
the database and table names dynamic too then you can use the same formula*/
insert into #temptable
select SUM(fileSize)/1024/1024/1024, SUM(extractedTextSize)/1024/1024
FROM [EDDS'+cast(#databasename as nvarchar(128))+'].[EDDSDBO].[Document] ed
where ed.CreatedDate >= (select CONVERT(varchar,dateadd(d,- (day(getdate())),getdate()),106))
--remove that row from the databases table so the row won't be redone This will take the #max and lessen it by one
delete from #databases where ArtifactID=#databasename;
--Once the #max is less than 1 then end the loop
-- ***********************************************
-- no need to select from the table and change your max value, just change your runs by adding one for each run
-- ***********************************************
--the #max is now the number of databases inserted in to this table
select #runs = #runs + 1 --#max=count(*) from #databases;
end
-- Query the final values in the temp table after the iteration is complete
select filesize+extractedTextSize as Gigs from #temptable
This is second answer, but its alternative to like I mentioned in above and cleaner to post to post as an alternative answer to keep them seperate
This is a better way to do your looping (not fully tested out yet so you will have to verify).
But instead of deleting from your table just add an ID to it and loop through it using that ID. Way less steps and much cleaner.
--check if the #databases table is already present and then drop it
IF OBJECT_ID('tempdb..#databases', 'U') IS NOT NULL
drop table #databases;
--create the temp table as outside the loop
create table #databases(
ID INT IDENTITY,
ArtifactID VARCHAR(20) -- not sure of this ID's data type
)
--check if your temp table exists and drop if necessary
IF OBJECT_ID('tempdb..#temptable', 'U') IS NOT NULL
drop table #temptable;
--create the temp table as outside the loop
create table #temptable(
fileSize dec,
extractedTextSize dec
)
--this will allow the population of each database name
DECLARE #databasename sysname = ''
-- initialze to 1 so it matches first record in temp table
DECLARE #LoopOn int = 1;
--this will be the max count from table
DECLARE #MaxCount int = 0;
--Once this first statement has been run there will now be a number column that is associated with the artificatID. Each database has an area that is
-- titled [EDDS'artifactID']. So if the artifactID = 1111111 then the DB would be accessed at [EDDS1111111]
-- do insert here so it adds the ID column
INSERT INTO #databases(
ArtifactID
)
SELECT ArtifactID
FROM edds.eddsdbo.[Case]
where name like '%Review%'
-- sets the max number of loops we are going to do
select #MaxCount = COUNT(*)
FROM #databases;
while #LoopOn <= #MaxCount
BEGIN
-- your table has IDENTITY so select the one for the loop your on (initalize to 1)
select #databasename = ArtifactID
FROM #databases
WHERE ID = #LoopOn;
--generate your sql using the #databasename variable, if you want to make
--the database and table names dynamic too then you can use the same formula
insert into #temptable
select SUM(fileSize)/1024/1024/1024, SUM(extractedTextSize)/1024/1024
-- dont know/think this will work like this? If not you have to use dynamic SQL
FROM [EDDS'+cast(#databasename as nvarchar(128))+'].[EDDSDBO].[Document] ed
where ed.CreatedDate >= (select CONVERT(varchar,dateadd(d,- (day(getdate())),getdate()),106))
-- remove all deletes/etc and just add one to the #LoopOn and it will be selected above based off the ID
select #LoopOn += 1
end
-- Query the final values in the temp table after the iteration is complete
select filesize+extractedTextSize as Gigs
FROM #temptable

How to sum random rows column in where clause SQL

I have a stored procedure that selects a number of random rows from a table based on a parameter and I need to run this in a loop so it constantly selects random rows until the sum of them is accepted
So far I haven't started the loop yet because I can't figure out how do i use sum of a column as a condition
GO
IF OBJECT_ID('dbo.spx_SELECT_RandomLocalizacoes') IS NOT NULL
DROP PROCEDURE spx_SELECT_RandomLocalizacoes
GO
CREATE PROCEDURE spx_SELECT_RandomLocalizacoes
#Localizacoes_Max int,
#Filtro int,
#Armazem int
AS
BEGIN
SET NOCOUNT ON
SELECT TOP (#Localizacoes_Max) * FROM xInventario
WHERE Armazem = #Armazem AND SUM(Quantidade) > #Filtro
ORDER BY newid()
END
The final result should be a procedure that returns me the rows that obey the condition
EDIT:
I forgot to add that, I have to return the select statement with the random rows with the same seed so i can only do that query once
you can treat your query as a sub-query and SUM it, then apply whatever logic you are looking for
if (SELECT SUM(Randoms.YourField) FROM (SELECT TOP (#Localizacoes_Max) * FROM xInventario
WHERE Armazem = #Armazem AND SUM(Quantidade) > #Filtro
ORDER BY newid()) AS Randoms) = #Target
BEGIN
--do stuff
END

SQL cursor result in infinite loop

I'm trying to insert invoice items to existing invoice with negative value for quantity. I decided to use cursor for this but when I run the query it results in infinite loop.
Here is my code:
declare #cKey char(13);
set #cKey = '1512000000043';
-- declare cursor to get
-- all items for specific invoice
declare c cursor
for
select
acIdent, anQty
from
tHE_MoveItem where acKey = #cKey;
declare #cIdent char (16),
#nQty decimal(19,6),
#nNo int,
#cStatus varchar(2),
#cErrorOut varchar(1024);
open c
fetch c into #cIdent, #nQty
while (##fetch_status=0)
begin
-- add all items with negative qty
-- to same invoice
select #cIdent;
-- invert value
set #nQty = #nQty *-1;
select #nQty;
-- insert ident with negative value to invoice
EXEC dbo.pHE_MoveItemsCreAll #cKey, #cIdent,#nQty, '', 1, #nNo OUTPUT,#cErrorOut OUTPUT,#cStatus OUTPUT;
fetch c into #cIdent, #nQty
end
close c
deallocate c
I'm using SQL Server 2008 R2.
The procedure pHE_MoveItemsCreAll is inserting values in same table as the cursor reads from.
You have to declare your cursor using static keyword (declare c cursor static) to prevent fetching newly inserted records back to cursor.
A static cursor always displays the result set as it was when the cursor was opened. In other case when you're inserting your records into the same table and they met conditions of data selected into cursor - these new records will be retrieved and cursor iterates again.

Why is the natural ID generation in this SQL Stored Proc creating duplicates?

I am incrementing the alphanumeric value by 1 for the productid using stored procedure. My procedure incrementing the values up to 10 records, once its reaching to 10th say for PRD0010...no more its incrementing... however, the problem is it is repeating
the same values PRD0010.. for each SP call.
What could be the cause of this?
create table tblProduct
(
id varchar(15)
)
insert into tblProduct(id)values('PRD00')
create procedure spInsertInProduct
AS
Begin
DECLARE #PId VARCHAR(15)
DECLARE #NId INT
DECLARE #COUNTER INT
SET #PId = 'PRD00'
SET #COUNTER = 0
SELECT #NId = cast(substring(MAX(id), 4, len(MAX(id))) as int)
FROM tblProduct group by left(id, 3) order by left(id, 3)
--here increse the vlaue to numeric id by 1
SET #NId = #NId + 1
--GENERATE ACTUAL APHANUMERIC ID HERE
SET #PId = #PId + cast(#NId AS VARCHAR)
INSERT INTO tblProduct(id)values (#PId)
END
Change
SELECT #NId = cast(substring(MAX(id), 4, len(MAX(id))) as int)
FROM tblProduct group by left(id, 3) order by left(id, 3)
To
SELECT TOP 1
#NId = cast(substring(id, 4, len(id)) as int)
FROM tblProduct order by LEN(id) DESC, ID DESC
You have to remember that
PRD009
is always greater than
PRD0010
or
PRD001
All in all, I think your approach is incorrect.
Your values will be
PRD00
PRD001
...
PRD009
PRD0010
PRD0011
...
PRD0099
PRD00100
This will make sorting a complete nightmare.
In addition to astander's analysis, you also have a concurrency issue.
The simple fix would be to add this at the beginning of your proc:
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE
BEGIN TRANSACTION
And add a COMMIT at the end. Otherwise, two callers of this stored proc will get the same MAX/TOP 1 value from your table, and insert the same value.
Also, you can and should prevent these duplicates from existing by adding a key to your table, for this column. If you already have a PRIMARY KEY on this table, you can add an additional key using a UNIQUE constraint. This will prevent duplicates occurring in the future, no matter what programming errors occur. E.g.
ALTER TABLE tblProduct ADD CONSTRAINT UQ_Product_ID UNIQUE (ID)

Resources