【问题标题】:MySQL - how to replace ORDER BY in a bit more complicated query?MySQL - 如何在更复杂的查询中替换 ORDER BY?
【发布时间】:2012-09-14 17:22:12
【问题描述】:

为了从表中获取记录,我使用了这个 mysql 查询:

SELECT 
    a.id as aid, a.data1 as adata1, a.data2 as adata2
    b.id as bid, b.data1 as bdata1, b.data2 as bdata2
FROM table AS a
JOIN table AS b ON ( a.id <> b.id ) 
WHERE (a.data1=1 AND b.data1=1) AND ABS( a.rating - b.rating ) <100
ORDER BY RAND() 
LIMIT 1

这个查询准确地获取了我需要的记录,但不幸的是因为RAND(),这个查询很慢。

我找到了一些方法,如何避免使用RAND() 函数,例如here。但我的问题是,我仍然找不到方法,如何在此查询中替换 RAND() 函数。 在一些简单的查询中替换RAND()是没有问题的,但是我不知道,在上面的例子中怎么做……因为WHERE子句中有更多的条件。

【问题讨论】:

  • 您确定问题出在 RAND() 上吗?这几乎是笛卡尔连接。如果你拿出“ORDER BY”,你会得到什么类型的解释计划?
  • 可以添加SHOW CREATE TABLE 输出吗?
  • 表格有多少行? data1=1 有多少?如果您删除了LIMIT,则返回多少行?

标签: php mysql sql random sql-order-by


【解决方案1】:

由于您使用的是 MySQL,您可以尝试使用以下 SQL 查询,首先从表中获取计数,然后根据该计数选择随机偏移量。然后它准备一个语句,以便可以使用计算出的偏移量并执行该语句。

SELECT @count := COUNT(*) FROM table AS a JOIN table AS b ON ( a.id <> b.id ) WHERE (a.data1=1 AND b.data1=1) AND ABS( a.rating - b.rating ) <100;
SET @offset = CONVERT(FLOOR(RAND() * @count), SIGNED);
PREPARE mystatement FROM "SELECT 
                          a.id as aid, a.data1 as adata1, a.data2 as adata2
                          b.id as bid, b.data1 as bdata1, b.data2 as bdata2
                          FROM table AS a
                          JOIN table AS b ON ( a.id <> b.id ) 
                          WHERE (a.data1=1 AND b.data1=1) AND ABS( a.rating - b.rating ) <100 LIMIT ?, 1";
EXECUTE mystatement USING @offset;
DEALLOCATE PREPARE mystatement;

在大型数据集上的执行速度应该比ORDER BY RAND() 快,请尝试让我知道... ;-)

编辑

查询在 phpmyadmin 上无法使用,因此请使用 MySQL 控制台运行它们或编写一个 php 脚本,其中有两个选项,第一个是让 mysql 完成工作:

mysql_query('SELECT @count := COUNT(*) FROM table AS a JOIN table AS b ON ( a.id <> b.id ) WHERE (a.data1=1 AND b.data1=1) AND ABS( a.rating - b.rating ) <100');
mysql_query('SET @offset = CONVERT(FLOOR(RAND() * @count), SIGNED)');
mysql_query('PREPARE mystatement FROM "SELECT 
                          a.id as aid, a.data1 as adata1, a.data2 as adata2
                          b.id as bid, b.data1 as bdata1, b.data2 as bdata2
                          FROM table AS a
                          JOIN table AS b ON ( a.id <> b.id ) 
                          WHERE (a.data1=1 AND b.data1=1) AND ABS( a.rating - b.rating ) <100 LIMIT ?, 1"');
$res = mysql_query('EXECUTE mystatement USING @offset');
$row = mysql_fetch_assoc($res);
print_r($row);

第二个可能更快的选择包括使用 MySQL 和编程语言(在我们的例子中为 PHP)做一部分工作:

$res = mysql_query("SELECT COUNT(*) FROM table AS a JOIN table AS b ON ( a.id <> b.id ) WHERE (a.data1=1 AND b.data1=1) AND ABS( a.rating - b.rating ) <100')");
$row = mysql_fetch_array($res);
$offset = rand(0, $row[0]-1);

$res = mysql_query("SELECT 
                              a.id as aid, a.data1 as adata1, a.data2 as adata2
                              b.id as bid, b.data1 as bdata1, b.data2 as bdata2
                              FROM table AS a
                              JOIN table AS b ON ( a.id <> b.id ) 
                              WHERE (a.data1=1 AND b.data1=1) AND ABS( a.rating - b.rating ) <100 LIMIT $offset, 1");
$row = mysql_fetch_assoc($res);

我发现的另一种加快 ORDER BY RAND() 的替代方法包括如下查询:

SELECT 
    a.id as aid, a.data1 as adata1, a.data2 as adata2
    b.id as bid, b.data1 as bdata1, b.data2 as bdata2
FROM table AS a
JOIN table AS b ON ( a.id <> b.id ) 
WHERE (RAND() < (SELECT ((1/COUNT(*))*10) FROM table AS a JOIN table AS b ON ( a.id <> b.id ) ) )
 AND (a.data1=1 AND b.data1=1) AND ABS( a.rating - b.rating ) <100
ORDER BY RAND() 
LIMIT 1

别忘了告诉我你得到的结果;-)。

【讨论】:

  • aleroot:谢谢你的帖子。我尝试将此查询运行到 phpmyadmin 并收到此错误消息:#1243 - Unknown prepared statement handler (mystatement) given to EXECUTE 所以,我尝试删除最后两行(以EXECUTEDEALLOCATE 开头)并得到@count := COUNT(*) =&gt; 683066` 什么是我做错了吗?
  • 不使用来自 phpmyadmin 的查询将不起作用,从命令行实用程序 (mysql) 执行。
  • 不幸的是,该应用程序在主机上,我无法从命令行运行查询。所以,我必须找到一种方法来运行它——我会尝试将它重写到我的 PHP 代码中。
  • @user984621 是的,如果您需要使用 PHP 脚本运行它,我在编辑后的答案中编写了两个 sn-ps 代码来帮助您...
  • 非常感谢你!你可能解决了我两晚的噩梦!它正在工作,我现在测试了几分钟,它肯定比以前更快。现在查询平均需要大约 2 秒。目前我正在使用第二个 PHP sn-p,我将尝试最后一个 SQL 查询,如果它会更快的话。
【解决方案2】:

你的问题不是很具体。 . .桌子有多大?究竟什么是“相当慢”?您正在尝试查找表中的所有记录对,其中 data1 = 1 并且评级差异小于 100。在以下版本中,我将所有条件移至“ON”子句,因此它们更清晰地放在一起:

SELECT a.id as aid, a.data1 as adata1, a.data2 as adata2
       b.id as bid, b.data1 as bdata1, b.data2 as bdata2
FROM table AS a join
     table AS b
     ON a.id <> b.id and
        a.data1 = b.data1 and
        a.data1 = 1 and b.data1 = 1 and
        ABS( a.rating - b.rating ) < 100
ORDER BY RAND() 
LIMIT 1

我还添加了附加条件a.data1 = b.data1,因为这有助于 SQL 引擎将其识别为等值连接,这应该有助于提高连接性能。

假设 data1 是选择性的(意味着相对较少的记录有 data1),那么您应该能够使用 (data1, id) 或 (data1, rating) 上的索引来加快这一速度。

如果您知道每条记录至少有一个匹配项(即,每条记录都有另一个具有相似评分的记录),那么以下变体应该会更好:

SELECT a.id as aid, a.data1 as adata1, a.data2 as adata2
       b.id as bid, b.data1 as bdata1, b.data2 as bdata2
FROM (select *
      from table AS a
      where a.data1 = 1
      order by rand()
      limit 1
     ) a join
     table AS b
     ON a.id <> b.id and
        a.data1 = b.data1 and
        a.data1 = 1 and b.data1 = 1 and
        ABS( a.rating - b.rating ) < 100
ORDER BY RAND() 
LIMIT 1

这首先选择一个随机记录,然后进行自连接。

这让我想到您可以采取不同的方法来解决这个问题,如下所示。首先计算您正在查看的数据的评级。然后选择差值小于 100 的随机评分对,然后找到与这些评分匹配的随机记录。使用 data1 和 rating 的索引,这种方法可能是最快的。

【讨论】:

  • @Gordong:感谢您的留言。我要试试。关于您的问题 - 该表有大约 11.000 条记录,“相当慢”是 6 秒,没有 ORDER BY RAND() 0.5 秒。我正在尝试从同一类别中找到 2 条唯一记录,其中评分差异小于 100。
【解决方案3】:

如果你没事的话。在问题空间上的分布不太均匀,您可以尝试:

SELECT a.id as aid, a.data1 as adata1, a.data2 as adata2
       b.id as bid, b.data1 as bdata1, b.data2 as bdata2
  FROM ( SELECT *
           FROM table
          WHERE data1 = 1
          ORDER
             BY RAND()
          LIMIT 1
       ) a
  JOIN table b
    ON b.data1 = 1
   AND b.rating BETWEEN a.rating - 100 AND a.rating + 100
 ORDER
    BY RAND()
 LIMIT 1
;

上面会随机选择一条记录为a,然后随机选择一条记录为b。因此,它需要订购和加入的记录要少得多。这不太统一,因为这意味着a 的所有选择的可能性将是相等的,而不是与b 的可能对应选择的数量成正比,但也许它足以满足您的目的?

【讨论】:

  • 这有可能不返回任何行,即使存在满足原始条件的行。
  • @ypercube:是的,绝对的。这是因为a 的所有选择的可能性都是相等的——即使a 的选择没有b 的匹配行。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多