2012-05-15 21 views
9

Tengo dos tablas, custassets y tags. Para generar algunos datos de prueba me gustaría hacer una tabla de muchos a muchos INSERT INTO con un SELECT que obtiene filas aleatorias de cada uno (de modo que una clave primaria aleatoria de una tabla se empareja con una clave primaria aleatoria de la segunda) . Para mi sorpresa, esto no es tan fácil como pensé, así que persisto en esto para enseñarme a mí mismo.¿Cómo puedo obtener un producto cartesiano al azar en PostgreSQL?

Aquí está mi primer intento. Seleccioné 10 custassets y 3 tags, pero ambos son iguales en cada caso. Estaré bien con la primera tabla arreglada, pero me gustaría aleatorizar las etiquetas asignadas.

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 

Esto produce:

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

Luego probé el siguiente enfoque: hacer el segundo RANDOM() llamada de la lista SELECT columna. Sin embargo, este fue peor, ya que elige una sola etiqueta PK y se queda con ella.

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 

Resultado:

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

Esto sería fácil en un lenguaje de script, y estoy seguro de que se puede hacer muy fácilmente con un procedimiento almacenado o tabla temporal. ¿Pero puedo hacerlo solo con un INSERT INTO SELECT?

Pensé en elegir claves primarias enteras usando una función aleatoria, pero desafortunadamente las claves primarias para ambas tablas tienen lagunas en las secuencias de incremento (por lo que se puede elegir una fila vacía en cada tabla). ¡Eso hubiera estado bien de otra manera!

+0

Gracias a todos los que hicieron comentarios, si dependiera de mí, ¡habría otorgado varios tics! ':-)' – halfer

Respuesta

11

Se ha actualizado para reemplazar los CTE con subconsultas que suelen ser más rápidas.

Para producir verdaderamente aleatorios combinaciones, es suficiente para cambiar aleatoriamente rn para el conjunto más grande:

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

If arbitrarias combinaciones son lo suficientemente buenas, esto es más rápido (especialmente para las grandes tablas):

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

Si el número de filas en ambas tablas no coincide y no desea perder filas de la ta grande ble, utilizar el modulo operator % para unirse a las filas de la tabla más pequeña en múltiples ocasiones:

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

Como se ha mencionado en mi comentario, window functions (with appended OVER clause) están disponibles en PostgreSQL 8.4 o posterior.

+0

Erwin, gracias por su minuciosa respuesta, muy apreciada. ¡Ahora también debo buscar 'WITH' y' USING'! ':)' – halfer

+0

@halfer: sin preocupaciones, ambos son fáciles de entender. Los CTE son básicamente subconsultas que se pueden usar varias veces y 'USING (rn)' es básicamente la abreviatura de 'ON x.rn = y.rn'. Sin embargo, hay diferencias sutiles. Solo sigue mis enlaces. –

1

Me molesta que después de todos estos años de bases de datos relacionales, no parece haber muy buenas formas de hacer una base de datos cruzada para hacer cosas como esta. El artículo http://msdn.microsoft.com/en-us/library/cc441928.aspx de MSDN parece tener algunas ideas interesantes, pero, por supuesto, eso no es PostgreSQL. E incluso entonces, su solución requiere una sola pasada, cuando creo que debería poder hacerse sin el escaneo.

Puedo imaginar algunas maneras que podrían funcionar sin un pase (en selección), pero implicaría crear otra tabla que asigne claves primarias de la tabla a números aleatorios (o a secuencias lineales que luego seleccione al azar, que en algunas maneras en realidad pueden ser mejores), y por supuesto, eso también puede tener problemas.

Me doy cuenta de que este es probablemente un comentario no útil, simplemente sentí que necesitaba despotricar un poco.

+0

Heh, bueno, si la respuesta es 'no posible', entonces eso es suficiente ':)'. Veremos qué otras respuestas vienen. – halfer

+0

De hecho, también quiero ver qué otras respuestas vienen. No quise dar a entender que la respuesta no es posible, solo quise decir "no es bueno" como en una solución en particular o bien parece requerir una gran cantidad de configuración o cerca de escaneos completos de tablas. Tengo que admitir que no estoy seguro de qué pasa con tu consulta. – JayC

+3

Puede que se esté perdiendo la mayoría de las funciones de ventana de soporte de RDBMS modernas hoy en día (MySQL es la excepción sin gloria). Todas las respuestas aquí deberían funcionar básicamente igual en MSSQL, Oracle y PostgreSQL. –

1

Si solo desea obtener un conjunto aleatorio de filas de cada lado, utilice un generador de números pseudoaleatorio. Me gustaría utilizar algo como:

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 

esto está haciendo un producto cartesiano, que devuelve 900 filas asumiendo una y B tienen al menos 30 filas.

Sin embargo, interpreté su pregunta como combinaciones al azar. Una vez más, elegiría el enfoque pseudoaleatorio.

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> 

Esto le permite obtener combinaciones entre filas arbitrarias.

+0

Gracias por la respuesta; sí, son combinaciones aleatorias que necesito (he agregado los resultados problemáticos a la pregunta para mayor claridad). Intenté tu segunda consulta, pero no estoy seguro de que 'OVER' sea compatible con Postgres (8.4). ¿Es una palabra clave solo para MSSQL Server? – halfer

+0

@halfer: las funciones de ventana (incluido 'row_number()') son [compatibles con Postgres 8.4] (http://www.postgresql.org/docs/8.4/interactive/functions-window.html). Sin embargo, 'OVER (ORDER BY NULL)' es solo ruido y se puede simplificar a 'OVER()'. Ninguno de los dos es bueno para producir resultados aleatorios. Obtiene un orden específico, arbitrario de implementación, principalmente en la misma secuencia en que se ingresaron las filas. –

+0

@ErwinBrandstetter - gracias por eso. Busqué exhaustivamente 'postgresql over', pero no lo he extrañado, ¡tal vez 'over' es una palabra demasiado común!No estoy familiarizado con este conjunto de funciones, así que las leeré. – 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; 

Puede probar esta consulta en SQL Fiddle.

+0

¡Uf, si la solución de Erwin hizo que mi cerebro se sobrecaliente, esta colapsó en un agujero negro! Gran esfuerzo, con un SQLfiddle también; gracias y +1. – halfer

1

Simplemente un producto carthesian simple ON random() parece funcionar razonablemente bien. Comme sencilla 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

Aquí es un enfoque diferente para recoger una sola combinación de 2 mesas por azar, suponiendo dos mesas a y b, ambos con clave primaria id. Las tablas no necesitan ser del mismo tamaño, y la segunda fila se elige independientemente de la primera, lo que puede no ser tan importante para los 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); 

Probado con dos tablas, una de las filas de tamaño 7000, una con 100k filas, resultado: inmediatamente. Para más de un resultado, debe llamar a la consulta repetidamente; aumentar el LIMITE y cambiar x.id = a x.id IN produciría patrones de resultado (aA, aB, bA, bB).

+0

Una solución muy novedosa, cosas buenas. ¡Gracias! – halfer

Cuestiones relacionadas