2010-03-04 57 views
9

我想通过一张表并随机清除一些数据。我正在做一些数据随机化,把真正的名字变成伪造的名字等等。所涉及的表中有一列有大约40%的时间空列。我的名字randomizer应用程序可以在它指定新的名字时在它的某个位置投掷硬币。但我宁愿最后这样做:随机删除一些数据。如何随机更新行?

我有这样的代码,这是不行的,但看起来确实像它应该对我说:

Use MyDb 
go 

CREATE VIEW vRandNumber 
AS 
SELECT RAND() as RandNumber 

go 

CREATE FUNCTION RandNumber() 
RETURNS float 
AS 
    BEGIN 
    RETURN (SELECT RandNumber FROM vRandNumber) 
    END 

go 

select dbo.RandNumber() 

update names set nickname = null 
where ((select dbo.RandNumber()) > 0.5) 

当我运行RandNumber功能也很好,很多随机的。但是当我进行更新时,它会在一半时间更新所有行,而另一半则不更新行。

我希望它在每次运行脚本时更新随机数的行。我真的认为像RandNumber这样的函数会为表中的每一行运行一次。显然不是。

这可能没有循环,没有控制台应用程序?

编辑:我也尝试了几个RAND()的变体直接在哪里得到了相同的结果。

+1

您可以使用:WHERE RAND()> 0.5'。不需要视图和功能。 – 2010-03-04 15:40:11

+0

@OMG:尝试过,结果相同。 – jcollum 2010-03-04 15:42:49

+0

10k查看,7 upvotes; smh – jcollum 2017-03-15 16:40:57

回答

22

假设你的名字表中有称为ID的主键字段,这将抵消的昵称中的行的随机50%:

update dbo.Names set Nickname = null where Id in 
(
    select top 50 percent id from dbo.Names order by NEWID() 
) 
+0

这有效,但效率低下 - 为每行生成一个新的GUID将比生成该行的单个整数花费更长的时间。 – 2010-03-04 15:55:07

+7

Pfft,来吧!超级性能真的是这种用例的一个问题吗?谈论过早的优化。 我刚刚在182,770行的桌子上试过这种方法,它在11秒内运行。 – 2010-03-04 16:04:25

+0

嗯,它很接近。但它会一直更新相同数量的行。我想我需要一个循环来更新随机数的行。 – jcollum 2010-03-04 16:08:10

1

RandNumber是一个函数。 SQL中的函数必须每次输出相同的结果才能进行相同的输入,除非底层数据库数据已更改。这是一个函数的数学定义(与正常的编程语言如何对待一个“函数”,它更像一个函数式构造)相反。

由于您的函数的结果决不会在更新语句(这是一个原子操作)期间发生变化,查询的查询计划编译器只会调用RandNumber一次,然后缓存结果。

您或许可以直接在查询中引用RAND,但如果仍然无法正常工作,则必须在存储过程中迭代执行此操作。

+0

好的,但我听说当你做一个getdate()作为select中的where部分时,getdate()将为select中的每一行运行一次。这是不正确的? – jcollum 2010-03-04 15:44:03

+3

在MS-SQL函数中可以是确定性的(如您所述)或非确定性请参阅http://msdn.microsoft.com/en-us/library/aa214775%28SQL.80%29.aspx RAND函数是非确定性的。 – 2010-03-04 15:49:01

+0

在这种情况下,几乎每个函数都会确定性地运行,因为'update'是原子的。因此,即使调用了一个通常不确定的函数,它的模式绑定特性也会被底层数据在查询执行期间无法更改的事实所抵销。我认为这**可以迭代地完成,除非你可以强制查询计划编译器将该函数视为非确定性函数,尽管它不想这样做。 – 2010-03-04 15:52:45

0

如何

update names set nickname = null 
where abs(checksum(nickname) % 2) = 0 
+0

不,我使用你的位置运行更新,并且每次运行时都会更新所有行。 – jcollum 2010-03-04 15:47:47

+0

@jcollumn,奇/偶是怎么做的? – Hogan 2010-03-04 15:48:55

+0

@jcollumn - 好的我测试了这一个,它会工作...警告空名称或“常量”名称都会做同样的事情,所以它不是完全随机的。 – Hogan 2010-03-04 15:53:57

0

尝试这样的事:

WHERE DATEPART(ms,CreateDate)>500 

其中“CREATEDATE”是一列已经在拥有它的实际日期和时间的表。里边反毫秒应该是相当随机

编辑 这里的另一种方法:

DECLARE @YourTable table (RowID int, RowValue varchar(5)) 
INSERT INTO @YourTable VALUES (1,'one') 
INSERT INTO @YourTable VALUES (2,'two') 
INSERT INTO @YourTable VALUES (3,'three') 

SELECT 
    RAND(row_number() over(order by RowID)+DATEPART(ms,GETDATE())),* 
    FROM @YourTable 

输出运行1:

     RowID  RowValue 
---------------------- ----------- -------- 
0.716200609189072  1   one 
0.71621924216033  2   two 
0.716237875131588  3   three 

(3列(S)的影响)

输出运行2:

     RowID  RowValue 
---------------------- ----------- -------- 
0.727007732518828  1   one 
0.727026365490086  2   two 
0.727044998461344  3   three 

(3 row(s) affected) 
0

RAND()(和GETDATE/CURRENT_TIMESTAMP)被评估一次每声明。你需要一些方法来解决这个问题。一种方法是(如果你有一个方便的行值整数,例如一个ID列),就是调用RAND(ID)。

+0

还只调用过一次。 – Hogan 2010-03-04 15:57:54

+0

恩,没有。 RAND(ID),其中ID在每行的基础上变化,每行调用一次。 – 2010-03-04 18:50:40

0

RAND()在查询中持续存在。

SELECT RAND() 
FROM names 

会给你一组相等的数字。

你需要做这样的事情:

WITH q AS 
     (
     SELECT *, 
       ABS(CHECKSUM(NEWID())) % 2 AS r 
     FROM names 
     ) 
UPDATE q 
SET  nickname = NULL 
WHERE r = 0 
0

这是正常的分布(不是随机的)解决方案。它根据Vehicle.ID % 10 + 1 = branch_number分配车辆到分支:

; WITH mytbl AS (
    SELECT TOP 10 *, ROW_NUMBER() OVER (ORDER BY NEWID()) num 
    FROM Branch 
    ORDER BY num 
) 

UPDATE v 
SET BranchID = mytbl.ID 
FROM Vehicle v 
    INNER JOIN mytbl ON mytbl.num = v.ID % 10 + 1 

SELECT BranchID, COUNT(*) FROM Vehicle GROUP BY BranchID