2012-05-15 1 views
9

У меня есть две таблицы, custassets и tags. Чтобы сгенерировать некоторые тестовые данные, я хотел бы сделать таблицу INSERT INTO со многими для многих с SELECT, которая получает случайные строки от каждого (чтобы случайный первичный ключ из одной таблицы был сопряжен со случайным первичным ключом из второго) , К моему удивлению, это не так просто, как я впервые подумал, поэтому я упорствую в этом, чтобы научить себя.Как я могу получить случайный декартова продукт в PostgreSQL?

Это моя первая попытка. Я выбираю 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 

Это дает:

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?

Я думал о выборе целых первичных ключей с использованием случайной функции, но, к сожалению, первичные ключи для обеих таблиц имеют пробелы в последовательностях инкремента (и поэтому в каждой таблице может быть выбрана пустая строка). В противном случае это было бы хорошо!

+0

Спасибо всем, кто прокомментировал - если бы это зависело от меня, я бы присвоил несколько тиков! ':-)' – halfer

ответ

11

Обновлено для замены 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); 

Если количество строк в обеих таблицах не соответствует, и вы не хотите потерять строки из большего размера ta BLE, использовать modulo operator % присоединиться строки из нескольких раз меньше таблицы:

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

Как уже упоминалось в моем комментарии, window functions (with appended OVER clause) доступны в PostgreSQL 8.4 или более поздней версии.

+0

Эрвин, спасибо за ваш тщательный ответ - очень ценю. Теперь мне нужно искать «С» и «ИСПОЛЬЗОВАТЬ»! ':)' – halfer

+0

@halfer: Не стоит беспокоиться, оба легко понять. CTE - это, в основном, подзапросы, которые можно использовать несколько раз, и 'USING (rn)' в основном является коротким для 'ON x.rn = y.rn'. Однако есть тонкие различия. Просто следуйте моим ссылкам. –

1

Это меня задевает, что после всех этих лет реляционных баз данных, похоже, не очень хорошие способы перекрестной базы данных делать такие вещи. В статье MSDN http://msdn.microsoft.com/en-us/library/cc441928.aspx, похоже, есть интересные идеи, но, конечно же, это не PostgreSQL. И даже тогда их решение требует одного прохода, когда я думаю, что он должен быть выполнен без сканирования.

Я могу представить несколько способов, которые могут работать без прохода (в выборе), но это потребует создания другой таблицы, которая отображает первичные ключи вашей таблицы в случайные числа (или линейные последовательности, которые вы впоследствии произвольно выбираете, что в некоторые способы могут быть лучше), и, конечно же, это может иметь проблемы.

Я понимаю, что это, вероятно, полезный комментарий, я просто почувствовал, что мне нужно немного рассказать.

+0

Хе, ну, если ответ «невозможно», то это справедливо ':)'. Мы увидим, какие другие ответы приходят. – halfer

+0

Действительно, я также хочу посмотреть, какие другие ответы приходят. Я не хотел подразумевать, что ответ невозможен, я просто имел в виду «нехорошо», как в конкретном решении либо, кажется, требует много настроек или почти полного сканирования таблицы. Должен признаться, я не уверен, что случилось с вашим запросом. – JayC

+3

Возможно, вам не хватало, что большинство современных функций окна поддержки RDBMS в настоящее время (MySQL - бесславное исключение). Все ответы здесь должны в основном работать одинаково в MSSQL, Oracle и PostgreSQL. –

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 

Это делает декартово произведение, которое возвращает 900 строк в предположении а и Ь имеют по меньшей мере 30 строк.

Однако, я интерпретировал ваш вопрос как получение случайных комбинаций. Еще раз, я бы пошел на псевдослучайный подход.

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> 

Это позволит вам получить комбинации между произвольными строками.

+0

Спасибо за ответ; да, это случайные комбинации, в которых я нуждаюсь (я добавил проблематичные результаты на вопрос для ясности). Я попробовал второй запрос, но я не уверен, что «OVER» поддерживается Postgres (8.4). Это ключевое слово для сервера MSSQL? – halfer

+0

@halfer: Функции окна (включая 'row_number()') [поддерживаются в Postgres 8.4] (http://www.postgresql.org/docs/8.4/interactive/functions-window.html). Однако «OVER (ORDER BY NULL)» является просто шумом и может быть упрощен до «OVER()». Ничего хорошего при производстве случайных результатов. Вы получаете конкретный вариант выполнения, произвольный порядок, в основном в той же последовательности, что и строки. –

+0

@ErwinBrandstetter - спасибо за это. Я тщательно искал «postgresql over», но, должно быть, пропустил его - возможно, «слишком» - слишком распространенное слово!Я не знаком с этим набором функций, поэтому я буду читать их. – halfer

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

+0

Фу, если решение Эрвина заставит мой мозг перегреться, у этого он рухнет в черную дыру! Большие усилия, с SQLfiddle; спасибо и +1. – halfer

1

Просто случайный карточный продукт ON random(), кажется, работает достаточно хорошо. Простой Comme бонжур ...

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

Вот другой подход, чтобы выбрать один из комбинации 2-х таблиц случайных, предполагая две таблицы a и b, как с первичным ключом id. Таблицы не должны быть одинакового размера, а вторая строка независимо выбирается из первой, что может быть не так важно для testdata.

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 строк, один с 100 тыс. Строк, результат: немедленно. Для более чем одного результата вам нужно повторно вызвать запрос - увеличение значения LIMIT и изменение x.id = до x.id IN приведет к созданию шаблонов результатов (aA, aB, bA, bB).

+0

Очень новое решение, хороший материал. Благодаря! – halfer