¿Cómo hacer una subconsulta de Postgresql en la cláusula select con unirse desde la cláusula como SQL Server?

81

Estoy tratando de escribir la siguiente consulta en postgresql:

select name, author_id, count(1), 
    (select count(1)
    from names as n2
    where n2.id = n1.id
        and t2.author_id = t1.author_id
    )               
from names as n1
group by name, author_id

Esto sin duda funcionaría en Microsoft SQL Server, pero no en postegresql. Leí un poco su documentación y parece que podría reescribirlo como:

select name, author_id, count(1), total                     
from names as n1, (select count(1) as total
    from names as n2
    where n2.id = n1.id
        and n2.author_id = t1.author_id
    ) as total
group by name, author_id

Pero eso devuelve el siguiente error en postegresql: "la subconsulta en FROM no puede referirse a otras relaciones del mismo nivel de consulta". Entonces estoy estancado. ¿Alguien sabe cómo puedo lograr eso?

Gracias

Ricardo
fuente
En realidad, parece que esto debería funcionar en Postgres (tal vez hace 6 años no lo hizo :))
qwertzguy

Respuestas:

121

No estoy seguro de entender perfectamente tu intención, pero quizás lo siguiente se acerque a lo que quieres:

select n1.name, n1.author_id, count_1, total_count
  from (select id, name, author_id, count(1) as count_1
          from names
          group by id, name, author_id) n1
inner join (select id, author_id, count(1) as total_count
              from names
              group by id, author_id) n2
  on (n2.id = n1.id and n2.author_id = n1.author_id)

Desafortunadamente, esto agrega el requisito de agrupar la primera subconsulta por id, así como por nombre y author_id, que no creo que se quisiera. Sin embargo, no estoy seguro de cómo solucionarlo, ya que necesita tener una identificación disponible para unirse a la segunda subconsulta. Quizás a alguien más se le ocurra una solución mejor.

Comparte y Disfruta.

Bob Jarvis - Reincorporar a Monica
fuente
Perfecto Bob, eso realmente funcionó. ¡Muchas gracias! Tuve que hacer un pequeño cambio porque no necesito la combinación con el id, solo el author_id. Entonces, la consulta final es: seleccione n1.name, n1.author_id, count_1, total_count from (seleccione id, name, author_id, count (1) as count_1 from names group by id, name, author_id) n1 inner join (seleccione author_id, count (1) as total_count del grupo de nombres por author_id) n2 en (n2.author_id = n1.author_id) Ahora que tengo esto, lo que realmente quiero es dividir count_1 por total_count para tener una frecuencia normalizada. = D
Ricardo
ops, acabo de darme cuenta de que el sql no se formatea correctamente aquí. :( Daré una respuesta para complementar.
Ricardo
No tuve el problema del que Ricado estaba hablando, pero este SQL solucionó totalmente mis problemas ...: ¡¡¡MUCHAS GRACIAS !!!
tftd
15

Como complemento de @Bob Jarvis y @dmikam responden, Postgres no realiza un buen plan cuando no usa LATERAL, debajo de una simulación, en ambos casos los resultados de los datos de consulta son los mismos, pero el costo es muy diferente

Estructura de la mesa

CREATE TABLE ITEMS (
    N INTEGER NOT NULL,
    S TEXT NOT NULL
);

INSERT INTO ITEMS
  SELECT
    (random()*1000000)::integer AS n,
    md5(random()::text) AS s
  FROM
    generate_series(1,1000000);

CREATE INDEX N_INDEX ON ITEMS(N);

Realización JOINcon GROUP BYen subconsulta sinLATERAL

EXPLAIN 
SELECT 
    I.*
FROM ITEMS I
INNER JOIN (
    SELECT 
        COUNT(1), n
    FROM ITEMS
    GROUP BY N
) I2 ON I2.N = I.N
WHERE I.N IN (243477, 997947);

Los resultados

Merge Join  (cost=0.87..637500.40 rows=23 width=37)
  Merge Cond: (i.n = items.n)
  ->  Index Scan using n_index on items i  (cost=0.43..101.28 rows=23 width=37)
        Index Cond: (n = ANY ('{243477,997947}'::integer[]))
  ->  GroupAggregate  (cost=0.43..626631.11 rows=861418 width=12)
        Group Key: items.n
        ->  Index Only Scan using n_index on items  (cost=0.43..593016.93 rows=10000000 width=4)

Utilizando LATERAL

EXPLAIN 
SELECT 
    I.*
FROM ITEMS I
INNER JOIN LATERAL (
    SELECT 
        COUNT(1), n
    FROM ITEMS
    WHERE N = I.N
    GROUP BY N
) I2 ON 1=1 --I2.N = I.N
WHERE I.N IN (243477, 997947);

Resultados

Nested Loop  (cost=9.49..1319.97 rows=276 width=37)
  ->  Bitmap Heap Scan on items i  (cost=9.06..100.20 rows=23 width=37)
        Recheck Cond: (n = ANY ('{243477,997947}'::integer[]))
        ->  Bitmap Index Scan on n_index  (cost=0.00..9.05 rows=23 width=0)
              Index Cond: (n = ANY ('{243477,997947}'::integer[]))
  ->  GroupAggregate  (cost=0.43..52.79 rows=12 width=12)
        Group Key: items.n
        ->  Index Only Scan using n_index on items  (cost=0.43..52.64 rows=12 width=4)
              Index Cond: (n = i.n)

Mi versión de Postgres es PostgreSQL 10.3 (Debian 10.3-1.pgdg90+1)

deFreitas
fuente
3
¡¡Gracias por la pista para usar LATERAL !!
leole
13

Solo estoy respondiendo aquí con la versión formateada del sql final que necesitaba según la respuesta de Bob Jarvis como se publicó en mi comentario anterior:

select n1.name, n1.author_id, cast(count_1 as numeric)/total_count
  from (select id, name, author_id, count(1) as count_1
          from names
          group by id, name, author_id) n1
inner join (select author_id, count(1) as total_count
              from names
              group by author_id) n2
  on (n2.author_id = n1.author_id)
Ricardo
fuente
13

Sé que esto es antiguo, pero desde Postgresql 9.3 hay una opción para usar una palabra clave "LATERAL" para usar subconsultas RELACIONADAS dentro de JOINS, por lo que la consulta de la pregunta se vería así:

SELECT 
    name, author_id, count(*), t.total
FROM
    names as n1
    INNER JOIN LATERAL (
        SELECT 
            count(*) as total
        FROM 
            names as n2
        WHERE 
            n2.id = n1.id
            AND n2.author_id = n1.author_id
    ) as t ON 1=1
GROUP BY 
    n1.name, n1.author_id
dmikam
fuente
1
Me pregunto si el rendimiento de estas dos consultas tiene diferencia, o si para postgresql es el mismo plan
deFreitas
1
Hice esta prueba y la respuesta está aquí (mi respuesta)
deFreitas
2
select n1.name, n1.author_id, cast(count_1 as numeric)/total_count
  from (select id, name, author_id, count(1) as count_1
          from names
          group by id, name, author_id) n1
inner join (select distinct(author_id), count(1) as total_count
              from names) n2
  on (n2.author_id = n1.author_id)
Where true

se usa distinctsi hay más unión interna, porque el rendimiento de un grupo de unión más es lento

Zahid Gani
fuente