SQL Server - 与随机值的相关性?

Posted

技术标签:

【中文标题】SQL Server - 与随机值的相关性?【英文标题】:SQL Server - Correlation with random values? 【发布时间】:2019-10-21 15:11:33 【问题描述】:

我的情况的简化示例:

我有一个包含三列的表:IDCATEGORYTIMESTAMP。每个IDTIMESTAMP 都是唯一的,但CATEGORY 不是唯一的。

我进行此查询以返回表中行的伪随机列表(每个 CATEGORY 一个)。

SELECT b.*
FROM
(
  SELECT MIN(RAND(ID)*100000-FLOOR(RAND(ID)*100000)) [RandomID] -- Select random identifier for each row
  FROM MYTABLE
  GROUP BY CATEGORY
) a
INNER JOIN
MYTABLE b
ON a.RandomID = (RAND(b.ID)*100000-FLOOR(RAND(b.ID)*100000))

它似乎工作正常,但我担心两个(或更多)不同的ID 可能对应于相同的RandomID。如果发生这种情况,那么查询将返回不准确的结果,因为这些表是基于RandomIDJOINed。

这是一个有效的担忧吗?如果有,如何克服?


附:一些上下文:

在我的例子中,这个查询的结果将用于每月保留或清除一些记录和文件,因此查询结果的准确性非常重要。

需要明确的是,选择哪些行对我来说并不重要,只要保证每个CATEGORY 都有一个行,有一个条件:我想要选择的行根据TIMESTAMP“大致”均匀分布。这就是为什么我要关联来自随机值而不是来自TIMESTAMP 的数据。 (例如,通过MIN(TIMESTAMP) 关联会导致月初的行密度更高。)考虑到我每月有数千个类别,伪随机选择行通常会导致均匀分布TIMESTAMPS(即我的目标)。

另外一点:我希望查询是可重复的(即我希望它始终选择相同的伪随机值。)因此,涉及诸如newid() 之类的解决方案是不够的.


根据要求,这里是示例数据。

TIMESTAMP       | ID | CATEGORY
-------------------------------
10/21/19 1:00AM | 1  | A
10/21/19 2:00AM | 2  | B
10/21/19 3:00AM | 3  | A
10/21/19 4:00AM | 4  | B
10/21/19 5:00AM | 5  | A
10/21/19 6:00AM | 6  | B

一个可能的输出(取决于RAND() 选择的确切内容)是:

TIMESTAMP       | ID | CATEGORY
-------------------------------
10/21/19 3:00AM | 3  | A
10/21/19 6:00AM | 6  | B

选择哪些行并不特别重要,只要每个类别都有一个。同样,我不想基于TIMESTAMP 进行关联,因为这样可以保证我会选择样本数据中的前两行,但我希望TIMESTAMPS 大致均匀分布。

【问题讨论】:

我不清楚你想做什么。但是,您可能不知道对rand() 的每次调用都会在每一行上产生相同的值。样本数据和期望的结果会有所帮助。 @GordonLinoff RAND() 没有参数返回相同的结果到每一行,是的,但我传递一个唯一的 ID 作为参数,它返回不同的结果到每一行(或“几乎当然不同”——这就是问题的原因)。我还乘以一个大数,然后只保留小数部分以“进一步随机化”选定的随机数。 如果是日期时间,可以根据最小微秒来选择。这可能会也可能不会产生足够好的分布,具体取决于数据输入中的任何特质。 @avery_larry 我的数据没有毫秒(或微秒)分量。但即使是这样,我仍然会遇到同样的问题。如果您仅基于时间戳的微秒分量进行关联,则无法保证结果准确无误,因为两行(或更多)行的时间戳可能具有相同的微秒分量。 那你可以加倍吗?找到具有最小秒数和最小 ID 的单行?除非 ID 与时间戳顺序相关。使用newid() 而不是rand() 怎么样?它被认为是足够不可复制的。 【参考方案1】:

这种方法可能会导致性能问题。

declare @mytable table (timestamp datetime, ID int, category varchar(150))

insert into @mytable
values ('10/21/19 1:00AM', 1, 'A'),
    ('10/21/19 2:00AM', 2, 'B'),
    ('10/21/19 3:00AM', 3, 'A'),
    ('10/21/19 4:00AM', 4, 'B'),
    ('10/21/19 5:00AM', 5, 'A'),
    ('10/21/19 6:00AM', 6, 'A'),
    ('10/21/19 7:00AM', 7, 'A'),
    ('10/21/19 8:00AM', 8, 'A'),
    ('10/21/19 9:00AM', 9, 'A'),
    ('10/21/19 10:00AM', 10, 'A'),
    ('10/21/19 11:00AM', 11, 'A'),
    ('10/21/19 12:00AM', 12, 'A'),
    ('10/21/19 1:00PM', 13, 'A'),
    ('10/21/19 2:00PM', 14, 'A'),
    ('10/21/19 3:00PM', 15, 'A'),
    ('10/21/19 4:00PM', 16, 'A'),
    ('10/21/19 5:00PM', 17, 'A'),
    ('10/21/19 6:00PM', 18, 'A'),
    ('10/21/19 7:00PM', 19, 'A'),
    ('10/21/19 8:00PM', 20, 'A'),
    ('10/21/19 6:00PM', 21, 'B')

select timestamp, id, category
from (
   select *, row_number() over (partition by category order by newid()) rown
   from @mytable
) a
where rown=1

我认为您也可以使用随机代码。我不知道这两种方法如何比较分布。 编辑我在订单中添加了 ID。即使在随机代码发生冲突的(非常)不太可能发生的情况下,这也使得结果可重复。

...
select timestamp, id, category
from (
   select *, row_number() over (partition by category order by RAND(ID)*100000-FLOOR(RAND(ID)*100000),ID) rown
   from @mytable
) a
where rown=1

【讨论】:

其实我刚刚意识到,newid() 行不通,因为我希望这个结果是可重复的。因为newid() 每次被调用时都会得到一些新的东西,所以这是行不通的。不过,很好的答案。 +1 答案中的第二个查询是可重复的。 @ImaginaryHuman072889 我在第二个查询中将 ID 添加到 order by。这应该是可重复的,包括在 RAND 代码中发生冲突的可能性非常小。请注意,当然,表中的新数据会改变结果。 是的,当然理解,如果数据发生变化,结果也会发生变化。 RAND 代码中发生冲突的可能性很小,但仍然是一个可能的问题。 我所有的查询都消除了冲突。更新后的第二个查询以一致可重复的方式消除了冲突。【参考方案2】:

这不是您问题的答案。只是你的方法似乎没有希望。

SQL Server 中的rand() 对顺序 ID 不是特别好。考虑这段代码:

 select id, floor(RAND(ID)*100000)
 from (values (1), (2), (3), (4), (5), (6), (7), (8), (9)) v(id);

返回:

id  (No column name)
1   71359
2   71361
3   71362
4   71364
5   71366
6   71368
7   71370
8   71372
9   71374

(Here 是一个 dbfiddle。)

这些并不完全是重复的。但它们也不是大多数人的“随机”版本。我问你真正想做的是什么,因为你的问题可能有解决方案。但是,您的问题并没有清楚地解释代码的用途。

【讨论】:

请仔细看看我的问题。我没有选择FLOOR(RAND(ID)*100000)。我选择(RAND(ID)*100000)-FLOOR(RAND(ID)*100000)。就像您指出的那样,RAND 输入的微小变化实际上并不会改变输出。这就是为什么我乘以一个大数并只保留小数部分,以“进一步随机化”它。【参考方案3】:

(回答我自己的问题)

几个小时后,我想出了一个有点奇怪的解决方案,但它解决了问题中列出的问题。

解决方法是将随机生成的数字与ID串联起来,然后在聚合函数发生后,将字符串中包含随机数的部分去掉,以取回原来的ID,即

SELECT b.*
FROM
(
  SELECT
  MIN(
    RIGHT(
     CAST(
      CAST(
       RAND(ID)                   -- 1. Get pseudo-random number   (e.g. 0.01234)
      AS decimal(10,10))          -- 2. Get 10 decimal places      (e.g. 0.0123456789)
     AS varchar(20)),             -- 3. Cast it to varchar         (e.g. '0.0123456789')
    4)                            -- 4. Get only the last 4 digits (e.g. '6789')
   + '_' + CAST(ID as varchar(3)) -- 5. Append underscore and ID   (e.g. '6789_1')
  ) [RandomID]
  FROM MYTABLE
  GROUP BY CATEGORY
) a
INNER JOIN
MYTABLE b ON b.ID = 
CAST(SUBSTRING(a.RandomID,6,100) as int) -- Strip away first 5 chars to get ID back

这解决了两者的问题:

    GROUP BY CATEGORY选择伪随机行

    保证JOIN中的IDRandomID对应的原始ID相关。

【讨论】:

以上是关于SQL Server - 与随机值的相关性?的主要内容,如果未能解决你的问题,请参考以下文章

SQL Server ->> 与SQL Server服务配置相关的DMV

PowerShell分析SQL Server待创建索引的字段与已有索引之间的相关性

与 SQL Server 中的 EXEC 函数相关的问题

sql server 2008 在与 SQL Server 提示建立连接时出现与网络相关的或特定于实例的错误

PHP sqlsrv_connect 到 SQL Server:建立与 SQL Server 的连接时发生与网络相关或特定于实例的错误

在与SQL Server建立连接时出现与网络相关的或特定实例的错误