选择 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_id
中proposals
中的用户那里获得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 个具有复杂过滤的随机行的主要内容,如果未能解决你的问题,请参考以下文章