FOR UPDATE cannot be specified on a READ ONLY cursor - sql

I'm using a cursor to update a single field in a table, and I'm attempting to declare the cursor using an ORDER BY in the text of the select.
I've got the following example data:
testTable:
RecordGuid RecordID DupeParentID
---------- -------- ------------
[guid] A Y
[guid] A N
[guid] A N
[guid] A N
[guid] B Y
[guid] B N
[guid] B N
[guid] C Y
[guid] C N
[guid] C N
And script:
DECLARE #allcounter INT
SET #allcounter = 1
SELECT RecordID, count(*) as [NumberDupes]
INTO #RecordGroupCounts
FROM testTable
GROUP BY RecordID
DECLARE #temp VARCHAR(500)
DECLARE #current VARCHAR(500)
DECLARE c1 CURSOR
FOR
SELECT RecordID FROM testTable WHERE RecordID IN (SELECT RecordID FROM testTable WHERE DupeParentID = 'Y')
ORDER BY RecordID
FOR UPDATE OF RecordID
OPEN c1
FETCH NEXT FROM c1 INTO #current
FETCH NEXT FROM c1 INTO #current
WHILE ##fetch_status = 0
BEGIN
UPDATE testTable
SET RecordID = RecordID + '-' + cast(#allcounter AS VARCHAR)
WHERE CURRENT OF c1
IF (#allcounter + 1) = (SELECT [NumberDupes] FROM #RecordGroupCounts WHERE RecordID = #current)
BEGIN
FETCH NEXT FROM c1 INTO #current
SET #allcounter = 0
END
SET #allcounter = #allcounter + 1
FETCH NEXT FROM c1 INTO #current
END
CLOSE c1
DEALLOCATE c1
The desired output of all of this is:
RecordGuid RecordID DupeParentID
---------- -------- ------------
[guid] A Y
[guid] A-1 N
[guid] A-2 N
[guid] A-3 N
[guid] B Y
[guid] B-1 N
[guid] B-2 N
[guid] C Y
[guid] C-1 N
[guid] C-2 N
I'm working with SQL Server 2000 so I don't have ROW_NUMBER() available - I know the common way to do this is with loops, but I am by no means a DBA, and this currently works if I remove my ORDER BY RecordID in the cursor declaration.
With as small as my current test table is this seems to be working fine, but the reason I'm attempting to order this is that I'm fairly sure it'll break if the RecordIDs aren't in order (by RecordID ASC, DupeParentID DESC) and I intend to use this on a much larger set of records semi-regularly. Is there a way to define the order for a cursor that updates? Is the cursor ordered automatically somehow? If not, is there a simpler (or faster) way to write this for SQL Server 2000?

select recordid, max(recordguid)
from testable
where DupeParentID = 'N'
group by recordID
The above statement should return 1 row per recordID with the max(guID) for that recordID. heh, spell check keeps changing guid to guide. Now we can increment all of these with the 1.
update testtable
set recordID = recordID + '-1'
where recordguid in (select recordguid from ( select recordid, max(recordguid) recordguid
from testable
where DupeParentID = 'N'
group by recordID) a)
Get the logic here? What we're doing is taking the first duplicate ID using the max recordguid as an identifier for the 'first'...lil arbitrary, we could use min as well as long as it returns just one guID for each recordID. If you had some other logic as to which record was to be called the -1 vs the -2, you can include it here.
This will create all the recordID-1 (A-1,B-1) and leave the rest alone. Now you should be able to loop this and increment the -1 as needed...or you could just repeat run it manually incrementing the -1 yourself if this is a one time fix...your call there.
Let me know how it goes...the logic should work and will be quite quicker than going through each recordID at a time.

You can try the following. Although I do not expect performance to be to great, so I would not recomend this solution if you need to run the query on production on regular basis.
declare #counter int ,#continue tinyint
set #counter =1
set #continue = 1
while #continue = 1
Begin
update testtable
set testtable.recordid = testtable.recordid + ' - ' + CAST (#counter as nchar(6))
from
testtable
inner join
(
select MAX(cast(t1.guid as char(36))) as maxguid from testtable t1
inner join testtable t2 on t1.recordid = t2.recordid
where
t2.dupeparentid = 'y' and t1.dupeparentid = 'n'
group by t1.recordid
) t4
on testtable.guid = cast (t4.maxguid as uniqueidentifier)
if ##ROWCOUNT = 0
set #continue = 0
set #counter = #counter + 1
end

Related

SQL Server: how to update a column with a value that is in that column when another number in another column is >1

I have a table with the following data:
Part Comp level item_nbr
-------------------------------
abc ab 1 1
null cd 2 2
null ef 3 3
cde gh 1 4
null ij 2 5
null kl 3 6
null mn 4 7
I would like to update the nulls to the value in each level 1, so every level that is >1 is updated with the level one value.
Part Comp level
---------------------
abc ab 1
abc cd 2
abc ef 3
cde gh 1
cde ij 2
cde kl 3
cde mn 4
I am at a loss as to how to achieve this on a very large dataset. Any help would be greatly appreciated!
To explain another way,
part level
abc 1
2
3
Then the next row is populated with another part
efg 1
2
2
etc.
Further clarification:
I need the string"abc" to be filled down with the string "abc" while the column fields below are null. The next row has a string of efg and the following column fields below are null, again, those fields should be filled down with the value "efg" and so on.
The level field = 1 will always have a part number, but all the other levels report up to the level 1 part, so should be populated identically. And repeat.
Hope this makes sense.
Use an updatable CTE with window functions:
with toupdate as (
select t.*,
max(part) over (partition by itm_nbr_not_null) as new_part
from (select t.*,
max(case when part is not null then item_nbr end) over (order by item_nbr) as itm_nbr_not_null
from t
) t
)
update toupdate
set part = new_part
where part is null;
You can run the CTE to see what is happening.
well, from your question what I understand is, you need to update the null column's value until you get a not null value. and you want to continue it up to the last row of the table.
for that scenario, I created a stored procedure, where I read the value of every n-th cell if it is null I changing it with the prev. cell's value, when the cell was not null.
Approach:
create a temporary table/ table variable.
add an extra column, which is basically identity, which will help to rank the column.
iterate a loop until the maximum row is reached.
in each iteration, read the cell value for the i-th row
4.1 if it is not null put it in a temporary variable.
4.2 else, replace/update the i-th cell's value with the temporary variable
continue it, until you reached up to the last row of the table/table variable.
look at my following snippets:
create proc DemoPost
as
begin
declare #table table(serial_no int identity(1,1), name varchar(30), text varchar(30), level int)
insert #table
select Name, Text, Level from Demo
declare #max as int = (select max(serial_no) from #table)
--select #max
declare #i as int =0
declare #temp as varchar(30)
declare #text as varchar(30)
while #i < #max
begin
set #i = #i +1
set #temp = (select name from #table where serial_no = #i)
-- if #temp is not null, fetch its value, otherwise, update/replace it with
-- previously gotten not-null cell's value.
if #temp is not null
begin
set #text = (select name from #table where serial_no = #i)
end
else
begin
update #table
set name = #text where serial_no = #i
end
end
select name, text, level from #table
end
You can update it using temporary table according to the given scenario i thought item_nbr is unique in row Hope this will help
SELECT *
INTO #TEMP
FROM URTablehere
DECLARE #PRev VARCHAR(MAX)
WHILE ( SELECT COUNT(*)
FROM URTablehere
) > 0
BEGIN
DECLARE #ID INT
DECLARE #Part VARCHAR(MAX)
DECLARE #Num INT
SELECT TOP ( 1 )
#ID = level ,
#Part = Part ,
#Num = item_nbr
FROM #TEMP
IF ( #ID = 1 )
BEGIN
SELECT #PRev = #Part
END
IF ( #ID > 1
AND #Part IS NULL
)
BEGIN
UPDATE URTablehere
SET Part = #PRev
WHERE item_nbr = #Num
END
DELETE
FROM #TEMP WHERE item_nbr=#Num
END

Update table with new value for each row

I need to update a column (type of datetime) in the top 1000 rows my table. However the catch is with each additional row I must increment the GETDATE() by 1 second... something like DATEADD(ss,1,GETDATE())
The only way I know how to do this is something like this:
UPDATE tablename
SET columnname = CASE id
WHEN 1 THEN DATEADD(ss,1,GETDATE())
WHEN 2 THEN DATEADD(ss,2,GETDATE())
...
END
Obviously this is not plausible. Any ideas?
How about using id rather than a constant?
UPDATE tablename
SET columnname = DATEADD(second, id, GETDATE() )
WHERE id <= 1000;
If you want the first 1000 rows (by id), but the id has gaps or other problems, then you can use a CTE:
with toupdate as (
select t.*, row_number() over (order by id) as seqnum
from tablename
)
update toupdate
set columnname = dateadd(second, seqnum, getdate())
where seqnum <= 1000;
I don't know what your ID is like, and I'm assuming you have at least SQL Server 2008 or else ROW_NUMBER() won't work.
Note: I did top 2 to show you that you that the top works. You can change it to top 1000 for your actual query.
DECLARE #table TABLE (ID int, columnName DATETIME);
INSERT INTO #table(ID)
VALUES(1),(2),(3);
UPDATE #table
SET columnName = DATEADD(SECOND,B.row_num,GETDATE())
FROM #table A
INNER JOIN
(
SELECT TOP 2 *, ROW_NUMBER() OVER (ORDER BY ID) row_num
FROM #table
ORDER BY ID
) B
ON A.ID = B.ID
SELECT *
FROM #table
Results:
ID columnName
----------- -----------------------
1 2015-03-31 13:11:59.760
2 2015-03-31 13:12:00.760
3 NULL
You don't make explicit the SQL Server version you're using, so I will assume SQL Server 2005 or above. I believe the WAITFOR DELAY command would be a good option to keep adding 1 second to each rows of the datetime column.
See this example:
-- Create temp table
CREATE TABLE #Client
(
RecordID int identity(1,1),
[Name] nvarchar(100) not null,
PurchaseDate datetime null
)
-- Fill in temp table with example values
INSERT INTO #Client
VALUES ( 'Jhon', NULL)
INSERT INTO #Client
VALUES ( 'Martha', NULL)
INSERT INTO #Client
VALUES ( 'Jimmy', NULL)
-- Create local variables
DECLARE #currentRecordId int,
#currentName nvarchar(100)
-- Create cursor
DECLARE ClientsCursor CURSOR FOR
SELECT RecordID,
[Name]
FROM #Client
OPEN ClientsCursor
FETCH FROM ClientsCursor INTO #currentRecordId, #currentName
-- Check ##FETCH_STATUS to see if there are any more rows to fetch.
WHILE ##FETCH_STATUS = 0
BEGIN
UPDATE #Client
SET PurchaseDate = DATEADD(ss,1,GETDATE())
WHERE RecordID = #currentRecordId
AND [Name] = #currentName
WAITFOR DELAY '00:00:01.000'
FETCH NEXT FROM ClientsCursor INTO #currentRecordId, #currentName
END
CLOSE ClientsCursor;
DEALLOCATE ClientsCursor;
SELECT *
FROM #Client
And here's the result:
1 Jhon 2015-03-31 15:20:04.477
2 Martha 2015-03-31 15:20:05.473
3 Jimmy 2015-03-31 15:20:06.470
Hope you find this answer helpful
This should be what you need (at least a guidline)
DELIMITER $$
CREATE PROCEDURE ADDTIME()
BEGIN
DECLARE i INT Default 1 ;
simple_loop: LOOP
UPDATE tablename SET columnname = DATE_ADD(NOW(), INTERVAL i SECOND) where rownumber = i
SET i=i+1;
IF i=1001 THEN
LEAVE simple_loop;
END IF;
END LOOP simple_loop;
END $$
To call that stored procedure use
CALL ADDTIME()

Loop for each row

I have two tables with FOREIGN KEY([Table_ID])
Columns
ID Table_ID ActiveFlag
1 1 0
2 2 1
3 1 1
4 3 0
Sys_Tables
Table_ID Name
1 Request
2 Plan
3 Contecst
I'm writing a stored procedure that returns any column for each table.
Example Output for values ​​above
--first output table
ID Table_ID ActiveFlag
1 1 0
3 1 1
--second output table
ID Table_ID ActiveFlag
2 2 1
--third output table
ID Table_ID ActiveFlag
4 3 0
My idea is this
Select c.*
from Ccolumns c
inner join Sys_tables t
on t.Table_ID = c.Table_ID and t.Table_ID = #Parameter
My problem, i do't know how to make a loop for each row. I need the best way. Example i can use following loop:
DECLARE #i int = 0
DECLARE #count int;
select #count = count(t.Table_ID)
from Sys_tables t
WHILE #i < #count BEGIN
SET #i = #i + 1
--DO ABOVE SELECT
END
But this is not entirely correct. Example my Sys_tables such data may be
Table_ID Name
1 Request
102 Plan
1001 Contecst
Do You have any idea?
There are couple ways you can achieve that: loops and cursors, but first of all you need to know that it's a bad idea: either are very slow, anyway, here's some kind of loop sample:
declare #row_ids table (
id INT IDENTITY (1, 1),
rid INT
);
insert into #row_ids (rid) select someIdField from SomeTable
declare #cnt INT = ##ROWCOUNT
declare #currentRow INT = 1
WHILE (#currentRow <= #cnt)
BEGIN
SELECT rid FROM #row_ids WHERE id = #currentRow
SET #currentRow = #currentRow + 1
END
I guess you're using SQL Server, right?
Then, you can use a CURSOR as here: How to write a cursor inside a stored procedure in SQL Server 2008

Efficient SQL Server stored procedure

I am using SQL Server 2008 and running the following stored procedure that needs to "clean" a 70 mill table from about 50 mill rows to another table, the id_col is integer (primary identity key)
According to the last running I made it is working good but it is expected to last for about 200 days:
SET NOCOUNT ON
-- define the last ID handled
DECLARE #LastID integer
SET #LastID = 0
declare #tempDate datetime
set #tempDate = dateadd(dd,-20,getdate())
-- define the ID to be handled now
DECLARE #IDToHandle integer
DECLARE #iCounter integer
DECLARE #watch1 nvarchar(50)
DECLARE #watch2 nvarchar(50)
set #iCounter = 0
-- select the next to handle
SELECT TOP 1 #IDToHandle = id_col
FROM MAIN_TABLE
WHERE id_col> #LastID and DATEDIFF(DD,someDateCol,otherDateCol) < 1
and datediff(dd,someDateCol,#tempDate) > 0 and (some_other_int_col = 1745 or some_other_int_col = 1548 or some_other_int_col = 4785)
ORDER BY id_col
-- as long as we have s......
WHILE #IDToHandle IS NOT NULL
BEGIN
IF ((select count(1) from SOME_OTHER_TABLE_THAT_CONTAINS_20k_ROWS where some_int_col = #IDToHandle) = 0 and (select count(1) from A_70k_rows_table where some_int_col =#IDToHandle )=0)
BEGIN
INSERT INTO SECONDERY_TABLE
SELECT col1,col2,col3.....
FROM MAIN_TABLE WHERE id_col = #IDToHandle
EXEC [dbo].[DeleteByID] #ID = #IDToHandle --deletes the row from 2 other tables that is related to the MAIN_TABLE and than from the MAIN_TABLE
set #iCounter = #iCounter +1
END
IF (#iCounter % 1000 = 0)
begin
set #watch1 = 'iCounter - ' + CAST(#iCounter AS VARCHAR)
set #watch2 = 'IDToHandle - '+ CAST(#IDToHandle AS VARCHAR)
raiserror ( #watch1, 10,1) with nowait
raiserror (#watch2, 10,1) with nowait
end
-- set the last handled to the one we just handled
SET #LastID = #IDToHandle
SET #IDToHandle = NULL
-- select the next to handle
SELECT TOP 1 #IDToHandle = id_col
FROM MAIN_TABLE
WHERE id_col> #LastID and DATEDIFF(DD,someDateCol,otherDateCol) < 1
and datediff(dd,someDateCol,#tempDate) > 0 and (some_other_int_col = 1745 or some_other_int_col = 1548 or some_other_int_col = 4785)
ORDER BY id_col
END
Any ideas or directions to improve this procedure run-time will be welcomed
Yes, try this:
Declare #Ids Table (id int Primary Key not Null)
Insert #Ids(id)
Select id_col
From MAIN_TABLE m
Where someDateCol >= otherDateCol
And someDateCol < #tempDate -- If there are times in these datetime fields,
-- then you may need to modify this condition.
And some_other_int_col In (1745, 1548, 4785)
And Not exists (Select * from SOME_OTHER_TABLE_THAT_CONTAINS_20k_ROWS
Where some_int_col = m.id_col)
And Not Exists (Select * From A_70k_rows_table
Where some_int_col = m.id_col)
Select id from #Ids -- this to confirm above code generates the correct list of Ids
return -- this line to stop (Not do insert/deletes) until you have verified #Ids is correct
-- Once you have verified that above #Ids is correctly populated,
-- then delete or comment out the select and return lines above so insert runs.
Begin Transaction
Delete OT -- eliminate row-by-row call to second stored proc
From OtherTable ot
Join MAIN_TABLE m On m.id_col = ot.FKCol
Join #Ids i On i.Id = m.id_col
Insert SECONDERY_TABLE(col1, col2, etc.)
Select col1,col2,col3.....
FROM MAIN_TABLE m Join #Ids i On i.Id = m.id_col
Delete m -- eliminate row-by-row call to second stored proc
FROM MAIN_TABLE m
Join #Ids i On i.Id = m.id_col
Commit Transaction
Explaanation.
You had numerous filtering conditions that were not SARGable, i.e., they would force a complete table scan for every iteration of your loop, instead of being able to use any existing index. Always try to avoid filter conditions that apply processing logic to a table column value before comparing it to some other value. This eliminates the opportunity for the query optimizer to use an index.
You were executing the inserts one at a time... Way better to generate a list of PK Ids that need to be processed (all at once) and then do all the inserts at once, in one statement.

Sequential numbers randomly selected and added to table

The SO Question has lead me to the following question.
If a table has 16 rows I'd like to add a field to the table with the numbers 1,2,3,4,5,...,16 arranged randomly i.e in the 'RndVal' field for row 1 this could be 2, then for row 2 it could be 5 i.e each of the 16 integers needs to appear once without repetition.
Why doesn't the following work? Ideally I'd like to see this working then to see alternative solutions.
This creates the table ok:
IF OBJECT_ID('tempdb..#A') IS NOT NULL BEGIN DROP TABLE #A END
IF OBJECT_ID('tempdb..#B') IS NOT NULL BEGIN DROP TABLE #B END
IF OBJECT_ID('tempdb..#C') IS NOT NULL BEGIN DROP TABLE #C END
IF OBJECT_ID('tempdb..#myTable') IS NOT NULL BEGIN DROP TABLE #myTable END
CREATE TABLE #B (B_ID INT)
CREATE TABLE #C (C_ID INT)
INSERT INTO #B(B_ID) VALUES
(10),
(20),
(30),
(40)
INSERT INTO #C(C_ID)VALUES
(1),
(2),
(3),
(4)
CREATE TABLE #A
(
B_ID INT
, C_ID INT
, RndVal INT
)
INSERT INTO #A(B_ID, C_ID, RndVal)
SELECT
#B.B_ID
, #C.C_ID
, 0
FROM #B CROSS JOIN #C;
Then I'm attempting to add the random column using the following. The logic is to add random numbers between 1 and 16 > then to effectively overwrite any that are duplicated with other numbers > in a loop ...
SELECT
ROW_NUMBER() OVER(ORDER BY B_ID) AS Row
, B_ID
, C_ID
, RndVal
INTO #myTable
FROM #A
DECLARE #rowsRequired INT = (SELECT COUNT(*) CNT FROM #myTable)
DECLARE #i INT = (SELECT #rowsRequired - SUM(CASE WHEN RndVal > 0 THEN 1 ELSE 0 END) FROM #myTable)--0
DECLARE #end INT = 1
WHILE #end > 0
BEGIN
SELECT #i = #rowsRequired - SUM(CASE WHEN RndVal > 0 THEN 1 ELSE 0 END) FROM #myTable
WHILE #i>0
BEGIN
UPDATE x
SET x.RndVal = FLOOR(RAND()*#rowsRequired)
FROM #myTable x
WHERE x.RndVal = 0
SET #i = #i-1
END
--this is to remove possible duplicates
UPDATE c
SET c.RndVal = 0
FROM
#myTable c
INNER JOIN
(
SELECT RndVal
FROM #myTable
GROUP BY RndVal
HAVING COUNT(RndVal)>1
) t
ON
c.RndVal = t.RndVal
SET #end = ##ROWCOUNT
END
TRUNCATE TABLE #A
INSERT INTO #A
SELECT
B_ID
, C_ID
, RndVal
FROM #myTable
If the original table has 6 rows then the result should end up something like this
B_ID|C_ID|RndVal
----------------
| | 5
| | 4
| | 1
| | 6
| | 3
| | 2
I don't understand your code, frankly
This will update each row with a random number, non-repeated number between 1 and the number of rows in the table
UPDATE T
SET SomeCol = T2.X
FROM
MyTable T
JOIN
(
SELECT
KeyCol, ROW_NUMBER() OVER (ORDER BY NEWID()) AS X
FROM
MyTable
) T2 ON T.KeyCol = T2.KeyCol
This is more concise but can't test to see if it works as expected
UPDATE T
SET SomeCol = X
FROM
(
SELECT
SomeCol, ROW_NUMBER() OVER (ORDER BY NEWID()) AS X
FROM
MyTable
) T
When you add TOP (1) (because you need to update first RndVal=0 record) and +1 (because otherwise your zero mark means nothing) to your update, things will start to move. But extremely slowly (around 40 seconds on my rather outdated laptop). This is because, as #myTable gets filled with generated random numbers, it becomes less and less probable to get missing numbers - you usually get duplicate, and have to start again.
UPDATE top (1) x
SET x.RndVal = FLOOR(RAND()*#rowsRequired) + 1
FROM #myTable x
WHERE x.RndVal = 0
Of course, #gbn has perfectly valid solution.
This is basically the same as the previous answer, but specific to your code:
;WITH CTE As
(
SELECT B_ID, C_ID, RndVal,
ROW_NUMBER() OVER(ORDER BY NewID()) As NewOrder
FROM #A
)
UPDATE CTE
SET RndVal = NewOrder
SELECT * FROM #A ORDER BY RndVal