Получение бесед с несколькими участниками с последним сообщением для каждого - PullRequest
0 голосов
/ 01 августа 2020

Я пытаюсь создать простую схему базы данных приложения чата и запрашивать разговоры. Моя текущая настройка таблицы следующая:

CREATE TABLE chat_user (
    id bigint GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
    display_name VARCHAR(140),
    ... other user stuff ...
);

CREATE TABLE conversation (
    id bigint GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
    title VARCHAR(140),
    created timestamp with time zone NOT NULL
);

CREATE TABLE conversation_message (
    id bigint GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
    conversation_id bigint NOT NULL,
    sender_id bigint NOT NULL,
    body TEXT NOT NULL,
    created timestamp with time zone NOT NULL
);

CREATE TABLE conversation_participant (
    id bigint GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
    conversation_id bigint NOT NULL,
    user_id bigint NOT NULL
);

Таким образом, в основном каждый диалог имеет свой собственный заголовок и несколько участников. То, что я хотел бы получить, мои беседы отсортированы по дате последнего сообщения в беседе (чтобы сначала показывались беседы с самыми новыми сообщениями). Набор результатов должен содержать идентификатор, заголовок беседы и список участников + идентификатор, sender_id и тело последнего сообщения.

Также потребуется получить разговоры, разбитые на страницы на основе даты создания беседа (20 на страницу)

Достаточно ли эффективна моя настройка таблицы, чтобы удовлетворить указанное выше ограничение? Мне кажется, что это может привести к довольно большому запросу с несколькими подзапросами?.

Ответы [ 4 ]

1 голос
/ 14 августа 2020

Короче: Я думаю, у вас есть разумный дизайн для normalized (3NF) базы данных OLTP. Это то, к чему вы должны стремиться, а не количество JOIN s для конкретного c варианта использования. Имеющийся у вас дизайн будет соответствовать определенному вами варианту использования и многим другим вариантам использования, которые, я уверен, задействованы в этом вашем приложении.

Подробно: Вы разрабатываете для Система OLTP, в которой данные хранятся нормализованными для обеспечения согласованности данных и повышения эффективности транзакций OLTP.

Это, однако, означает, что вам придется делать гораздо больше JOIN s, чем ненормализованная база данных (которая подходит больше для OLAP, отчетности, систем аналитики). Такова природа OLTP relational databases.

Попытка уменьшить количество JOIN s в нормализованной базе данных (т.е. 3NF - Третья нормальная форма ) означает, что вы будете комбинировать данные с разной степенью детализации в одну и ту же таблицу и вызывают дублирование, что усложняет и замедляет обновления и, в конечном итоге, приводит к несогласованности данных. Вместо этого убедитесь, что у вас нормализованный дизайн и избегайте чрезмерной нормализации. В случаях, когда вы, возможно, захотите избежать написания длинных запросов, вы можете добавить VIEWS и использовать представление для написания запросов, чтобы упростить ваши запросы (но это может иногда приводить к неоптимальной производительности запросов из-за ненужных соединений).

1 голос
/ 10 августа 2020

Вы можете попробовать использовать lateral join .

Таким образом, ваш запрос будет выглядеть примерно так. Вы можете получить все необходимые данные, применить ограничения и смещения, а также получить последнее сообщение для каждого разговора. Надеюсь, это поможет.

select * from conversation c
left join lateral (
    select * from conversation_message cm 
       where cm.conversation_id=c.id
       order by created desc 
       limit 1
 ) cm on true
 left join conversation_participant cp on cp.id = cm.sender_id;

Левое присоединение здесь предназначено для чатов без сообщений.

1 голос
/ 01 августа 2020

Это ответ на исходную версию вопроса.

Кажется, вам нужен join и агрегирование:

select cm.conversation_id, max(created)
from conversation_message cm join
     conversation_participant cp
     on cm.conversation_id = cp.conversation_id
where cp.user_id = ?
group by cm.conversation_id
order by max(created) desc;
0 голосов
/ 11 августа 2020

Чтобы получить последнее сообщение для ваших разговоров, есть способы добиться этого, например, самостоятельное объединение или оконные функции (row_number (), rank () et c). Используя оконную функцию, вы можете написать свой запрос как

with cm as (
  select *,
  rank() over (partition by conversation_id order by created desc) as r
  from conversation_message
)

select  c.id, 
        c.title, 
        cm.body,
        cm.created,
        cm.r,
        cu.display_name
from conversation as c
left join cm on c.id = cm.conversation_id and cm.r  <= 1
left join chat_user cu on cu.id = cm.sender_id

DEMO

В приведенном выше запросе я использовал левые соединения, чтобы включить преобразования без сообщений, если вам нужно только разговоры, в которых есть сообщения, затем используют внутренние объединения. Если вам нужно более одного последнего сообщения для каждого изменения разговора cm.r <= @no

Чтобы получить список участников для каждого разговора, вы можете добавить новый CTE, например

with cm as (
  select *,
    rank() over (partition by conversation_id order by created desc) as r
  from conversation_message
),
 message_participants as (
  select
    m.conversation_id,
    array_agg(u.display_name order by m.created desc) as participants
  from chat_user as u
  join conversation_message as m on u.id = m.sender_id
  group by m.conversation_id
)

select  c.id, 
        c.title, 
        cm.body,
        cm.created,
        cm.r,
        cu.display_name,
        cmp.participants
from conversation c
left join cm on c.id = cm.conversation_id and cm.r  <= 1
left join chat_user cu on cu.id = cm.sender_id
left join message_participants cmp on c.id = cmp.conversation_id

DEMO

Улучшения

  • Добавьте user_id в conversation таблицу, чтобы определить, кто создал этот диалог.

  • Таблица conversation_participant является избыточным, а вы можете извлечь список участников из conversation_message

...