12

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

这是我的第一次尝试。我选择 10custassets和 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

这会产生:

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
....

然后我尝试了以下方法:在列列表中进行第二次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

结果:

 custassets_id | tags_rand_id 
---------------+--------------
         16694 |         1537
         14204 |         1537
         23823 |         1537
         34799 |         1537
         36388 |         1537
....

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

我确实考虑过使用随机函数选择整数主键,但不幸的是,两个表的主键在增量序列中都有间隙(因此可能在每个表中选择一个空行)。不然就好了!

4

6 回答 6

15

更新以用通常更快的子查询替换 CTE。

为了产生真正的随机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);

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

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);

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

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);

正如我在评论中提到的,窗口函数(带有附加OVER子句)在 PostgreSQL 8.4 或更高版本中可用。

于 2012-05-15T19:57:15.427 回答
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;

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

于 2012-05-15T20:02:31.820 回答
2

这是一种从 2 个表中随机选择单个组合的不同方法,假设两个表ab,都具有主键id。这些表不必具有相同的大小,并且第二行是从第一行独立选择的,这对于测试数据可能并不那么重要。

SELECT * FROM a, b 
 WHERE a.id = (
    SELECT id 
    FROM a 
    OFFSET (
        SELECT random () * (SELECT count(*) FROM a)
    ) 
    LIMIT 1) 
 AND b.id = (
    SELECT id 
    FROM b 
    OFFSET (
        SELECT random () * (SELECT count(*) FROM b)
        ) 
    LIMIT 1);

用两张表进行测试,一张有 7000 行,一张有 100k 行,结果:立即。对于多个结果,您必须重复调用查询 - 增加 LIMIT 并更改x.id =x.id IN会产生 (aA, aB, bA, bB) 结果模式。

于 2012-05-25T15:33:15.497 回答
1

只是一个简单的 carthesian 产品 ON random() 似乎工作得相当好。简单的来吧您好...

-- Cartesian product
-- EXPLAIN ANALYZE
INSERT INTO dirgraph(point_from,point_to,costs)
SELECT p1.the_point , p2.the_point, (1000*random() ) +1
FROM allpoints p1
JOIN allpoints p2 ON random() < 0.002
        ;
于 2012-05-16T12:05:07.597 回答
1

如果您只想从每一侧获取一组随机行,请使用伪随机数生成器。我会使用类似的东西:

select *
from (select a.*, row_number() over (order by NULL) as rownum -- NULL may not work, "(SELECT NULL)" works in MSSQL
      from a
     ) a cross join
     (select b.*,  row_number() over (order by NULL) as rownum
      from b
     ) b
where a.rownum <= 30 and b.rownum <= 30

这是一个笛卡尔积,假设 a 和 b 至少有 30 行,则返回 900 行。

但是,我将您的问题解释为随机组合。再一次,我会选择伪随机方法。

select *
from (select a.*, row_number() over (order by NULL) as rownum -- NULL may not work, "(SELECT NULL)" works in MSSQL
      from a
     ) a cross join
     (select b.*,  row_number() over (order by NULL) as rownum
      from b
     ) b
where modf(a.rownum*107+b.rownum*257+17, 101) < <some vaue>

这让你得到任意行之间的组合。

于 2012-05-15T16:20:02.873 回答
1

让我感到困扰的是,经过这么多年的关系数据库,似乎没有很好的跨数据库方式来做这样的事情。MSDN 文章http://msdn.microsoft.com/en-us/library/cc441928.aspx似乎有一些有趣的想法,但那当然不是 PostgreSQL。即使那样,他们的解决方案也需要一次通过,而我认为它应该能够在没有扫描的情况下完成。

我可以想象一些没有通过(在选择中)的方法,但它会涉及创建另一个表,将表的主键映射到随机数(或稍后随机选择的线性序列,在某些方面实际上可能更好),当然,这也可能有问题。

我意识到这可能是一个无用的评论,我只是觉得我需要咆哮一下。

于 2012-05-15T15:22:47.413 回答