Selecting the values from a table in sql using a loop - sql-server

I have a table with a certain amount of ids and I want to use these seperate ids to retrieve data from another table.
set #CurrentRow = 0
set #RowsToProcess = (SELECT COUNT(*) FROM #QuestionsPrimaryTable)
WHILE(#CurrentRow < #RowsToProcess)
BEGIN
DECLARE #id int
DECLARE #value varchar(200)
SET #CurrentRow = #CurrentRow + 1
SELECT #id = Q.QuestionsId FROM #QuestionsPrimaryTable Q
SET #value = (SELECT Q.QuestionPrimaryDescription FROM QuestionPrimary Q WHERE Q.QuestionPrimaryID = #id)
PRINT #value
END
the seperate id values I am trying to retrieve is 5, 7, 9
as it is at the moment I only retrieve value of 9
How can I retrieve the separate id values?

I'm not sure if what you're after actually requires a loop. Cursors are very rarely required in SQL, so I'd always look to achieve the result without one if possible.
Are you looking for something like this, where you can JOIN QuestionPrimary and #QuestionsPrimaryTable, and filter results where the ID in 5 or 7 or 9?
SELECT qp.QuestionPrimaryID, qp.QuestionPrimaryDescription
FROM QuestionPrimary qp
INNER JOIN #QuestionsPrimaryTable qpt
ON qp.QuestionPrimaryID = qpt.[JOIN_COLUMN]
WHERE qpt.QuestionPrimaryID IN(5,7,9)

If you absolutely need to loop through this data, you will need to add something to your script that will move to the next record in #QuestionsPrimaryTable. The way it is currently written it sets #Id to the same value during every iteration.
Depending on how you plan to use #QuestionsPrimaryTable, you could simply add a delete into the loop to remove the last record you selected.
set #CurrentRow = 0
set #RowsToProcess = (SELECT COUNT(*) FROM #QuestionsPrimaryTable)
WHILE(#CurrentRow < #RowsToProcess)
BEGIN
DECLARE #id int
DECLARE #value varchar(200)
SET #CurrentRow = #CurrentRow + 1
SELECT #id = MAX(Q.QuestionsId) FROM #QuestionsPrimaryTable Q
SET #value = (SELECT Q.QuestionPrimaryDescription FROM QuestionPrimary Q WHERE Q.QuestionPrimaryID = #id)
PRINT #value
DELETE #QuestionsPrimaryTable
WHERE QuestionsId = #id
END
That being said, there is likely a much better way to accomplish this. If you can elaborate on your question, we can probably provide a better solution for you.

Loops should be avoided wherever a set based approach is feasible. Having said that, in case you definitely want a loop, then this should fix your issue:
SET #CurrentRow = #CurrentRow + 1 -- first value is 1
SELECT #id = Q.QuestionsId
FROM (
SELECT QuestionsId,
ROW_NUMBER() OVER (ORDER BY QuestionsId) AS rn
FROM #QuestionsPrimaryTable) Q
WHERE Q.rn = #CurrentRow
In your code the same QuestionsId value is being fetched for each loop iteration. Using ROW_NUMBER() you can access the #CurrentRow record.

Related

How do I use WHILE loop in CASE statement in SQL

In 'CASE' statement in SQL we use a bool condition and get a TRUE or FALSE result. In this situation I have to use non-bool unlimited condition. But I can't...
ALTER proc [dbo].[sp_StudentList](#CreatedBy nvarchar(max))
as
begin
declare #LikedBy nvarchar(max) = (Select LikedBy from LikeStatus)
declare #TeacherRequestID int = (Select TeacherRequestID from LikeStatus where LikedBy=#CreatedBy)
declare #UserName nvarchar(max) = #CreatedBy
declare #i int = 1
declare #NumberOfRows int = (select count(*) from TeacherRequest)
select SP.StuThana, SP.StuDist, TR.StudentName,TR.StudentCode, TR.Class, TR.Subject, TR.StuGroup,TR.StuRelation, TR.Institute,TR.Status, TR.LikeStatus,
**CASE
WHEN
WHILE(#i <= #NumberOfRows)
BEGIN
#TeacherRequestID = TR.ID THEN 'Liked' Else 'Like'
set #i = #i + 1
END
END as LikeFlag**
from StudentsProfile SP join TeacherRequest TR on SP.CreatedBy=TR.CreatedBy
--sp_StudentList 'teacher1#gmail.com'
end
The technical answer to your question as posed in your title is that you can't.
declare #i int = 5;
select case when (while #i > 0 begin set #i = #i - 1 end) then 1 else 0 end;
-- Incorrect syntax near the keyword 'while'
Is your intention to just determine whether a student listed in a row likes the associated teacher? If so, then you're looking for whether an entry exists in another table, not how often it occurs. And I would tie it to sp.createdBy, not #createdBy.
select // ...,
likeFlag =
case when exists (
select 0
from likeStatus ls
where ls.likedBy = sp.createdBy
and ls.TeacherRequestId = tr.id
) then 'Liked'
else 'Like'
end
from studentsProfile sp
join teacherRequest tr on sp.createdBy = tr.createdBy
If for some reason you really only need 'Liked' based on #createdBy, then change ls.likedBy = sp.createdBy to ls.likedBy = #createdBy, but I don't see a strong use case for that.

T-SQL While Infinite Loop

The goal of the below script is to delete all records in a table for all the distinct users on it except the two first records for each user.
The thing is that the script goes into an infinite loop between these two lines
WHILE ##FETCH_STATUS = 0
SET #Event = 0;
The complete script is
DECLARE #Event int, #User int;
DECLARE cUsers CURSOR STATIC LOCAL FOR SELECT DISTINCT(UserID) FROM Identifications;
OPEN cUsers
FETCH NEXT FROM cUsers INTO #User;
WHILE ##FETCH_STATUS = 0
SET #Event = 0;
BEGIN
DECLARE cRows CURSOR STATIC LOCAL FOR
SELECT EventIdentificacionId FROM Identifications WHERE UserId = #User AND EventIdentificacionId NOT IN
(SELECT TOP 2 EventIdentificacionId FROM Identifications WHERE UserId = #User ORDER BY EventIdentificacionId);
OPEN cRows
FETCH NEXT FROM cRows INTO #Event;
WHILE ##FETCH_STATUS = 0
BEGIN
DELETE FROM Identifications WHERE EventIdentificacionId = #Event;
FETCH NEXT FROM cRows INTO #Event;
END
CLOSE cRows;
DEALLOCATE cRows;
FETCH NEXT FROM cUsers INTO #User;
END
CLOSE cUsers;
DEALLOCATE cUsers;
Can anybody give me some solution/explanation please?
As I wrote in my comment, There are far better ways to do such a thing than using a cursor, let alone a couple of nested cursors.
One such better option is to use a common table expression and row_number, and then delete the rows directly from the common table expression.
I'm not entirely sure this code is correct because I have no real way to test it as you didn't provide sample data or desired results, but I came up with that based on the code in the question:
;WITH CTE AS
(
SELECT UserId,
EventIdentificacionId,
ROW_NUMBER() OVER(PARTITION BY UserId ORDER BY EventIdentificacionId) As Rn
FROM Identifications
)
DELETE
FROM CTE
WHERE Rn > 2 -- Delete all but the first two rows
Change this line as shown:
DECLARE #Event int = 0, #User int = 0;
And remove this line
SET #Event = 0;
The reason you have an infinite loop is that this code:
WHILE ##FETCH_STATUS = 0
SET #Event = 0;
BEGIN
Is actually this:
-- A loop of a single instruction, with no exit criteria
WHILE ##FETCH_STATUS = 0 SET #Event = 0;
-- begin a new code block, with no condition or loop
BEGIN

using script in a variable

Can you declare a variable, set it as a script definition and then execute it repeatedly throughout your script? I understand how to set a variable to the result of a script, but I want to re-use the definition itself. This is because I want to occasionally get the count from a script and sometimes the top result throughout the rest of my script and I want to make it so the script is easily customized by only needing to change the script once at the beginning.
An example:
declare #RepeatScript nvarchar(200)
declare #count int
declare #topresult int
set #RepeatScript = ' from Table1 where something = 1 and something else > getdate()-5'
set #count = select count(ID) & #RepeatScript
set #topresult = select top 1 (ID) & #RepeatScript
This very simple case would be simple to fix, but if I wanted to reference the same set of information multiple times without having to create and drop a temp_table over and over, this would be very helpful. I do this kind of thing in MS Access all the time, but I can't seem to figure out how to do it in SSMS.
You don't need to repeatedly run these queries. You don't even need to run more than 1 query to capture this information. This will capture both pieces of data in a single query. You can then reference that information anywhere else within the current batch. This meets your criteria of simply changing the script at the beginning.
declare #count int
, #topresult int
select #count = count(ID)
, #topresult = MAX(ID) --MAX would the same thing as top 1 order by ID desc
from Table1
where something = 1
declare #RepeatScript nvarchar(200)
declare #count varchar(200)
declare #topresult varchar(200)
set #RepeatScript = ' from Table1 where something = 1 and something else > getdate()-5'
set #count = 'select count(ID) '+#RepeatScript+''
set #topresult = 'select top 1 (ID)'+#RepeatScript+''
print (#count)
print (#topresult)
Something like that? but instead of using print you would be using exec to run the select statement. Does that help?

SQL Server 2012 Trigger

Hey guys thank you in advance for any help,
I have this trigger in my SQL Server 2012 database
USE Teste_TextMining
CREATE TRIGGER Noticia07032016 ON dbo.textos
AFTER INSERT
AS
DECLARE #ID INT
SET #ID = ( SELECT MAX(ID_texto) FROM dbo.textos)
DECLARE #tag NVARCHAR(MAX)
SET #tag = ( SELECT TOP 1 keyphrase
FROM semantickeyphrasetable(textos, *)
WHERE document_key=#ID)
BEGIN
UPDATE dbo.textos
SET tag = UPPER(#tag)
WHERE ID_texto = #ID
END
BEGIN
UPDATE dbo.textos
SET data = GETDATE()
WHERE ID_texto = #ID
END
GO
And as you can see it should update 2 values the "tag" row and the "data" row once something is inserted in the table, however its only updating the "data" row.
If i just select this piece of code and run/debug it, it actually updates both rows, any idea why this is hapening ?
DECLARE #ID INT
SET #ID = ( SELECT MAX(ID_texto) FROM dbo.textos)
DECLARE #tag NVARCHAR(MAX)
SET #tag = ( SELECT TOP 1 keyphrase
FROM semantickeyphrasetable(textos, *)
WHERE document_key=#ID)
BEGIN
UPDATE dbo.textos
SET tag = UPPER(#tag)
WHERE ID_texto = #ID
END
BEGIN
UPDATE dbo.textos
SET data = GETDATE()
WHERE ID_texto = #ID
END
Once again thank you in advance for your help and time.
I assume that you are performing the following query simply to get the inserted row:
SELECT MAX(ID_texto) FROM dbo.textos
That won't work, as others have pointed out. If you insert more than one row at once, only the last in the set will be modified by the trigger.
Do a JOIN on the INSERTED table to get the new rows, then another JOIN on semantickeyphrasetable(textos, *) to get the tag values. Something like this:
USE Teste_TextMining
CREATE TRIGGER Noticia07032016 ON dbo.textos
AFTER INSERT
AS
BEGIN
UPDATE T
SET tag = UPPER(K.keyphrase), data = GETDATE()
FROM dbo.textos T
JOIN INSERTED ON INSERTED.ID_texto = T.ID_texto
LEFT JOIN (
SELECT TOP 1 document_key, keyphrase
FROM semantickeyphrasetable(textos, *)
) K ON K.document_key=T.ID_texto
END
GO
Triggers will basically trigger once for each batch operation, so you should perform your logic based on this reality. This is also in SQL spirit, which favors (read as performs better) set based operations.
All inserted items are stored into a special table, called inserted, so you should join with this table to know what are the exact records that were touched:
CREATE TRIGGER Noticia07032016 ON dbo.textos
AFTER INSERT
AS
BEGIN
DECLARE #ID INT
SET #ID = ( SELECT MAX(ID_texto) FROM dbo.textos)
DECLARE #tag NVARCHAR(MAX)
SET #tag = ( SELECT TOP 1 keyphrase
FROM semantickeyphrasetable(textos, *)
WHERE document_key=#ID)
BEGIN
UPDATE Dest
SET tag = UPPER(#tag)
FROM dbo.textos Dest
JOIN inserted I ON I.ID_texto = Dest.ID_texto
WHERE ID_texto = #ID
END
BEGIN
UPDATE Dest
SET data = GETDATE()
FROM dbo.textos Dest
JOIN inserted I ON I.ID_texto = Dest.ID_texto
WHERE ID_texto = #ID
END
END
The above is not tested, but should help you get an idea on how to proceed to actually update records that were inserted.
Did this answer ever get solved?
If not, why not just add both updates in one line instead of having 2 BEGIN...END blocks?
CREATE TRIGGER Noticia07032016 ON dbo.textos
AFTER INSERT
AS
BEGIN
DECLARE #ID INT
SET #ID = ( SELECT MAX(ID_texto) FROM dbo.textos)
DECLARE #tag NVARCHAR(MAX)
SET #tag = ( SELECT TOP 1 keyphrase
FROM semantickeyphrasetable(textos, *)
WHERE document_key=#ID)
BEGIN
UPDATE Dest
SET tag = UPPER(#tag), data = GETDATE()
FROM dbo.textos Dest
JOIN inserted I ON I.ID_texto = Dest.ID_texto
WHERE ID_texto = #ID
END
END
Use the below code. In your case I think the trigger is firing before semantickeyphrasetable TABLE insertion done. So updating nothing in first begin as #tag is empty.
Its better to put the trigger in child table.(If we need to update Parent table with child table data.)
USE Teste_TextMining
CREATE TRIGGER Noticia07032016 ON dbo.textos
AFTER INSERT
AS
DECLARE #ID INT
,#tag NVARCHAR(MAX)
SELECT #ID = ID_texto
FROM INSERTED
SET #tag = (
SELECT TOP 1 keyphrase
FROM semantickeyphrasetable(textos, *)
WHERE document_key = #ID
)
UPDATE dbo.textos
SET tag = UPPER(#tag)
,
SET data = GETDATE()
WHERE ID_texto = #ID
GO
Note: When multiple insertion done, it will fail.

Update Large Number of rows in sql server

I'm trying to update a column in a table which has ~90,000 rows. Is there is any optimized way to update the table?
I have added necessary indexes.. so that no table scans/lookups are not happening. But still it takes much time to run (1hr).
My scenario:
DECLARE #ParentID NVARCHAR(100),
#Con_ERID INT
DECLARE #MaxCount INT,
#MinCount INT,
#Id INT
SELECT #MaxCount = MAX(Id) from [dbo].[ParentIDStaging] where Type='grid'
SET #MinCount = 1
WHILE #MinCount <= #MaxCount
BEGIN
SELECT #Id = ConID FROM [dbo].[ParentIDStaging] WHERE Id = #MinCount and Type = 'grid'
IF #Id IS NOT NULL
BEGIN
SELECT #Con_ERID = ErId FROM Context (NOLOCK) Where ConId = #Id
SELECT #ParentID = Identifier FROM Recording (NOLOCK) where ErId = #Con_ERID
BEGIN TRAN
UPDATE [ParentIDStaging] WITH (ROWLOCK)
SET [ParentID] = #ParentID
WHERE ContentType = 'grid'
AND ConID = #Id
COMMIT
END
SET #MinCount = #MinCount + 1
END
Looping is slow. Try doing it in one update with include the relevant other tables using joins. Your query can probably be writen like this (don't know your actual schema):
UPDATE PS
SET PS.ParentID = Recording.Identifier
FROM ParetnIDStaging PS
JOIN Context on (Context.ConId = PS.ConId)
JOIN Recording on (Recording.ErId = Context.ErId)
WHERE ...
It is because you are looping and updating one record at a time and using explicit locks/transactions.
Without knowing your underlying structure - I would bet you could do what you are trying with an update from a select.
UPDATE ParentIDStaging
SET parentIdStaging.ParentID=recording.Identifier
from ParentIDStaging
join Context on context.ConId = ParentIDStaging.ConId
join recording on contect.erid=recording.erId
WHERE parentIdStaging.ContentType = 'grid'
AND parentidStaging.Type='grid'

Resources