Сложный SQL-запрос для приложения для обмена сообщениями

Я создаю функцию обмена сообщениями в приложении Django 1.6.2 с использованием PostgreSQL 9.3.4. На домашней странице «Сообщения» пользователя я покажу список разговоров, которые пользователь ведет с другими пользователями. Каждая «плитка» или блок беседы будет показывать изображение и имя другого пользователя в этом диалоге, дату отправки последнего сообщения в этом диалоге и первые 25 символов в этом последнем сообщении. Я также покажу небольшой значок «ответить», если последнее сообщение было отправлено пользователем, просматривающим эти разговоры. У меня есть запрос до такой степени, что я могу идентифицировать все разговоры между зрителем и всеми другими пользователями, но у меня возникают проблемы с извлечением нужных мне полей из таблиц пользователей и сообщений.

Мои таблицы (показаны внизу): «Пользователь», «Сообщение» и «Разговор». Хотя я реализовал свои схемы таблиц так, чтобы между пользователями и разговорами существовала связь «многие ко многим», вначале я собираюсь создать свой интерфейс, чтобы пользователь мог отправлять сообщения только одному другому пользователю, а не несколько пользователей.

Когда я запускаю свой запрос по данным, показанным ниже, я пытаюсь получить обратно беседу и идентификаторы пользователей для пользователей 3, 4, 5 вместе с их связанными именами пользователей, последнее сообщение в этом разговоре, кто его отправил и дата его отправки. Вместо этого я получаю сообщение об ошибке:

ERROR: syntax error at or near "WHERE"

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

Прецедент

Данные в таблице связывания беседы_пользователь:

 id | conversation_id | user_id 
----+-----------------+---------
  1 |               1 |      32
  2 |               1 |       3   <- want this
  3 |               2 |      32
  4 |               2 |       4   <- want this
  6 |               3 |       3
  7 |               3 |       1
  8 |               4 |      32
  9 |               4 |       5   <- want this
 10 |               5 |       7
 11 |               5 |       9

Ряды хочу вернуть. Каждое сообщение - последнее сообщение в этой беседе.

conversation_id | user_id | username  | from_user | message | send_date
----------------+---------+-----------+-----------+---------+----------
 1              | 3       | user3     | u3 or u32 | <msg3>  | <date>
 2              | 4       | user4     | u4 or u32 | <msg4>  | <date>
 4              | 5       | user5     | u5 or u32 | <msg5>  | <date>

Запрос, который не работает:

SELECT cu.conversation_id,
       cu.user_id,
       au.username,
       m.from_user,
       m.message,
       m.send_date
FROM conversation_user cu
INNER JOIN auth_user au ON cu.user_id = au.id
INNER JOIN message m ON cu.conversation_id = m.conversation_id
ORDER BY m.send_date DESC LIMIT 1
WHERE conversation_id IN
    (SELECT conversation_id
     FROM conversation_user
     WHERE user_id = 32)
  AND user_id != 32;

Определения таблиц

# auth_user
--------------+--------------------------+------------------------------
 id           | integer                  | not null default nextval(...
 username     | character varying(30)    | not null
Referenced by:
    TABLE "conversation_user" CONSTRAINT "conversation_user_user_id_fkey" FOREIGN KEY (user_id) REFERENCES auth_user(id) DEFERRABLE INITIALLY DEFERRED
    TABLE "message" CONSTRAINT "message_from_user_id_fkey" FOREIGN KEY (from_user_id) REFERENCES auth_user(id) DEFERRABLE INITIALLY DEFERRED

# conversation
------------+--------------------------+--------------------------------
 id         | integer                  | not null default nextval(...
 start_date | timestamp with time zone | not null
Referenced by:
    TABLE "conversation_user" CONSTRAINT "conversation_id_refs_id_4344ca71" FOREIGN KEY (conversation_id) REFERENCES conversation(id) DEFERRABLE INITIALLY DEFERRED
    TABLE "message" CONSTRAINT "message_conversation_id_fkey" FOREIGN KEY (conversation_id) REFERENCES conversation(id) DEFERRABLE INITIALLY DEFERRED

# conversation_user
-----------------+---------+--------------------------------------------
 id              | integer | not null default nextval(...
 conversation_id | integer | not null
 user_id         | integer | not null
Foreign-key constraints:
    "conversation_id_refs_id_4344ca71" FOREIGN KEY (conversation_id) REFERENCES conversation(id) DEFERRABLE INITIALLY DEFERRED
    "conversation_user_user_id_fkey" FOREIGN KEY (user_id) REFERENCES auth_user(id) DEFERRABLE INITIALLY DEFERRED

# message
     Column      |           Type           |
-----------------+--------------------------+---------------------------
 id              | integer                  | not null default nextval(...
 conversation_id | integer                  | not null
 from_user_id    | integer                  | not null
 to_user_uid     | integer                  | not null
 message         | text                     | not null
 send_date       | timestamp with time zone | not null
Foreign-key constraints:
    "message_conversation_id_fkey" FOREIGN KEY (conversation_id) REFERENCES conversation(id) DEFERRABLE INITIALLY DEFERRED
    "message_from_user_id_fkey" FOREIGN KEY (from_user_id) REFERENCES auth_user(id) DEFERRABLE INITIALLY DEFERRED

person Jim    schedule 14.01.2015    source источник
comment
Переместите ORDER BY в положение после WHERE условий: WHERE conversation_id..... ORDER BY m.send_date DESC LIMIT 1   -  person Lamak    schedule 14.01.2015
comment
Почему вы пишете необработанный SQL, а не используете слой модели Django?   -  person Daniel Roseman    schedule 14.01.2015
comment
@DanielRoseman, я использую необработанный SQL, потому что, хотя я не эксперт по SQL, мне гораздо удобнее писать SQL, чем использовать команды ORM Django. Кроме того, я хотел бы устранить любые возможные издержки производительности, которые могут возникнуть из-за того, что Django должен переводить команды ORM в SQL.   -  person Jim    schedule 15.01.2015
comment
@DanielRoseman: В чем преимущество использования слоя модели Django?   -  person Erwin Brandstetter    schedule 15.01.2015
comment
@Lamak, спасибо за ваш вклад, но я должен отдать должное Эрвину, поскольку он оказал мне огромную помощь.   -  person Jim    schedule 15.01.2015
comment
@DanielRoseman, я использую ORM для простых запросов и модели Django для определения исходной структуры базы данных. Но я бы даже не стал пытаться перевести запрос решения в ORM Django.   -  person Jim    schedule 15.01.2015


Ответы (1)


Исправить синтаксис

По сути, вам просто нужно переместить условие WHERE в нужное место, например @ Lamak прокомментировал:

SELECT  ...
FROM conversation_user cu
INNER JOIN ...
WHERE conversation_id IN
    (SELECT conversation_id
     FROM conversation_user
     WHERE user_id = 32)
AND user_id != 32
ORDER BY m.send_date DESC
LIMIT 1;

Сделай это быстро

Согласно комментарию:

Я пытаюсь выбрать последнее сообщение в каждом [...] разговоре пользователя 32.

SELECT cu.conversation_id
     , ufrom.username AS from_user
     , uto.username   AS to_user
     , m.message
     , m.send_date
FROM   conversation_user cu
LEFT   JOIN LATERAL (
   SELECT from_user_id, to_user_id, message, send_date
   FROM   message   m
   WHERE  m.conversation_id = cu.conversation_id
   ORDER  BY send_date DESC
   LIMIT  1
   ) m ON TRUE
LEFT   JOIN auth_user ufrom ON ufrom.id = m.from_user_id
LEFT   JOIN auth_user uto   ON uto.id = m.to_user_id
WHERE  cu.user_id = 32;

Примечания

Дизайн БД

person Erwin Brandstetter    schedule 14.01.2015
comment
Эрвин, большое спасибо. Это почти то, что я пытаюсь сделать. Я пытаюсь выбрать последнее сообщение в каждом из трех разговоров, которые ведет user32 (с пользователями 3, 4 и 5). Это дает мне только последнее сообщение в последнем разговоре между user32 и user5. Я собираюсь продолжить смотреть на это, чтобы увидеть, смогу ли я это исправить. Пожалуйста, дайте мне знать, если вы знаете, какие изменения необходимо внести. Спасибо еще раз! - person Jim; 15.01.2015
comment
Этот запрос фактически извлекает нужные строки; ему просто не хватает столбцов из таблиц пользователей и сообщений. Это происходит из более раннего вопрос, с которым вы мне помогли. - person Jim; 15.01.2015
comment
@Robert: Любое количество пользователей может принять участие в одном разговоре? Но каждый пользователь только один раз? Ограничение UNIQUE обеспечит это (и автоматически создаст упомянутый индекс). - person Erwin Brandstetter; 15.01.2015
comment
О нет, в каждом разговоре могут принимать участие только два пользователя. user32 и user3 могут вести диалог, а user32 и user4 могут вести отдельный диалог, но пользователи 32, 3 и 4 не могут все быть в одном диалоге. В каждом разговоре каждый из двух пользователей может отправлять другому пользователю столько сообщений, сколько они хотят. Я только что написал запрос, который выбирает поля сообщения с помощью идентификатора беседы, поэтому почти кажется, что запрос, который выбирает разговоры на основе user_id и дает идентификатор разговора, должен быть подзапросом запроса таблицы сообщений. Я пытаюсь понять это. - person Jim; 15.01.2015
comment
По идее, он должен работать как этот запрос, за исключением того, что этот запрос не будет выполняться. Подзапросы могут выбирать только один столбец, а мой подзапрос выбирает четыре. - person Jim; 15.01.2015
comment
@Robert: Я удалил некоторые устаревшие вещи после того, как предоставил новый запрос. Также есть несколько ссылок, по которым я предлагаю перейти и прочитать. - person Erwin Brandstetter; 15.01.2015
comment
В вашем примере есть несколько из нас (например, ufrom ON ufrom.id). Полагаю, это опечатки? - person Jim; 15.01.2015
comment
@Robert: Это псевдонимы таблиц. Подробности в руководстве. - person Erwin Brandstetter; 15.01.2015
comment
О, теперь я это вижу. Но Postgres не любит строку SELECT 8. В ней написано: «ОШИБКА: столбец from_user не существует, я над этим работаю. - person Jim; 15.01.2015
comment
Эрвин, работает !!! Мне также пришлось внести это изменение в предложение JOIN ниже, а затем также изменить to_user на to_user_uid. Я очень ценю вашу помощь. Очевидно, вы освоили SQL. Есть ли какие-нибудь книги, которые вы могли бы порекомендовать мне изучить, чтобы улучшить свои навыки работы с SQL? Я создаю мобильное веб-приложение, поэтому производительность важна. Во многих статьях, которые я прочитал, говорится, что сайты работают медленно из-за взаимодействия с БД. Поэтому мне нужно научиться писать SQL, который выполняет свою работу и обладает высокой производительностью. Спасибо еще раз! - person Jim; 15.01.2015
comment
@Robert: Мой любимый источник - это отличное руководство. Это как с любым другим навыком: вы можете далеко продвинуться за короткое время, если приложите к нему некоторые усилия, но для овладения им требуется много времени. Рассмотрите возможность найма эксперта. - person Erwin Brandstetter; 15.01.2015