如何在PostgreSQL中获得随机笛卡尔积?

hal*_*fer 11 sql random postgresql join cartesian-product

我有两张桌子,custassetstags.为了生成一些测试数据,我想做INSERT INTO一个多对多的表,SELECT每个表从每个表中获取随机行(这样一个表中的随机主键与第二个表中的随机主键配对).令我惊讶的是,这并不像我最初想的那么容易,所以我坚持用这个来自学.

这是我的第一次尝试.我选择10 custassets和3 tags,但两者在每种情况下都相同.第一个表被修复后我会没事的,但是我想随机分配分配的标签.

SELECT
    custassets_rand.id custassets_id,
    tags_rand.id tags_rand_id
FROM
    (
        SELECT id FROM custassets WHERE defunct = false ORDER BY RANDOM() LIMIT 10
    ) AS custassets_rand
,
    (
        SELECT id FROM tags WHERE defunct = false ORDER BY RANDOM() LIMIT 3
    ) AS tags_rand
Run Code Online (Sandbox Code Playgroud)

这会产生:

custassets_id | tags_rand_id 
---------------+--------------
          9849 |         3322  }
          9849 |         4871  } this pattern of tag PKs is repeated
          9849 |         5188  }
         12145 |         3322
         12145 |         4871
         12145 |         5188
         17837 |         3322
         17837 |         4871
         17837 |         5188
....
Run Code Online (Sandbox Code Playgroud)

然后我尝试了以下方法:RANDOM()SELECT列列表中进行第二次调用.然而,这个更糟糕,因为它选择单个标签PK并坚持下去.

SELECT
    custassets_rand.id custassets_id,
    (SELECT id FROM tags WHERE defunct = false ORDER BY RANDOM() LIMIT 1) tags_rand_id
FROM
    (
        SELECT id FROM custassets WHERE defunct = false ORDER BY RANDOM() LIMIT 30
    ) AS custassets_rand
Run Code Online (Sandbox Code Playgroud)

结果:

 custassets_id | tags_rand_id 
---------------+--------------
         16694 |         1537
         14204 |         1537
         23823 |         1537
         34799 |         1537
         36388 |         1537
....
Run Code Online (Sandbox Code Playgroud)

这在脚本语言中很容易,我相信使用存储过程或临时表可以很容易地完成.但是我可以用一个INSERT INTO SELECT吗?

我确实考虑过使用随机函数选择整数主键,但遗憾的是两个表的主键在增量序列中都有间隙(因此每个表中可能会选择一个空行).那本来没问题!

Erw*_*ter 12

更新以使用通常更快的子查询替换CT​​E.

为了产生真正随机的组合,它足以随机化为rn更大的集合:

SELECT c_id, t_id
FROM  (
   SELECT id AS c_id, row_number() OVER (ORDER BY random()) AS rn
   FROM   custassets
   ) x
JOIN   (SELECT id AS t_id, row_number() OVER () AS rn FROM tags) y USING (rn);
Run Code Online (Sandbox Code Playgroud)

如果任意组合足够好,这会更快(特别是对于大表):

SELECT c_id, t_id
FROM   (SELECT id AS c_id, row_number() OVER () AS rn FROM custassets) x
JOIN   (SELECT id AS t_id, row_number() OVER () AS rn FROM tags) y USING (rn);
Run Code Online (Sandbox Code Playgroud)

如果两个表中的行数不匹配,并且您不希望丢失较大表中的行,请使用模运算符%多次连接较小表中的行:

SELECT c_id, t_id
FROM  (
   SELECT id AS c_id, row_number() OVER () AS rn
   FROM   custassets -- table with fewer rows
   ) x
JOIN  (
   SELECT id AS t_id, (row_number() OVER () % small.ct) + 1 AS rn
   FROM   tags
       , (SELECT count(*) AS ct FROM custassets) AS small
   ) y USING (rn);
Run Code Online (Sandbox Code Playgroud)

正如我的评论中所提到的,PostgreSQL 8.4或更高版本中提供了窗口函数(带附加OVER子句).


vye*_*rov 5

WITH a_ttl AS (
    SELECT count(*) AS ttl FROM custassets c),
b_ttl AS (
    SELECT count(*) AS ttl FROM tags),
rows AS (
    SELECT gs.*
      FROM generate_series(1,
           (SELECT max(ttl) AS ttl FROM
              (SELECT ttl FROM a_ttl UNION SELECT ttl FROM b_ttl) AS m))
           AS gs(row)),
tab_a_rand AS (
    SELECT custassets_id, row_number() OVER (order by random()) as row
      FROM custassets),
tab_b_rand AS (
    SELECT id, row_number() OVER (order by random()) as row
      FROM tags)
SELECT a.custassets_id, b.id
  FROM rows r
  JOIN a_ttl ON 1=1 JOIN b_ttl ON 1=1
  LEFT JOIN tab_a_rand a ON a.row = (r.row % a_ttl.ttl)+1
  LEFT JOIN tab_b_rand b ON b.row = (r.row % b_ttl.ttl)+1
 ORDER BY 1,2;
Run Code Online (Sandbox Code Playgroud)

您可以在SQL Fiddle上测试此查询。