选择 1 个具有复杂过滤的随机行

Posted

技术标签:

【中文标题】选择 1 个具有复杂过滤的随机行【英文标题】:select 1 random row with complex filtering 【发布时间】:2017-04-20 10:05:47 【问题描述】:

我有 2 张桌子:

第一个表users

+-------------------------+---------+------+-----+---------+-------+
| Field                   | Type    | Null | Key | Default | Extra |
+-------------------------+---------+------+-----+---------+-------+
| id                      | int(11) | NO   | PRI | NULL    |       |
| first_name              | text    | NO   |     | NULL    |       |
| age                     | int(11) | YES  |     | NULL    |       |
| settings                | text    | YES  |     | NULL    |       |
+-------------------------+---------+------+-----+---------+-------+

第二张桌子proposals

+---------+---------+------+-----+---------+----------------+
| Field   | Type    | Null | Key | Default | Extra          |
+---------+---------+------+-----+---------+----------------+
| id      | int(11) | NO   | PRI | NULL    | auto_increment |
| from_id | int(11) | NO   |     | NULL    |                |
| to_id   | int(11) | NO   |     | NULL    |                |
| status  | int(11) | NO   |     | NULL    |                |
+---------+---------+------+-----+---------+----------------+

我需要从id 不在to_idproposals 中的用户那里获得1 随机

我正在用这个 sql 做(没有 rand):

SELECT DISTINCT *
FROM profiles
WHERE
    profiles.first_name IS NOT NULL
AND
NOT EXISTS (
    SELECT *
    FROM proposal
    WHERE
        proposal.to_id = profiles.id
)
LIMIT 0 , 1

性能很好:1 row in set (0.00 sec)

但是性能很差:1 row in set (1.78 sec) 当我在末尾添加ORDER BY RAND()

我在users.id 中有大漏洞,我不能使用MAX(id) 之类的东西

我尝试设置随机limit,例如:

...
LIMIT 1234 , 1;
Empty set (2.71 sec)

但这也需要很多时间:(

如何以良好的性能获得users.id 中不存在的proposals.to_id 的随机1 个用户?

我认为我需要先用rand() 获取所有profiles,然后过滤它们,但我不知道该怎么做。

【问题讨论】:

RAND() 很难优化。但是here 是一些技巧。大多数其他技术确实涉及表扫描,这是主要的杀手。 【参考方案1】:

我有两个问题解决方案。

1) 随机 id,来自https://***.com/a/4329447/2051938

SELECT *
FROM profiles AS r1
JOIN
    (SELECT CEIL(RAND() *
                     (SELECT MAX(id)
                        FROM profiles)) AS id)
        AS r2
WHERE
    r1.id >= r2.id
    AND
    r1.first_name IS NOT NULL
AND
NOT EXISTS (
    SELECT *
    FROM proposal
    WHERE
        proposal.to_id = r1.id
)
LIMIT 0 , 1

2) 与ORDER BY RAND()

SELECT *
FROM
    (
        SELECT *
        FROM profiles
        WHERE
            profiles.first_name IS NOT NULL
        ORDER BY RAND()
    ) AS users
WHERE
    NOT EXISTS (
        SELECT *
        FROM proposal
        WHERE
            proposal.to_id = users.id
    )
LIMIT 0 , 1

第一个解决方案更快,但它存在“id 中的漏洞”以及当你从最后得到id 时的问题(用户可能会在匹配之前结束)

第二种方案速度较慢但没有缺陷!

【讨论】:

【参考方案2】:

您是否尝试过将not exists 切换为left join

SELECT DISTINCT *
FROM   profiles t1
LEFT JOIN
       proposal t2
ON     t1.id = t2.to_id
WHERE  t1.first_name IS NOT NULL AND
       t2.to_id IS NULL
ORDER BY RAND()
LIMIT 0 , 1

这将返回profiles 的所有行,对于那些不是matched 的行,它将分配NULL 值,您可以对其进行过滤。

结果应该是一样的,但性能可能会更好。

【讨论】:

我错了:#1054 - Unknown column 'profiles.first_name' in 'where clause' 我编辑了我的答案,可能是因为它现在需要使用表别名 你的 sql 比简单的 add "order by rand()" 效率低 :( 结果:1 row in set (2.03 sec)【参考方案3】:

由于RAND() 函数会为结果中存在的每一行分配一个随机数,因此性能将与记录数成正比。

如果您只想选择一个(随机)记录,您可以申请LIMIT <random number from 0 to record count>, 1

例如:

SELECT u.id, count(u.id) as `count`
FROM users u
WHERE
    first_name IS NOT NULL
AND
NOT EXISTS (
    SELECT *
    FROM proposal
    WHERE
        proposal.to_id = u.id
)
LIMIT RAND(0, count-1) , 1

我没有尝试执行这个查询,但是,它mysql 抱怨在RAND 中使用count,您可以单独计算计数并替换此查询中的值。

【讨论】:

我有问题:#1064 - You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near '*) as count FROM users u WHERE first_name IS NOT NULL AND NOT EXISTS ' at line 1 试试改成u.id 错误:#1327 - Undeclared variable: RAND,如何解决? 将计数放入变量中(使用另一个查询)并在此查询中使用该值?【参考方案4】:

首先,我认为select distinct 没有必要。因此,请尝试删除它:

SELECT p.*
FROM profiles p
WHERE p.first_name IS NOT NULL AND
      NOT EXISTS (SELECT 1
                  FROM proposal pr
                  WHERE pr.to_id = p.id
                 )
ORDER BY rand()
LIMIT 0 , 1;

这可能会有所帮助。那么,减少花费时间的一个相对简单的方法是减少数据量。如果您知道您将始终有数千行满足条件,那么您可以这样做:

SELECT p.*
FROM profiles
WHERE p.first_name IS NOT NULL AND
      NOT EXISTS (SELECT 1
                  FROM proposal pr
                  WHERE pr.to_id = p.id
                 ) AND
      rand() < 0.01
ORDER BY rand()
LIMIT 0, 1;

诀窍是找到确保您至少获得一行的比较值。这很棘手,因为您有另一组数据。这是一种使用子查询的方法:

SELECT p.*
FROM (SELECT p.*, (@rn := @rn + 1) as rn
      FROM profiles p CROSS JOIN
           (SELECT @rn := 0) params
      WHERE p.first_name IS NOT NULL AND
            NOT EXISTS (SELECT 1
                        FROM proposal pr
                        WHERE pr.to_id = p.id
                       ) 
     ) p
WHERE rand() < 100 / @rn
ORDER BY rand()
LIMIT 0, 1;

这使用一个变量来计算行数,然后随机选择其中的 100 个进行处理。当随机选择 100 行时,至少有一个被选择的可能性非常、非常、非常高。

这种方法的缺点是子查询需要具体化,这增加了查询的成本。但是,它比对完整数据进行排序要便宜。

【讨论】:

它需要:1 row in set (1.06 sec) 在 1000 个用户数据库中,这很慢,因为我需要使用 10 000 000 个用户数据库 :(

以上是关于选择 1 个具有复杂过滤的随机行的主要内容,如果未能解决你的问题,请参考以下文章

Pandas 数据框中的随机行选择

shellshuf命令提取文件的随机行

从Hive表中选择每列的随机行数

如何从 SQL 数据库表中选择随机行? [复制]

读取大型 csv 文件、python、pandas 的随机行

为spark scala中的数据框中的每个组采样不同数量的随机行