Updating table with random data With NEWID() does not work - sql-server

SQL SERVER 2000:
I have a table with test data (about 100000 rows), I want to update a column value from another table with some random data from another table. According to this question, This is what I am trying:
UPDATE testdata
SET type = (SELECT TOP 1 id FROM testtypes ORDER BY CHECKSUM(NEWID()))
-- or even
UPDATE testdata
SET type = (SELECT TOP 1 id FROM testtypes ORDER BY NEWID())
However, the "type" field is still with the same value for all rows; Any ideas what Am I doing wrong?
[EDIT]
I would expect this query to return one different value for each row, but it doesn't:
SELECT testdata.id, (SELECT TOP 1 id FROM testtypes ORDER BY CHECKSUM(NEWID())) type
FROM testdata
-- however seeding a rand value works
SELECT testdata.id, (SELECT TOP 1 id FROM testtypes ORDER BY CHECKSUM(NEWID()) + RAND(testdata.id)) type
FROM testdata

Your problem is: you are selecting only a single value and then updating all columns with that one single value.
In order to really get a randomization going, you need to do a step-by-step / looping approach - I tried this in SQL Server 2008, but I think it should work in SQL Server 2000 as well:
-- declare a temporary TABLE variable in memory
DECLARE #Temporary TABLE (ID INT)
-- insert all your ID values (the PK) into that temporary table
INSERT INTO #Temporary SELECT ID FROM dbo.TestData
-- check to see we have the values
SELECT COUNT(*) AS 'Before the loop' FROM #Temporary
-- pick an ID from the temporary table at random
DECLARE #WorkID INT
SELECT TOP 1 #WorkID = ID FROM #Temporary ORDER BY NEWID()
WHILE #WorkID IS NOT NULL
BEGIN
-- now update exactly one row in your base table with a new random value
UPDATE dbo.TestData
SET [type] = (SELECT TOP 1 id FROM dbo.TestTypes ORDER BY NEWID())
WHERE ID = #WorkID
-- remove that ID from the temporary table - has been updated
DELETE FROM #Temporary WHERE ID = #WorkID
-- first set #WorkID back to NULL and then pick a new ID from
-- the temporary table at random
SET #WorkID = NULL
SELECT TOP 1 #WorkID = ID FROM #Temporary ORDER BY NEWID()
END
-- check to see we have no more IDs left
SELECT COUNT(*) AS 'After the update loop' FROM #Temporary

you need to enforce a per row calculation in the selection of the new ids ..
this would do the trick
UPDATE testdata
SET type = (SELECT TOP 1 id FROM testtypes ORDER BY outerTT*CHECKSUM(NEWID()))
FROM testtypes outerTT

Related

How to fiind out the missing records (ID) from an indexed [order] table in sql

I have a table [Order] that has records with sequential ID (in odd number only, i.e. 1,3,5,7...989, 991, 993, 995, 997, 999), it is seen that a few records were accidentally deleted and should be inserted back, first thing is to find out what records are missing in the current table, there are hundreds of records in this table
Don't know how to write the query, can anyone kindly help, please?
I am thinking if I have to write a stored procedure or function but would be better if I can avoid them for environment reasons.
Below peuso code is what I am thinking:
set #MaxValue = Max(numberfield)
set #TestValue = 1
open cursor on recordset ordered by numberfield
foreach numberfield
while (numberfield != #testvalue) and (#testvalue < #MaxValue) then
Insert #testvalue into #temp table
set #testvalue = #textvalue + 2
Next
Next
UPDATE:
Expected result:
Order ID = 7 should be picked up as the only missing record.
Update 2:
If I use
WHERE
o.id IS NULL;
It returns nothing:
Since I didn't get a response from you, in the comments, I've altered the script for you to fill in accordingly:
declare #id int
declare #maxid int
set #id = 1
select #maxid = max([Your ID Column Name]) from [Your Table Name]
declare #IDseq table (id int)
while #id < #maxid --whatever you max is
begin
insert into #IDseq values(#id)
set #id = #id + 1
end
select
s.id
from #IDseq s
left join [Your Table Name] t on s.id = t.[Your ID Column Name]
where t.[Your ID Column Name] is null
Where you see [Your ID Column Name], replace everything with your column name and the same goes for [Your Table Name].
I'm sure this will give you the results you seek.
We can try joining to a number table, which contains all the odd numbers which you might expect to appear in your own table.
DECLARE #start int = 1
DECLARE #end int = 1000
WITH cte AS (
SELECT #start num
UNION ALL
SELECT num + 2 FROM cte WHERE num < #end
)
SELECT num
FROM cte t
LEFT JOIN [Order] o
ON t.num = o.numberfield
WHERE
o.numberfield IS NULL;

How to UPDATE TOP(n) with ORDER BY giving a predictable result?

I'm trying to read the top 100 items of a database table that is being used like a queue. As I do this I'm trying to mark the items as done like this:
UPDATE TOP(#qty)
QueueTable WITH (READPAST)
SET
IsDone = 1
OUTPUT
inserted.Id,
inserted.Etc
FROM
QueueTable
WHERE
IsDone = 0
ORDER BY
CreatedDate ASC;
The only problem is, according to UPDATE (Transact-SQL) on MSDN, the ORDER BY is not valid in an UPDATE and:
The rows referenced in the TOP expression used with INSERT, UPDATE, or
DELETE are not arranged in any order.
How can I achieve what I need which is to update the items at the top of the queue while also selecting them?
SQL Server allows you to update a derived table, CTE or view:
UPDATE x
SET
IsDone = 1
OUTPUT
inserted.Id,
inserted.Etc
FROM (
select TOP (N) *
FROM
QueueTable
WHERE
IsDone = 0
ORDER BY
CreatedDate ASC;
) x
No need to compute a set of IDs first. This is faster and usually has more desirable locking behavior.
Tested in SSMS, it works fine. You may need to do some modification accordingly.
--create table structure
create table #temp1 (
id int identity(1,1),
value int
)
go
--insert sample data
insert #temp1 values (1)
go 20
--below is solution
declare #qty int = 10
declare #cmd nvarchar(2000) =
N'update #temp1
set value= 100
output inserted.value
where id in
(
select top '+ cast(#qty as nvarchar(5)) +' id from #temp1
order by id
)';
execute sp_executesql #cmd
You can use ranking function (for example row_number).
update top (100) q
set IsDone = 1
output
inserted.Id,
inserted.Etc
from (
select *, row_number() over(order by CreatedDate asc, (select 0)) rn
from QueueTable) q
where rn <= 100

Updating a primary key with another column of scrambled unique values

Disclaimer: this change is not generally a useful thing to do to a properly normalized database, but I have business reasons for it.
I have a table of data with a primary key of numeric values. This key is used as a foreign key reference in multiple other tables. There is also a column of numeric values that can be updated to reflect the desired order for the rows. The order and PK columns contain the same numbers, but ordering the table by either column scrambles the other one.
What I'm trying to do is to update the primary key to follow the same order as the order column, but SSMS gives me the error "Violation of PRIMARY KEY constraint 'PK_Constraint'. Cannot insert duplicate key in object 'tbl'. The duplicate key value is <value>."
My update statement looks like this:
update tbl set tbl.key = tbl.order where tbl.key <> tbl.order
I already know how to update the foreign key references in the other tables, so I just need to know how I can update the key in this situation.
Check to make sure that there are no duplicate values in tbl.Order. If there are, you must resolve the duplicates before you can update the PK column with those values.
SELECT
order,COUNT(order) as NumDupes
FROM tbl
GROUP BY order
HAVING COUNT(order) > 1
I eventually figured out enough of the issue that I could solve this using a cursor. I'm putting my solution here for reference. If someone wants to simplify/modify this to use set-based queries, I'll accept that answer.
Step 1
Using a query from this answer, I found that there were a few order/ID "chains" that had one end that would result in a duplicate with a simple set-based update:
with parents as
(
select 1 idx, ID, Order, Name from tbl where ID <> Order
union all
select idx+1, p.ID, v.Order, p.Name from parents p inner join tbl v on p.Order = v.ID and idx < 100
)
select parents from (
select distinct parents from (
select *, parents = stuff
( ( select ', ' + cast(p.Order as varchar(100)) from parents p
where t.ID = p.ID for xml path('')
) , 1, 2, '') from parents t ) x ) y
order by len(parents) desc
Step 2
I manually looked through the result set to find the longest row that ended with a given value. I then put the values from one chain into a temp table in the order given:
create table #tmp (id int identity(1,1), val int)
insert into #tmp values <list of values>
Step 3
Next I ran through the temp table with a cursor and updated each row (and foreign key references) individually:
declare #val int
declare #old int
declare val cursor for select val from #tmp order by id desc
open val
fetch next from val into #val
while ##fetch_status = 0
begin
set #old = (select ID from tbl where Order = #val)
insert into tbl(ID, <other columns>)
select #val, <other columns> from tbl where ID = #old
update <other tables> set FK_ID = #val where FK_ID = #old
delete from tbl where ID = #old
fetch next from val into #val
end;close val; deallocate val;
Step 4
I repeated steps 2 and 3 for each "chain". At the end, my table had the primary key in the same order as the Order field.

get a value and insert the increment of that value in sql server

I Have a table in SQL SERver with name T_ACCOUNT with 2 columns ACCOUNT_ID and CUSTOMER_ID both as int.
Now i have to read the last row of that table and insert a new row with the values of last row incrementing by 1.
How can I do this?
I tried like this
SELECT ACCOUNT_ID,CUSTOMER_ID FROM T_ACCOUNT order by ACCOUNT_ID desc
INSERT T_ACCOUNT(ACCOUNT_ID,CUSTOMER_ID)
VALUES(ACCOUNT_ID = ACCOUNT_ID + 1 AND CUSTOMER_ID = CUSTOMER_ID +1)
but it was showing the syntax error.can anyone help me
The below statement picks the last row (record with max account_id) and inserts the next row.
INSERT INTO T_ACCOUNT (ACCOUNT_ID,CUSTOMER_ID)
SELECT ACCOUNT_ID + 1, CUSTOMER_ID +1
FROM T_ACCOUNT
Where ACCOUNT_ID = (select max(ACCOUNT_ID) from T_ACCOUNT)
The where clause is used in picking the last row.
You should consider creating a column in your sql table that auto increments so you can just do an insert at end of existing records.
A very strange requirement but anyway this is how you would go about doing this....
INSERT INTO T_ACCOUNT (ACCOUNT_ID,CUSTOMER_ID)
SELECT TOP 1 ACCOUNT_ID + 1, CUSTOMER_ID +1
FROM T_ACCOUNT
ORDER BY ACCOUNT_ID desc
Order by is irrelevant here, no need for that.
You should really create a stored procedure to do this operation, Create two output parameters to get the newly created values. something like as follows
CREATE PROCEDURE dbo.Add_Record
#ACCOUNT_ID INT OUTPUT,
#CUSTOMER_ID INT OUTPUT
AS
BEGIN
SET NOCOUNT ON;
DECLARE #AccountID INT, #CustomerID INT;
SELECT TOP 1 #AccountID = ACCOUNT_ID
,#CUSTOMER_ID = CUSTOMER_ID
FROM T_ACCOUNT
ORDER BY ACCOUNT_ID DESC;
SET #AccountID = #AccountID + 1;
SET #CUSTOMER_ID = #CUSTOMER_ID + 1;
INSERT INTO T_ACCOUNT (ACCOUNT_ID,CUSTOMER_ID)
VALUES (#AccountID, #CUSTOMER_ID)
END

Checking if something has changed in a trigger

I have a need to monitor a subset of fields on a table and perform a task when one of them changes.
I am using a trigger on the table update which and then am looking at the changes as follows:
-- join the deleted and inserted to get a full list of rows
select * into #tmp from (select * from inserted union select * from deleted) un
-- select a count of differing rows, > 1 means something is different
select distinct count(*) from #tmp
This is fine and a count of 2 or more means something is different on single line updates. Issue is if I am doing a multiple line update then this breaks down.
Is there a way I can get this to work for a multi line update or do I need to try a different approach completely.
You could do something like this (syntax completely untested)
IF NOT UPDATE(col)
RETURN
SELECT inserted.key, inserted.col as i_col, deleted.col as d_col
INTO #interestingrows
FROM inserted JOIN deleted on inserted.key = deleted.key
and inserted.col <> deleted.col /*If col is nullable cater for that as well*/
IF ##ROWCOUNT=0
RETURN
/*Process contents of #interestingrows*/
I ended up with a fairly simple solution. I wrote an additional loop around the check that did the check per line in inserted.
-- get a list of updated line id's
select field1 as id into #loop from inserted
-- loop through all the id's and do a compare
while (select count(*) from #loop) > 0 begin
select top 1 #id = id from #loop
select * into #tmp from (select * from inserted where field1 = #id union
select * from deleted where field1 = #id) un
-- do a select ditinct to count the differing lines.
if (select distinct count(*) from #tmp) > 1 begin
-- the 2 lines don't match, so mark for update
update test1 set flag = 1 where field1 = #id
end
drop table #tmp
delete #loop where id = #id
end

Resources