【问题标题】:Is there a better way to retrieve a random row from an Oracle table?有没有更好的方法从 Oracle 表中检索随机行?
【发布时间】:2019-10-29 00:07:24
【问题描述】:

不久前,我需要从 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; 

它的运行速度更快,并且似乎返回随机值,但真的如此吗?有人可以深入了解它是否真的是随机的并且行为是否符合预期?我真的很好奇为什么我在寻找时没有在其他任何地方找到这种方法,如果它确实是随机的并且在性能方面更好,为什么它没有更普遍?

【问题讨论】:

  • 使用SELECT * FROM tabela WHERE warunek AND ROWNUM =1。当然,当你多次执行查询时,你会得到相同的行,但原则上你会得到一个随机的行。
  • 也看到了这种方法,但遗憾的是它不能满足我的需求(在测试场景中随机行被获取然后传递给一个 SOAP 请求,这意味着查询将被执行多次)
  • 您对为什么第一个查询慢的解释不正确。内部查询需要很长时间,因为需要很长时间才能为 EACH ROW 生成不同的随机数。 Oracle不会根据随机数对内部查询的所有行进行排序;相反,它能够在内部查询中推送外部 ROWNUM = 1 条件 - 它只会返回具有最小随机值的行,它不会按随机值对所有行进行排序。第二种方法更好,因为它只需要生成一个随机值。
  • "Oracle 不会根据随机数对内部查询的所有行进行排序;相反,它能够将外部 ROWNUM = 1 条件推入内部查询" @mathguy 真的吗?这听起来不太合乎逻辑检查manual 所说的内容,因为您说优化将SELECT * FROM ( SELECT * FROM tabela ORDER BY dbms_random.value ) WHERE rownum = 1​ 重写为SELECT * FROM tabela WHERE rownum = 1​ ORDER BY dbms_random.value??这是不正确的,因为这些查询并不意味着同样的事情。
  • @RaymondNijland - 不,这不是我要说的。如果没有内部查询 - 外部查询结构,您将无法重写查询。相反,我解释的是如何在内部解释查询。如果我说“订购这组”,然后我说“给我 xyz 值最小的行”,我不会真正订购该组中的所有东西。我将一一检查每一行中的 xyz 值;每当我发现比以前记录的值低时,我将覆盖“记录”行并继续前进。我不需要按 xyz 排序所有行。确实,这就是 Oracle 运行时的工作方式。

标签: sql oracle random


【解决方案1】:

这是(可能)获得结果的最简单的查询。
但是SELECT COUNT(*) FROM MAIN_PRODUCT 将进行表扫描,我怀疑您是否可以获得不这样做的查询。

P.s 此查询假定未删除记录。

查询

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

【讨论】:

  • 谢谢,它也有点用,但比SELECT * FROM ( SELECT * FROM MAIN_PRODUCT WHERE ROWNUM &lt;= CAST((SELECT COUNT(*) FROM MAIN_PRODUCT)*dbms_random.value AS INTEGER) ORDER BY ROWNUM DESC ) WHERE ROWNUM = 1; 慢得多,有时根本不返回任何行
  • “谢谢,它也有点用,但比“慢得多” ... “有时根本不返回任何行” 实际上,此查询假定未删除记录忘记提及@Tohuv​​abohu
  • @Tohuv​​abohu 如果你有Oracle12c+ 你也可以试试第二个查询。
【解决方案2】:

您拥有的第二个代码

    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 之间的实数。将其与行数相乘将为您提供一个真正的随机数,这里的瓶颈是计算行数,而不是为每一行生成一个随机值。

证明

考虑

0

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

0

如果您想加载单个元素,这正是您所需要的。这种情况不普遍的原因是,在许多情况下,由于只有几千条记录,我们不会感觉到性能问题。

编辑

如果您需要 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 个元素,那么您将能够在过滤器中使用这些值。

【讨论】:

  • 感谢您消除我的疑虑 :)
  • @Tohuv​​abohu 欢迎您。如果任何答案解决了您的问题,那么您可能会考虑接受它作为正确答案。我认为无论是我的 Gordon Linoff 和 Raymond Nijland 的答案,都可以作为公认的答案。
【解决方案3】:

提高性能的关键是减少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​ ;

【讨论】:

  • 感谢您的精彩回答 :)
  • 我不同意。原始帖子的第一个查询中没有order by 加载。外部查询中的行限制子句 ROWNUM = 1 将导致内部查询简单地忽略 ORDER BY 指令;相反,执行引擎将只跟踪每一行的生成值,并且每当该值小于当前最小值时,它将用当前行替换先前保存的行。由于DBMS_RANDOM 负载(需要为每一行生成不同的随机数),第一个解决方案很慢 - 这是一个缓慢的操作。
猜你喜欢
  • 1970-01-01
  • 2019-07-17
  • 1970-01-01
  • 1970-01-01
  • 2020-09-29
  • 1970-01-01
  • 1970-01-01
  • 2013-11-25
  • 1970-01-01
相关资源
最近更新 更多