有没有更好的方法从Oracle表中检索随机行?

时间:2019-06-14 10:52:42

标签: sql oracle random

不久前,我需要从Oracle数据库中的表中获取随机行。我发现的最广泛的解决方案是:

SELECT * FROM
( SELECT * FROM tabela WHERE warunek
ORDER BY dbms_random.value )
WHERE rownum = 1​ 

但是,这对于大型表来说是非常高的性能,因为它首先以随机顺序对表进行排序,然后获取第一行。

今天,我的一位同事提出了另一种建议:

    SELECT * FROM (
    SELECT * FROM MAIN_PRODUCT
    WHERE ROWNUM <= CAST((SELECT COUNT(*) FROM MAIN_PRODUCT)*dbms_random.value AS INTEGER)
    ORDER BY ROWNUM DESC

) WHERE ROWNUM = 1; 

它的运行速度更快,并且似乎返回随机值,但是真的吗?有人可以深入了解它是否真的是随机的,是否按预期方式运行?我真的很好奇为什么在看的时候没有在其他任何地方找到这种方法,如果它确实是随机的并且在性能上有更好的表现,为什么它不更广泛?

3 个答案:

答案 0 :(得分:2)

(可能)这是获得结果的最简单查询。
但是COUNT()会进行表扫描,我怀疑您会得到一个不这样做的查询。

Ps。此查询假定未删除的记录。

查询

SELECT * 
FROM
 MAIN_PRODUCT 
WHERE
 ROWNUM = FLOOR(
   (dbms_random.value * (SELECT COUNT(*) FROM MAIN_PRODUCT)) + 1
 )

FLOOR( (dbms_random.value * (SELECT COUNT(*) FROM MAIN_PRODUCT)) + 1 )

将生成一个介于1到表的最大数量之间的数字,请参见demo。刷新时如何工作。

Oracle12c +查询

SELECT * 
FROM
 MAIN_PRODUCT 
WHERE
ROWNUM <= FLOOR(
   (dbms_random.value * (SELECT COUNT(*) FROM MAIN_PRODUCT)) + 1
)
ORDER BY 
 ROWNUM DESC
FETCH FIRST ROW ONLY

答案 1 :(得分:1)

提高性能的关键是减轻ORDER BY的负担。

如果知道符合条件的行数,则可以在排序之前进行过滤。例如,以下内容占据了大约1%的行:

SELECT *
FROM (SELECT *
      FROM tabela
      WHERE warunek AND dbms_random.value < 0.01
      ORDER BY dbms_random.value
     )
WHERE rownum = 1​ ;

一种变化是计算匹配值的数量。然后随机选择一个较小的样本。以下获取约100个匹配的行,然后对它们进行排序以进行随机选择:

SELECT a.*
FROM (SELECT *
      FROM (SELECT a.*, COUNT(*) OVER () as cnt
            FROM tabela a
            WHERE warunek
           ) a
      WHERE dbms_random.value < 100 / cnt
      ORDER BY dbms_random.value
     ) a
WHERE rownum = 1​ ;

答案 2 :(得分:1)

您拥有的第二个代码

    SELECT * FROM (
    SELECT * FROM MAIN_PRODUCT
    WHERE ROWNUM <= CAST((SELECT COUNT(*) FROM MAIN_PRODUCT)*dbms_random.value AS INTEGER)
    ORDER BY ROWNUM DESC

) WHERE ROWNUM = 1; 

非常好,除了它将获得后续元素。 dbms_random.value返回一个0到1之间的实数。将其乘以行数将为您提供一个真正的随机数,这里的瓶颈是对行数进行计数,而不是为每行生成一个随机值。 / p>

证明

考虑

0 <= x <1

号码。如果将它乘以n,我们得到

0 <= n * x

这正是您要加载单个元素的需求。之所以不普及,是因为在许多情况下,仅数千条记录就不会引起性能问题。

编辑

如果您需要k个记录,而不仅是第一个,那么仍然有些困难,但是仍然可以解决。该算法将是这样的(我没有安装Oracle来测试它,所以我仅描述该算法):

randomize(n, k)
    randomized <- empty_set
    while (k > 0) do
        newValue <- random(n)
        n <- n - 1
        k <- k - 1
        //find out how many elements are lower than newValue
        //increase newValue with that amount
        //find out if newValue became larger than some values which were larger than new value
        //increase newValue with that amount
        //repeat until there is no need to increase newValue
    while end
randomize end

如果您从n个中随机分配了k个元素,那么您将可以在过滤器中使用这些值。