【问题标题】:How do I select unique pairs of rows from a table at random?如何从表中随机选择唯一的行对?
【发布时间】:2011-01-27 23:32:06
【问题描述】:

我有两张这样的表:

CREATE TABLE people (
    id INT NOT NULL,
    PRIMARY KEY (id)
)

CREATE TABLE pairs (
    person_a_id INT,
    person_b_id INT,
    FOREIGN KEY (person_a_id) REFERENCES people(id),
    FOREIGN KEY (person_b_id) REFERENCES people(id) 
)

我想从 people 表中随机选择一对人,在选择他们之后,我将随机选择的对添加到 pairs 表中。 person_a_id 总是指具有较低 id 对的人(因为对的顺序不相关)。

问题是我不想两次选择同一对,所以我需要在返回随机选择的对之前检查对表。

是否有可能以相当高效和优雅的方式仅使用一个 SQL 查询来做到这一点?

(我正在使用 Java Persistence API 执行此操作,但希望我能够将任何答案转换为 JPA 代码)

【问题讨论】:

  • 这可能可行,但不会很漂亮。
  • 我看不出这在基于集合的方法中是如何实现的。我可以使用游标解决它(因此可以在单个存储过程中完成)。最大的障碍是您的选择池必须随着您添加到对表中的每个随机对而减少。
  • 这是 MySQL 缺乏对检查约束的支持的地方。在这种情况下,检查强制 person_a_id
  • @Mark Byers - 排序并不是这里真正的问题。在这种情况下,MySQL 在数据完整性方面当然不会摇摆不定。只有使用触发器才能强制执行 A
  • 其他没有提到的是“随机性如何?”。从技术上讲,people 表的交叉连接是“随机的”;只是不是特别难以预测。分布必须是什么样的?成对的会员资格有限制吗?例如,person_id = 42 可以在每一对中吗?我认为在这种情况下,最好的解决方案是在记录使用的对并让数据库作为防止重复的最后一道防线时,在 Java 中生成随机化。

标签: sql mysql


【解决方案1】:
select a.id, b.id
from people1 a
inner join people1 b on a.id < b.id
where not exists (
    select *
    from pairs1 c
    where c.person_a_id = a.id
      and c.person_b_id = b.id)
order by a.id * rand()
limit 1;

Limit 1 如果您一次“抽签”一对,则只返回一对。否则,你需要多少对。

上面的查询假设你可以得到

1 - 2
2 - 7

并且配对 2 - 7 是有效的,因为它不存在,即使 2 再次出现。如果您只想让某人出现在 only one 配对中,那么

select a.id, b.id
from people1 a
inner join people1 b on a.id < b.id
where not exists (
    select *
    from pairs1 c
    where c.person_a_id in (a.id, b.id))
  and not exists (
    select *
    from pairs1 c
    where c.person_b_id in (a.id, b.id))
order by a.id * rand()
limit 1;

如果要在单个查询中生成multiple pairsAND 目标表仍然为空,则可以使用此单个查询。请注意,LIMIT 6 只返回 3 对。

select min(a) a, min(b) b
from
(
    select
      case when mod(@p,2) = 1 then id end a,
      case when mod(@p,2) = 0 then id end b,
      @p:=@p+1 grp
    from (
        select id
        from (select @p:=1) p, people1
        order by rand()
        limit 6
    ) x
) y
group by floor(grp/2)

【讨论】:

  • 这将返回所有组合的随机排序列表。它仍然需要迭代。使用LIMIT 1 以外的任何东西都是不安全的,因为有可能在一次迭代中将同一个人配对两次。编辑:您的编辑解决了这个问题。
  • SELECT 之前添加INSERT INTO tbl?需要迭代什么?是同一对还是同一个人?不清楚1-2和2-7in the same query是否有效
  • @cyberwiki 可能是一个模糊的问题...如果他想生成随机对(而不是所有可能的对),那么必须对其进行迭代,因为当您第一次查看时 pairs 将是空的.
  • 这可能是个愚蠢的问题,但您是否有理由将这些表称为“people1”和“pairs1”,而不仅仅是“people”和“pairs”?
  • @sanity - 好问题。因为我已经有一个“人”表,所以我不会费心把它放到我的测试数据库中:) 我通常只发布经过测试的代码
【解决方案2】:

这不能在基于单查询集合的方法中完成,因为您的集合不知道将哪些对插入到对表中。

相反,你应该循环

WHILE EXISTS(SELECT * FROM people 
    WHERE id NOT IN (SELECT person_a_id FROM pairs) 
    AND id NOT IN (SELECT person_b_id FROM pairs) 

当有无与伦比的人时,这将循环。 然后你应该从 1 到该表的CNT(*) 的两个随机数 这为您提供了无与伦比的人数...如果您两次获得相同的数字,请再次滚动。 (如果您对此感到担心,请从集合的两半中随机化数字......但是根据您的排序标准,您会失去一些随机性)

将这些人配对。

清洗、漂洗、重复...... 你唯一的“重做”将是当你两次生成相同的随机数时......更有可能因为你很少有人但仍然最多只有 25% 的机会(比 1/n^2 好得多)

【讨论】:

    猜你喜欢
    • 2017-01-26
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-05-09
    • 2014-09-10
    • 2014-11-21
    相关资源
    最近更新 更多