sql >> Base de Datos >  >> RDS >> PostgreSQL

CTE y la paradoja del cumpleaños

Will Leinweber de Postgres Open ha twitteado una consulta interesante:

-- this returns a different result each time it is ran
with recursive s as (
  select random()
union
  select random() from s
) select count(*) from s;

Me gusta este ejemplo:un resultado sorprendente, que puede explicarse (y de hecho ayuda a explicar) el comportamiento de CTE.

Las verdades inesperadas se denotan con la palabra "paradoja" y, de hecho, se trata de una manifestación (una "instancia", en la jerga de los programadores) de lo que se conoce como la paradoja del cumpleaños. .

Su formulación más simple es probablemente esta:si eliges al azar a 23 personas, la probabilidad de que dos de ellas compartan el mismo cumpleaños es mayor al 50 %.

El resultado es inesperado, porque hay 366 cumpleaños diferentes, y el número 23 parece muy pequeño en comparación con el 366.

Sin embargo, es correcto, ya que se puede demostrar con un cálculo directo. En PostgreSQL podemos ejecutar otro CTE recursivo:

WITH RECURSIVE r(i,acc) AS (
  SELECT 1, 1 :: double precision
UNION
  SELECT i + 1,
    acc * (((366 - i) :: double precision) / 366)
  FROM r WHERE acc > 0.5
) SELECT count(1) FROM r;

produciendo 23 como resultado.

Una CTE recursiva se detiene cuando el paso recursivo no agrega filas nuevas. En la última consulta, acc representa la probabilidad de que la primera i los cumpleaños son distintos, por lo que la recursividad se detiene cuando ese número no supera el 50 %.

En la consulta mencionada al principio, a la que llamaremos "consulta aleatoria" para abreviar, la CTE recursiva termina cuando random() no agrega una nueva fila. Es decir, cuando el valor calculado aleatoriamente ya se calculó en una iteración anterior; eso es porque el CTE recursivo está usando UNION en lugar de UNION ALL .

Esta es de hecho la paradoja del cumpleaños, con 366 reemplazado por el máximo número posible de valores distintos que random() puede producir. ¿Qué es exactamente ese número?

El random() La función devuelve un valor de doble precisión, cuya definición exacta depende del sistema. Sin embargo, no se pueden producir todos los valores de doble precisión; la función C subyacente puede generar 2^31 resultados diferentes, independientemente del tamaño de bit de un valor de doble precisión. Esto es lo suficientemente bueno en la práctica y, al mismo tiempo, se garantiza la compatibilidad con todas las distintas arquitecturas e implementaciones de bibliotecas.

Entonces podemos reemplazar 366 con 2^31 en nuestra consulta, y en lugar de 23 obtenemos 54563 como respuesta.

¿Se acerca al resultado real de la consulta aleatoria? Ejecutémoslo varias veces, recopilemos el resultado y calculemos el promedio:

gianni=# create table t(n int);
CREATE TABLE

gianni=# with recursive s as (
  select random()
union
  select random() from s
) insert into t select count(1) from s;
INSERT 0 1

/* repeat the last query 49 times */

gianni=# select count(1), avg(n) from t;

 count | avg
-------+--------------------
    50 | 54712.060000000000
 (1 row)

El promedio de los resultados reales está bastante cerca del umbral esperado de 54563; la diferencia es inferior al 0,3 %, bastante ortodoxamente , podríamos decir!