In my sql code I have declared a table(A) and inserting some rows to that table from a database table(B). Then I have to take those inserted rows from A and put it into a CURSOR and after I do a FETCH NEXT still the ##FETCH_STATUS is -1. But the expected value for the ##FETCH_STATUS is 0.
I am putting a simplified code below the question.
Can I know what is the wrong with this code. Can I use declared tables to populate the CURSORs in SQL or cursor has to be populated from a created table in the database.
// This is a code that goes inside a Stored Procedure.
AS
DECLARE A TABLE (.........)// A table has same fields in table B
DECLARE s INT
WHILE EXISTS ( SELECT * FROM B WHERE ......)
BEGIN
BEGIN TRAN
INSERT INTO A SELECT TOP 10 (....)FROM B WITH (UPDLOCK, HOLDLOCK) WHERE ....
SELECT s = count(*) from A // this returns some value which means inserting is working
DECLARE dataSet CURSOR FOR (SELECT..... FROM A)
OPEN dataSet
FETCH NEXT FROM dataSet INTO ...
WHILE ##FETCH_STATUS = 0 // coming value for this is -1
BEGIN
//Code goes here
FETCH NEXT FROM dataSet INTO ...
END
CLOSE dataSet
DEALLOCATE dataSet
DELETE FROM A
COMMIT TRAN
END
Here's a runnable version of the OPs question - but it doesn't exhibit the issue. This isn't an answer, so CW, and I'll delete if/when the OP does post an actual example:
create table B (ID int not null,Val1 varchar(10) not null)
go
insert into B(ID,Val1) values (1,'abc'),(2,'ade')
go
create procedure DoStuff
AS
DECLARE #A TABLE (ID int not null,Val1 varchar(10) not null)
DECLARE #s INT
WHILE EXISTS ( SELECT * FROM B WHERE Val1 like 'a%')
BEGIN
BEGIN TRAN
INSERT INTO #A SELECT TOP 10 ID,Val1 FROM B WITH (UPDLOCK, HOLDLOCK) WHERE Val1 like 'a%'
SELECT #s = count(*) from #A
DECLARE dataSet CURSOR FOR (SELECT ID,Val1 FROM #A)
declare #ID int
declare #Val1 varchar(10)
OPEN dataSet
FETCH NEXT FROM dataSet INTO #ID,#Val1
WHILE ##FETCH_STATUS = 0
BEGIN
RAISERROR('%i: %s',10,1,#ID,#Val1) WITH NOWAIT
UPDATE B set Val1 = 'done' where ID = #ID
FETCH NEXT FROM dataSet INTO #ID,#Val1
END
CLOSE dataSet
DEALLOCATE dataSet
DELETE FROM #A
COMMIT TRAN
END
GO
EXEC DoStuff
GO
SELECT * from B
Output:
(2 row(s) affected)
(2 row(s) affected)
1: abc
(1 row(s) affected)
2: ade
(1 row(s) affected)
(2 row(s) affected)
(2 row(s) affected)
and table B:
ID Val1
1 done
2 done
Since its uncommited transaction on table A, table A might have been locked. So for your dataset cursor try-
select '' from A with (nolock) where ...
i think create temp table then declare it for cursor, not must be have an existing table
like:
DECLARE cursor_name CURSOR FOR SELECT id INTO temp_table FROM user_id
Related
I am using Microsoft SQL Server, and attempting to insert some data into a temporary table. I then want to use a while loop to loop through each row in the temporary table. I do no want to use a cursor.
Please see the query below:
-- Create Table
DROP TABLE IF EXISTS #TMP_ABC
CREATE TABLE #TMP_ABC
(
[ABC] [varchar](3) NULL,
)
-- Insert Values
INSERT INTO [#TMP_ABC] VALUES ('AAA')
INSERT INTO [#TMP_ABC] VALUES ('BBB')
INSERT INTO [#TMP_ABC] VALUES ('CCC')
INSERT INTO [#TMP_ABC] VALUES ('DDD')
INSERT INTO [#TMP_ABC] VALUES ('EEE')
INSERT INTO [#TMP_ABC] VALUES ('FFF')
-- Display values
DECLARE #count INT
DECLARE #row INT
SET #row = 1;
DECLARE #ABC varchar(3)
SET #count = (SELECT COUNT(ABC) FROM #TMP_ABC)
WHILE (#row <= #count) BEGIN
SELECT #ABC = ABC FROM #TMP_ABC
PRINT #ABC
SET #row += 1
END
Here is what is returned from the query:
(1 row affected)
FFF
FFF
FFF
FFF
FFF
FFF
I was hoping for the following to be returned instead:
(1 row affected)
AAA
BBB
CCC
DDD
EEE
FFF
Please can somebody 'kindly' show me the error in my ways, and how to achieve this?
The issue happens because SQL Server doesn't associate #row with a row in the table (the correlation is obvious to you, but SQL Server isn't human).
As you loop through the numbers 1 -> #count, it is running the same SELECT #ABC = ABC FROM #TMP_ABC over and over again. There is no WHERE clause and no TOP so SQL Server is just reading the whole table every time, and setting the variable equal to the last ABC value it read.
Instead, you should use a cursor (if you need to loop at all; usually you don't, per #Larnu's comment). You have have read some misinformation somewhere that cursors are bad and that while loops are not cursors, but these are both false.
Bad Habits to Kick : Thinking a WHILE loop isn't a CURSOR
What impact can different cursor options have?
Follow-up on cursor options
Overlooked T-SQL Gems (see why using a local variable for a cursor is even better than the regular type you probably use)
If you do in fact need to loop for some reason, here's a rewrite:
CREATE TABLE #TMP_ABC(ABC varchar(3));
INSERT INTO #TMP_ABC(ABC) VALUES
('AAA'),('BBB'),('CCC'),('DDD'),('EEE'),('FFF');
DECLARE #ABC varchar(3), #c cursor;
SET #c = cursor LOCAL FAST_FORWARD
FOR SELECT ABC FROM #TMP_ABC;
OPEN #c;
FETCH NEXT FROM #c INTO #ABC;
WHILE ##FETCH_STATUS = 0
BEGIN
PRINT #ABC;
FETCH NEXT FROM #c INTO #ABC;
END
Output:
AAA
BBB
CCC
DDD
EEE
FFF
Example db<>fiddle
But I'm not sure what that accomplishes over SELECT ABC FROM #TMP_ABC;.
use ROW_NUMBER() function and pass the value through a variable in WHERE clause in the loop.
--Create another Temp table
CREATE table #Tem_abc (r int , abc varchar(3));
--Add Row number
insert into #Tem_abc
select ROW_NUMBER() over( order by ABC) as r,*
from #TMP_ABC;
--Use the loop
DECLARE #count INT;
DECLARE #row INT;
SET #row = 1;
DECLARE #ABC varchar(3);
SET #count = (SELECT COUNT(ABC) FROM #TMP_ABC);
--SET #count = (SELECT max(r) FROM #Tem_abc);
WHILE (#row <= #count) BEGIN
SELECT #ABC = ABC FROM #Tem_abc where #row = r;
PRINT #ABC;
SET #row += 1;
END
My problem:
table dbo.student has StudentID like SV001.
How can I create a trigger to check data inserted into dbo.student has a StudentID that begins with SV and the numbers in the range 000 to 100?
Example: SV099 is valid id to insert, while SV101 is not valid
Use SQL constraints:
CHECK (CAST(SUBSTRING(StudentID, 3, LEN(StudentID)) AS int) <= 100)
Example :
CREATE TABLE tb
(
StudentID varchar(10)
CHECK (CAST(SUBSTRING(StudentID, 3, LEN(StudentID)) AS int) <= 100)
);
// test data
INSERT INTO tb VALUES ('sv000'); //valid
INSERT INTO tb VALUES ('sv100'); //valid
INSERT INTO tb VALUES ('sv101'); //invalid
Demo in db<>fiddle
Or if you want to use a trigger:
Note: you must use the inserted keyword to access the record that has just been added
CREATE TRIGGER TriggerStudentID
ON tb
AFTER INSERT
AS
BEGIN
DECLARE #StudentID varchar(10);
SET #StudentID = (SELECT TOP 1 StudentID FROM inserted);
IF (CAST(SUBSTRING(#StudentID, 3, LEN(#StudentID)) AS int) > 100)
ROLLBACK TRANSACTION
END
Demo in db<>fiddle.
Or you can use the following trigger
Create Trigger TriggerStudentID
On tb
AFTER INSERT
As
Begin
Declare #StudentID varchar(10);
DECLARE my_Cursor CURSOR FOR SELECT StudentID FROM INSERTED;
OPEN my_Cursor;
FETCH NEXT FROM my_Cursor INTO #StudentID;
WHILE ##FETCH_STATUS = 0
BEGIN
FETCH NEXT FROM my_Cursor INTO #StudentID;
If (CAST(SUBSTRING(#StudentID,3,LEN(#StudentID)) AS int) > 100)
RollBack Transaction
END
CLOSE my_Cursor;
DEALLOCATE my_Cursor;
END
Demo in db<>fiddle.
I'm trying to generate dummy data from the existing data I have in the tables. All I want is to increase the number of records in Table1 to N specified amount. The other tables should increase based on the foreign key references.
The tables has one to many relationship. For one record in table 1, I can have multiple entries in table 2, and in table 3 I can have many records based on IDs of the second table.
Since IDs are primary keys, I either capture it by
SET #NEWLY_INSERTED_ID = SCOPE_IDENTITY()
after inserting to table 1 and using in insert for table2, or inserting them to temp table and joining them to achieve the same results for table 3.
Here's the approach I'm taking with the CURSOR.
DECLARE #MyId as INT;
DECLARE #myCursor as CURSOR;
DECLARE #DESIRED_ROW_COUNT INT = 70000
DECLARE #ROWS_INSERTED INT = 0
DECLARE #CURRENT_ROW_COUNT INT = 0
DECLARE #NEWLY_INSERTED_ID INT
DECLARE #LANGUAGE_PAIR_IDS TABLE ( LangugePairId INT, NewId INT, SourceLanguage varchar(100), TargetLangauge varchar(100) )
WHILE (#ROWS_INSERTED < #DESIRED_ROW_COUNT)
BEGIN
SET #myCursor = CURSOR FOR
SELECT Id FROM MyTable
SET #CURRENT_ROW_COUNT = (SELECT COUNT(ID) FROM MyTable)
OPEN #myCursor;
FETCH NEXT FROM #myCursor INTO #MyId;
WHILE ##FETCH_STATUS = 0
BEGIN
IF ((#CURRENT_SUBMISSION_COUNT < #DESIRED_ROW_COUNT) AND (#ROWS_INSERTED < #DESIRED_ROW_COUNT))
BEGIN
INSERT INTO [dbo].[MyTable]
([Column1]
([Column2]
([Column3]
)
SELECT
,convert(numeric(9,0),rand() * 899999999) + 100000000
,COlumn2
,Colum3
FROM MyTable
WHERE Id = #MyId
SET #NEWLY_INSERTED_ID = SCOPE_IDENTITY()
INSERT INTO [dbo].[Language]
([MyTable1Id]
,[Target]
,[Source]
OUTPUT inserted.Id, inserted.MyTable1Id, inserted.Source, inserted.[Target] INTO #LANGUAGE_PAIR_IDS (LangugePairId, NewId, SourceLanguage, TargetLangauge)
SELECT
#NEWLY_INSERTED_ID
,[Target]
,[Source]
FROM [dbo].[Language]
WHERE MyTableId = #MyId
ORDER BY Id
DECLARE #tbl AS TABLE (newLanguageId INT, oldLanguageId INT, sourceLanguage VARCHAR(100), targetLanguage VARCHAR(100))
INSERT INTO #tbl (newLanguageId, oldLanguageId, sourceLanguage, targetLanguage)
SELECT 0, id, [Source], [Target] MyTable1Id FROM Language WHERE MyTable1Id = #MyId ORDER BY Id
UPDATE t
SET t.newlanguageid = lp.LangugePairId
FROM #tbl t
JOIN #LANGUAGE_PAIR_IDS lp
ON t.sourceLanguage = lp.SourceLanguage
AND t.targetLanguage = lp.TargetLangauge
INSERT INTO [dbo].[Manager]
([LanguagePairId]
,[UserId]
,[MyDate])
SELECT
tbl.newLanguageId
,p.[UserId]
,p.[MyDate]
FROM Manager m
INNER JOIN #tbl tbl
ON m.LanguagePairId = tbl.oldLanguageId
WHERE m.LanguagePairId in (SELECT Id FROM Language WHERE MyTable1Id = #MyId) -- returns the old language pair id
SET #ROWS_INSERTED += 1
SET #CURRENT_ROW_COUNT +=1
END
ELSE
BEGIN
PRINT 'REACHED EXIT'
SET #ROWS_INSERTED = #DESIRED_ROW_COUNT
BREAK
END
FETCH NEXT FROM #myCursor INTO #MyId;
END
CLOSE #myCursor
DEALLOCATE #myCursor
END
The above code works! It generates the data I need. However, it's very very slow. Just to give some comparison. Initial load of data for table 1 was ~60,000 records, Table2: ~74,000 and Tabl3 ~3,400
I tried to insert 9,000 rows in Table1. With the above code, it took 17:05:01 seconds to complete.
Any suggestion on how I can optimize the query to run little faster? My goal is to insert 1-2 mln records in Table1 without having to wait for days. I'm not tied to CURSOR. I'm ok to achieve the same result in any other way possible.
I tried creating a store procedure for MS SQL
CREATE PROCEDURE YEARENDPROCESSING
AS
DECLARE #VAR AS VARCHAR(9)
DECLARE ACURSOR CURSOR FOR
SELECT COLUMN FROM TABLEA
OPEN ACURSOR
FETCH NEXT FROM ACURSOR INTO #VAR
while ##FETCH_STATUS = 0
BEGIN
INSERT INTO TABLEB SELECT * FROM TABLEC WHERE TABLEC.COLUMNC = #VAR
FETCH NEXT FROM ACURSOR INTO #VAR
END
CLOSE ACURSOR
DEALLOCATE ACURSOR
Assuming TableC column and TableB have identical columns
I get the result saying X rows affected
But in actual table I see nothing.
I have a new Finding
CREATE PROCEDURE YEARENDPROCESSING
AS
DECLARE #VAR AS VARCHAR(9)
DECLARE ACURSOR CURSOR FOR
SELECT COLUMN FROM TABLEA
OPEN ACURSOR
FETCH NEXT FROM ACURSOR INTO #VAR
while ##FETCH_STATUS = 0
BEGIN
INSERT INTO TABLEB SELECT * FROM TABLEC WHERE TABLEC.COLUMNC = #VAR
DELETE TABLEF where columnf = #VAR
FETCH NEXT FROM ACURSOR INTO #VAR
END
CLOSE ACURSOR
DEALLOCATE ACURSOR
Will get x row affected for the insert and x row affected for the delete
But the actual fact is insert result cannot be seen in the physical table itself. I can see the result of I remove the Delete query. Anyone knows why?
Your code shouldn't have a cursor. You can do this:
CREATE PROCEDURE dbo.YearEndProcessing
AS
BEGIN
SET NOCOUNT ON;
INSERT dbo.TableB(column1, column2, ...)
SELECT column1, column2, ...
FROM dbo.TableA;
END
GO
Well, you changed the code, now there's a third table involved, so let me try again without a cursor:
CREATE PROCEDURE dbo.YearEndProcessing
AS
BEGIN
SET NOCOUNT ON;
INSERT dbo.TableB(column1, column2, ...)
SELECT c.column1, c.column2, ...
FROM dbo.TableA AS a
INNER JOIN dbo.TableC AS c
ON a.[Column] = c.[ColumnC];
END
GO
Or:
CREATE PROCEDURE dbo.YearEndProcessing
AS
BEGIN
SET NOCOUNT ON;
INSERT dbo.TableB(column1, column2, ...)
SELECT c.column1, c.column2, ...
FROM dbo.TableC AS c
WHERE EXISTS
(
SELECT 1 FROM dbo.TableA AS a
WHERE a.[Column] = c.[ColumnC]
);
END
GO
You shouldn't use the INSERT TABLE SELECT * FROM TABLE pattern - this is very prone to breaking in the future (see this post for more details). Also see this post about using a schema prefix and these posts about cursor usage - it should be reserved for cases that actually need it, and when you do actually need it, don't use the default options:
https://sqlblog.org/2012/01/26/bad-habits-to-kick-thinking-a-while-loop-isnt-a-cursor
http://www.sqlperformance.com/2012/07/t-sql-queries/running-totals
http://www.sqlperformance.com/2012/09/t-sql-queries/cursor-options
As for why you see rows inserted but then later can't see the data, I suspect you're looking at a table in the wrong database, in the wrong schema, or you have multiple copies of your database (which happens when you use User Instance and AttachDbFileName options in your connection string).
I need to create a temporary table and then update the original table. Creating the temporary table is not a problem.
create table #mod_contact
(
id INT IDENTITY NOT NULL PRIMARY KEY,
SiteID INT,
Contact1 varchar(25)
)
INSERT INTO #mod_contact (SiteID, Contact1)
select r.id, r.Contact from dbo.table1 r where CID = 142
GO
Now I need to loop through the table and update r.contact = SiteID + r.contact
I have never used a while loop before and can't seem to make any examples I have seen work.
You can do this in multiple ways, but I think you're looking for a way using a cursor.
A cursor is sort of a pointer in a table, which when incremented points to the next record. ( it's more or less analogeous to a for-next loop )
to use a cursor you can do the following:
-- DECLARE the cursor
DECLARE CUR CURSOR FAST_FORWARD READ_ONLY FOR SELECT id, siteId, contract FROM #mod_contract
-- DECLARE some variables to store the values in
DECLARE #varId int
DECLARE #varSiteId int
DECLARE #varContract varchar(25)
-- Use the cursor
OPEN CUR
FETCH NEXT FROM CUR INTO #varId, #varSiteId, #varContract
WHILE ##FETCH_STATUS = 0
BEGIN
UPDATE dbo.table1
SET contract = #varSiteId + #varContract -- It might not work due to the different types
WHERE id = #varId
FETCH NEXT FROM CUR INTO #varId, #varSiteId, #varContract
END
CLOSE CUR
DEALLOCATE CUR
It's not the most efficient way to get this done, but I think this is what you where looking for.
Hope it helps.
Use a set based approach - no need to loop (from the little details):
UPDATE
r
SET
r.Contact = m.SiteID + r.Contact
FROM
table1 r
INNER JOIN
#mod_contact m
ON m.id=r.id
Your brain wants to do this:
while records
update(i); //update record i
records = records + 1
end while
SQL is set based and allows you to take a whole bunch of records and update them in a single command. The beauty of this is you can use the WHERE clause to filter certain rows that are not needed.
As others have mentioned, learning how to do loops in SQL is generally a bad idea; however, since you're trying to understand how to do something, here's an example:
DECLARE #id int
SELECT #ID =1
WHILE #ID <= (SELECT MAX(ID) FROM table_1)
-- while some condition is true, then do the following
--actions between the BEGIN and END
BEGIN
UPDATE table_1
SET contact = CAST(siteID as varchar(100)) + contact
WHERE table_1.CID = #ID
--increment the step variable so that the condition will eventually be false
SET #ID = #ID + 1
END
--do something else once the condition is satisfied
PRINT 'DONE!! Don't try this in production code...'
Try this one:
-- DECLARE the cursor
DECLARE CUR CURSOR FAST_FORWARD READ_ONLY FOR SELECT column1,column2 FROM table
-- DECLARE some variables to store the values in
DECLARE #varId int
DECLARE #varSiteId int
--DECLARE #varContract varchar(25)
-- Use the cursor
OPEN CUR
FETCH NEXT FROM CUR INTO #varId, #varSiteId
WHILE ##FETCH_STATUS = 0
BEGIN
SELECT *
FROM Table2
WHERE column1 = #varId
AND column2 = #varSiteId
FETCH NEXT FROM CUR INTO #varId, #varSiteId
END
CLOSE CUR
DEALLOCATE CUR
need to create a temporary table and then up date the original table.
Why use a temporary table at all? Your CID column doesn't appear in the temporary table, so I don't see how you can successfully update the original table using SiteID, unless there is only one row where CID = 142 in which using a temp table is definitely overkill.
You can just do this:
UPDATE dbo.table1
SET contact = SiteID + contact
WHERE CID = 142;
Here's a related example which may help getting you to 'think in SQL':
UPDATE T
SET A = B, B = A;
Assuming A and B are of the same type, this would successfully swap their values.