2012-05-15 2 views
9

Ich habe zwei Tabellen, custassets und tags. Um einige Testdaten zu erzeugen, möchte ich eine INSERT INTO eine Viele-zu-Viele-Tabelle mit einer SELECT machen, die zufällige Reihen von jedem bekommt (so dass ein zufälliger Primärschlüssel von einer Tabelle mit einem zufälligen Primärschlüssel von der Sekunde gepaart wird) . Zu meiner Überraschung ist das nicht so einfach wie ich zuerst dachte, also beharre ich damit, um mich selbst zu unterrichten.Wie bekomme ich ein zufälliges kartesisches Produkt in PostgreSQL?

Hier ist mein erster Versuch. Ich wähle 10 custassets und 3 tags, aber beide sind in jedem Fall gleich. Es würde mir gut gehen, wenn der erste Tisch repariert wird, aber ich möchte die zugewiesenen Tags zufällig randomisieren.

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 

Dies erzeugt:

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

Ich habe dann versucht, den folgenden Ansatz: den zweiten RANDOM() Aufruf in der SELECT Spaltenliste zu tun. Allerdings war dieses eine schlimmer, da es ein einzelnes Tag PK auswählt und dabei bleibt.

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 

Ergebnis:

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

Dies würde in einer Skriptsprache einfach sein, und ich bin sicher, dass mit einer Stored Procedure oder temporärer Tabelle ganz einfach durchgeführt werden. Aber kann ich es nur mit einer INSERT INTO SELECT tun?

Ich dachte über die Auswahl von Integer-Primärschlüssel mit einer zufälligen Funktion, aber leider haben die Primärschlüssel für beide Tabellen Lücken in den Inkrement-Sequenzen (und so eine leere Zeile in jeder Tabelle gewählt werden kann). Das wäre sonst gut gewesen!

+0

Danke an alle, die kommentiert haben - wenn es nach mir ginge, hätte ich mehrere Zecken vergeben! ':-)' – halfer

Antwort

11

Aktualisiert, um CTEs durch Unterabfragen zu ersetzen, die normalerweise schneller sind.

wirklich zufällige Kombinationen zu erzeugen, ist es genug rn für den größeren Satz randomisieren:

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

Wenn beliebigen Kombinationen sind gut genug, das ist schneller (vor allem für große Tabellen):

Wenn die Anzahl der Zeilen in beiden Tabellen nicht übereinstimmen und Sie keine Zeilen aus der größeren ta verlieren möchten ble, verwenden Sie die modulo operator % Zeilen aus dem kleineren Tisch mehrmals zu verbinden:

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

Wie in meinem Kommentar erwähnt, window functions (with appended OVER clause) in PostgreSQL 8.4 oder höher zur Verfügung stehen.

+0

Erwin, danke für deine gründliche Antwort - sehr geschätzt. Ich sollte jetzt auch 'WITH' und' USING' nachschlagen! ':)' – halfer

+0

@halfer: Keine Sorge, beide sind leicht zu verstehen. CTEs sind grundsätzlich Unterabfragen, die mehrfach verwendet werden können und 'USING (rn)' ist grundsätzlich kurz für 'ON x.rn = y.rn'. Es gibt jedoch subtile Unterschiede. Folge einfach meinen Links. –

1

Es stört mich, dass nach all den Jahren der relationalen Datenbanken scheint es nicht sehr gute Cross-Datenbank Möglichkeiten, solche Dinge zu tun. Der MSDN Artikel http://msdn.microsoft.com/en-us/library/cc441928.aspx scheint einige interessante Ideen zu haben, aber das ist natürlich nicht PostgreSQL. Und selbst dann benötigt ihre Lösung einen einzigen Durchlauf, wenn ich denke, dass es ohne den Scan möglich sein sollte.

Ich kann mir ein paar Möglichkeiten vorstellen, die ohne einen Pass (in Auswahl) funktionieren könnten, aber es würde eine andere Tabelle erstellen, die die Primärschlüssel Ihrer Tabelle auf zufällige Zahlen (oder auf lineare Sequenzen, die Sie später zufällig auswählen) einige Wege könnten tatsächlich besser sein), und natürlich könnte das auch Probleme haben.

Ich realisiere, dass dies wahrscheinlich ein nicht nützlicher Kommentar ist, ich fühlte nur, dass ich ein bisschen räkeln musste.

+0

Heh, nun, wenn die Antwort 'nicht möglich' ist, dann ist das fair genug ':)'. Wir werden sehen, welche anderen Antworten kommen. – halfer

+0

In der Tat, ich möchte auch sehen, welche anderen Antworten kommen. Ich wollte nicht implizieren, die Antwort ist nicht möglich, ich meinte nur "nicht gut" wie in einer bestimmten Lösung Beide scheinen eine Menge Setup- oder Fast-Table-Scans zu erfordern. Ich muss zugeben, dass ich nicht sicher bin, was mit Ihrer Anfrage nicht stimmt. – JayC

+3

Möglicherweise fehlt Ihnen heute das modernste RDBMS-Support-Fenster (MySQL ist die unrühmliche Ausnahme). Alle Antworten hier sollten grundsätzlich in MSSQL, Oracle und PostgreSQL gleich funktionieren. –

1

Wenn Sie nur eine zufällige Reihe von Zeilen von jeder Seite erhalten möchten, verwenden Sie einen Pseudozufallszahlengenerator. Ich würde so etwas wie verwenden:

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 

Dies ist ein kartesisches Produkt zu tun, die 900 Zeilen zurückgibt eine Annahme und b jeweils mindestens 30 Reihen.

Allerdings interpretierte ich Ihre Frage als zufällige Kombinationen. Noch einmal, ich würde mich für den Pseudozufallsansatz entscheiden.

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> 

Dadurch erhalten Sie Kombinationen unter beliebigen Zeilen.

+0

Danke für die Antwort; Ja, es sind zufällige Kombinationen, die ich brauche (ich habe die problematischen Resultsets der Frage hinzugefügt, um sie klarer zu machen). Ich habe Ihre zweite Abfrage versucht, aber ich bin mir nicht sicher, dass "OVER" von Postgres (8.4) unterstützt wird. Ist das ein MSSQL Server-only-Schlüsselwort? – halfer

+0

@halfer: Fensterfunktionen (einschließlich 'row_number()') werden in Postgres 8.4 unterstützt (http://www.postgresql.org/docs/8.4/interactive/functions-window.html). OVER (ORDER BY NULL) ist jedoch nur Rauschen und kann zu OVER() 'vereinfacht werden. Keines ist gut darin, zufällige Ergebnisse zu erzeugen. Sie erhalten eine implementierungsspezifische, willkürliche Reihenfolge, meist in der Reihenfolge, in der die Zeilen eingegeben wurden. –

+0

@ErwinBrandstetter - danke dafür. Ich habe gründlich nach 'postgresql over' gesucht, aber ich muss es verpasst haben - vielleicht ist 'over' ein zu häufiges Wort!Ich kenne diese Funktionen nicht, deshalb werde ich sie lesen. – 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; 

Sie können diese Abfrage unter SQL Fiddle testen.

+0

Puh, wenn Erwins Lösung mein Gehirn überhitzt hat, dann hat es sich in ein schwarzes Loch verwandelt! Große Anstrengung, mit einer SQLfiddle ebenso; Danke und +1. – halfer

1

Nur ein einfaches cartesian Produkt ON Random() scheint recht gut zu funktionieren. Einfaches comme bonjour ...

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

Hier ist ein anderer Ansatz eine einzige Kombination aus zwei Tabellen, die von zufällig auszuwählen, unter der Annahme, zwei Tabellen a und b, beide mit Primärschlüssel id. Die Tabellen müssen nicht dieselbe Größe haben, und die zweite Zeile wird unabhängig von der ersten ausgewählt, was für Testdaten möglicherweise nicht so wichtig ist.

Getestet mit zwei Tabellen, eine der Größe 7000 Zeilen, eine mit 100k Zeilen, Ergebnis: sofort. Für mehr als ein Ergebnis müssen Sie die Abfrage wiederholt aufrufen - das LIMIT erhöhen und das Ändern von x.id = zu x.id IN würde Ergebnismuster (aA, aB, bA, bB) ergeben.

+0

Eine sehr neue Lösung, gute Sachen. Vielen Dank! – halfer