Как составить составной SQL-запрос с обращением к сторонней таблице?

HSQLDB

Есть таблица Cinemas:

CREATE TABLE cinemas
(
    id   INTEGER GENERATED BY DEFAULT AS SEQUENCE GLOBAL_SEQ PRIMARY KEY,
);

Таблица Films (ссылается на Cinema отношением ManyToOne):

CREATE TABLE films
(
    id        INTEGER GENERATED BY DEFAULT AS SEQUENCE GLOBAL_SEQ PRIMARY KEY,
    cinema_id INTEGER              NOT NULL,
    date      DATE DEFAULT today() NOT NULL,
    CONSTRAINT films_cinema_id_date UNIQUE (cinema_id, date),
    FOREIGN KEY (cinema_id) REFERENCES cinemas (id) ON DELETE CASCADE
);

Таблица Users:

CREATE TABLE users
(
    id   INTEGER GENERATED BY DEFAULT AS SEQUENCE GLOBAL_SEQ PRIMARY KEY,
);

И последняя, четвёртая таблица Votes отображает голосование пользователей за фильмы. То есть: есть несколько кинотеатров. В каждом кинотеатре каждый день идёт новый фильм. Пользователи голосуют за этот фильм, что отражается в таблице Votes:

CREATE TABLE votes
(
    user_id INTEGER NOT NULL,
    film_id INTEGER NOT NULL,
    CONSTRAINT votes_user_film_idx UNIQUE (user_id, film_id),
    FOREIGN KEY (user_id) REFERENCES users (id),
    FOREIGN KEY (film_id) REFERENCES films (id)
);

Какой запрос мне составить, чтобы получить кинотеатр-победитель за конкретную дату? То есть кинотеатр, за фильм которого на конкретную дату проголосовало больше всего людей.

Я подозреваю, что надо использовать MAX и EXISTS, но без понятия как. Не могли бы вы привести пример правильного запроса?


Ответы (2 шт):

Автор решения: Victor Ishkov

Можно использовать для решения оконные функции. Запрос выведет кинотеатры победители за каждую дату, если нужна конкретная дата, просто дополните условие WHERE. Стоит добавить, что запрос не выведет дату в которую были показаны фильмы, если за них вообще никто не проголосовал

SELECT
    cinema_id,
    cinema_name,
    films_date,
    rating
FROM (
    SELECT
        *,
        CASE
            WHEN rating = MAX(rating) OVER(PARTITION BY films_date) THEN 1 ELSE 0
        END max_rating
    FROM (
        SELECT
            c.ID cinema_id,
            c.NAME cinema_name,
            f.DATE films_date,
            COUNT(*) rating
        FROM cinemas c
            INNER JOIN films f ON c.ID = f.CINEMA_ID
            INNER JOIN votes v ON f.ID = v.FILM_ID
        GROUP BY c.ID, c.NAME, f.DATE
    ) date_rating
) max_date_rating
WHERE max_rating = 1
ORDER BY films_date;
→ Ссылка
Автор решения: Zhenyria

Удалось справить при помощи такого кода (синтаксис HSQLDB):

SELECT *
    FROM CINEMAS
    WHERE ID IN (
    SELECT CINEMA_ID
    FROM (
             SELECT CINEMA_ID, COUNT(*) AS CINEMA_COUNT
             FROM VOTES as VOTE,
                  FILMS as FILM,
                  CINEMAS as CINEMA
             WHERE VOTE.FILM_ID = FILM.ID
               AND FILM.CINEMA_ID = CINEMA.ID
               AND FILM.DATE = :DATE
             GROUP BY CINEMA_ID
             ORDER BY CINEMA_COUNT DESC
             LIMIT 1));

SELECT в шестой строке возвращает таблицу вида:

  СINEMA_ID    CINEMA_COUNT
  ---------    ------------
   100004           4

А именно: считает количество голосов для фильма по определённой дате для каждого кинотеатра, а потом группирует по CINEMA_ID. Я не придумал, как сюда прикрутить MAX, чтобы выбирать максимальное из CINEMA_COUNT, а возвращать соответствующий максимальному значению CINEMA_ID, поэтому просто сортирую группу по количеству голосов и при помощи LIMIT 1 беру только верхнюю запись.

Потом парочка костылей. Я буду не против, если вы подскажете, как сделать этот запрос грамотней, но пока есть что есть. В строке 4 я получаю из полученной таблицы только одну колонку, а потом при помощи IN в строке 3 нахожу и возвращаю подходящий CINEMA, id которого соответствует CINEMA_ID.

→ Ссылка