On 06/12/2012 09:30, Matheus de Oliveira wrote:
> 2012/12/6 Thiago <[email protected]
> <mailto:[email protected]>>
>
>     On 05/12/2012 17:16, Matheus de Oliveira wrote:
>      >
>      >
>      > 2012/12/5 Thiago <[email protected]
>     <mailto:[email protected]>
>      > <mailto:[email protected]
>     <mailto:[email protected]>>>
>      >
>      >     Boa tarde.
>      >
>      >     Tenho as seguintes tabelas:
>      >
>      >     tb_mensagem
>      >         id
>      >         id_mensagem_pai
>      >         dth_insert
>      >
>      >     tb_mensagem_leitura
>      >         id_mensagem
>      >         dth_insert
>      >
>      >     A tabela de mensagens, sempre que existe uma mensagem com
>      >     id_mensagem_pai, significa que é uma resposta, as mensagens
>     originais
>      >     (primeiras) contém 0 (zero) no campo id_mensagem_pai.
>      >
>      >     Na tabela tb_mensagem_leitura eu tenho um histórico de todas
>     as vezes
>      >     que o usuário leu a mensagem, sendo gravado no campo
>     id_mensagem dessa
>      >     tabela sempre o id da mensagem original.
>      >
>      >     Preciso identificar quantas mensagens não lidas existe na para o
>      >     usuário, sendo que uma mensagem não lida é:
>      >     1) sem registro na tabela tb_mensagem_leitura;
>      >     2) o registro da tabela tb_mensagem_leitura é com data menor
>     que a
>      >     última resposta da mensagem original.
>      >
>      >     Alguém poderia me ajudar com este select? Ou até mesmo com a
>     forma em
>      >     que estruturei as tabelas, pois para chegar neste resultado
>     está um
>      >     pouco complicado.
>      >
>      >
>      > Cara, se eu não boiei na maionese, a consulta abaixo resolve o seu
>      > problema (não testado):
>      >
>      > WITH RECURSIVE mensagens AS (
>      >      SELECT id AS id_original, id, dth_insert
>      >      FROM tb_mensagem
>      >      WHERE id_mensagem_pai = 0
>      >      UNION ALL
>      >      SELECT pai.id_original, filha.id <http://filha..id>
>     <http://filha.id>, filha.dth_insert
>      >      FROM tb_mensagem AS filha
>      >          INNER JOIN mensagens AS pai ON pai.id <http://pai.id>
>     <http://pai.id> =
>      > filha.id_mensagem_pai
>      > ), ultimas AS (
>      >      SELECT m.id_original, MAX(m.dth_insert) AS dth_insert
>      >      FROM mensagens AS m
>      >      GROUP BY m.id_original
>      > )
>      > SELECT COUNT(*)
>      > FROM ultimas AS u
>      >      LEFT JOIN tb_mensagem_leitura AS l
>      >          ON l.id_mensagem = u.id_original
>      > WHERE l.id_mensagem IS NULL OR l.dth_insert < u.dth_insert;
>      >
>      >
>      > Não me parece muito performático, uma forma de melhorar seria SEMPRE
>      > guardar o id da mensagem original em tb_mensagem (não só a da
>     pai, mas a
>      > da primeira na hierarquia), o que evitaria toda essa sobrecarga
>     da query
>      > recursiva e da outra sub-query, que na prática só estão pegando a
>     data
>      > da última mensagem.
>      >
>      > Atenciosamente,
>
>     Mateus, muito obrigado pela resposta.
>
>     Acho que não expliquei direito.
>
>
> Também acho... =P
>
>     Na tabela tb_mensagem, o campo
>     id_mensagem_pai é sempre o id da mensagem original. Todas as respostas
>     estão amarradas a uma única mensagem e não em um formato de árvore.
>
>
> Agora entendi, é mais simples então.
>
>
>     Dessa forma você acha que existe uma solução melhor com uma melhor
>     performance? Essa será uma leitura muitas vezes executada, pois cada
>     página do portal que o usuário acessa essa consulta é refeita para
>     atualizar as mensagens não lidas.
>
>
>
> Sim. A forma abaixo:
>
>   SELECT COUNT(*) FROM (
>      SELECT
>          CASE m.id_mensagem_pai WHEN 0 THEN m.id <http://m.id> ELSE
> m.id_mensagem_pai END AS id_original,
>          MAX(m.dth_insert) AS dth_insert
>      FROM tb_mensagem AS m
>      GROUP BY CASE m.id_mensagem_pai WHEN 0 THEN m.id <http://m.id> ELSE
> m.id_mensagem_pai END
> ) AS u
>      LEFT JOIN tb_mensagem_leitura AS l
>          ON l.id_mensagem = u.id_original
> WHERE l.id_mensagem IS NULL OR l.dth_insert < u.dth_insert;

Não deu certo pelo fato de que na tabela de leitura eu vou ter n 
registros de leitura, por esse motivo o join está multiplicando pelas 
quantidades de leitura.

> Eu modelaria na mensagem original o id_mensagem_pai = id ao invés de 0
> na mensagem original, assim tira esse CASE daí.

Puxa, essa idéia foi ótima, ajudou e muito.

Eu cheguei a duas formas de fazer tal consulta:

1)
select count(*) from (
        select
                cr.id_mensagem_pai as id_mensagem,
                max(cr.dth_insert) as dth_insert
        from portal.tb_correio cr
        where cr.id_usuario_des = 2
        group by cr.id_mensagem_pai
) as cr
left join (
    select hl.id_mensagem,max(hl.dth_insert) as dth_insert
    from portal.tb_correio_historico_leitura hl
    where hl.id_usuario = 2
    group by hl.id_mensagem
) as hl on hl.id_mensagem = cr.id_mensagem
where hl.id_mensagem is null or hl.dth_insert < cr.dth_insert

2)
select count(*) from (
        select
                cr.id_mensagem_pai as id_mensagem,
                max(cr.dth_insert) as dth_insert
        from portal.tb_correio cr
        where cr.id_usuario_des = 2
        group by cr.id_mensagem_pai
) as cr
where
        (
                select hl.id
                from portal.tb_correio_historico_leitura hl
                where
                        hl.id_mensagem = cr.id_mensagem and
                        hl.dth_insert > cr.dth_insert and
                        hl.id_usuario = 2
                limit 1
) is null

Abaixo segue o explain de cada uma deles (coisa que eu nunca entendi).

1)
QUERY PLAN
Aggregate  (cost=5.18..5.19 rows=1 width=0)
   ->  Hash Left Join  (cost=4.68..5.16 rows=6 width=0)
         Hash Cond: (cr.id_mensagem_pai = hl.id_mensagem)
         Filter: ((hl.id_mensagem IS NULL) OR ((max(hl.dth_insert)) < 
(max(cr.dth_insert))))
         ->  HashAggregate  (cost=3.08..3.30 rows=17 width=12)
               ->  Seq Scan on tb_correio cr  (cost=0.00..2.98 rows=22 
width=12)
                     Filter: (id_usuario_des = 2)
         ->  Hash  (cost=1.55..1.55 rows=3 width=12)
               ->  HashAggregate  (cost=1.49..1.52 rows=3 width=12)
                     ->  Seq Scan on tb_correio_historico_leitura hl 
(cost=0.00..1.44 rows=10 width=12)
                           Filter: (id_usuario = 2)

2)
QUERY PLAN
Aggregate  (cost=30.88..30.89 rows=1 width=0)
   ->  Subquery Scan cr  (cost=3.08..30.88 rows=1 width=0)
         Filter: ((SubPlan 1) IS NULL)
         ->  HashAggregate  (cost=3.08..3.30 rows=17 width=12)
               ->  Seq Scan on tb_correio cr  (cost=0.00..2.98 rows=22 
width=12)
                     Filter: (id_usuario_des = 2)
         SubPlan 1
           ->  Limit  (cost=0.00..1.61 rows=1 width=4)
                 ->  Seq Scan on tb_correio_historico_leitura hl 
(cost=0.00..1.61 rows=1 width=4)
                       Filter: ((dth_insert > $1) AND (id_mensagem = $0) 
AND (id_usuario = 2))

Qual das formas será que terá melhor desempenho?

Obrigado!

_______________________________________________
pgbr-geral mailing list
[email protected]
https://listas.postgresql.org.br/cgi-bin/mailman/listinfo/pgbr-geral

Responder a