Счетчик SQL по нескольким полям - PullRequest
0 голосов
/ 23 июля 2011

Это продолжение предыдущего вопроса: Сложный запрос COUNT в MySQL .Ни один из ответов не сработал при любых условиях, и у меня тоже были проблемы с поиском решения.Я буду назначать награду в 75 баллов первому человеку, который предоставит полностью правильный ответ (я буду назначать награду, как только она будет доступна, и в качестве справки я уже делал это раньше: Улучшение кода представления Python / django).

Я хочу получить количество видео-кредитов, которые есть у пользователя, и не допустить дублирования (т. Е. Для каждого видео пользователь может быть зачислен в него 0 или 1 раз. Я хочу найти триподсчитывает: количество видео, загруженных пользователем (просто) - Uploads, количество видео, зачисленных на видео, не загруженные пользователем - Credited_by_others, а также общее количество видео, на которые пользователь был зачислен- Total_credits.

У меня есть три таблицы:

CREATE TABLE `userprofile_userprofile` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `full_name` varchar(100) NOT NULL,
   ...
 )

CREATE TABLE `videos_video` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `title` int(11) NOT NULL,
  `uploaded_by_id` int(11) NOT NULL,
  ...
  KEY `userprofile_video_e43a31e7` (`uploaded_by_id`),
  CONSTRAINT `uploaded_by_id_refs_id_492ba9396be0968c` FOREIGN KEY (`uploaded_by_id`) REFERENCES `userprofile_userprofile` (`id`)
)

Обратите внимание, что uploaded_by_id совпадает с userprofile.id

CREATE TABLE `videos_videocredit` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `video_id` int(11) NOT NULL,
  `profile_id` int(11) DEFAULT NULL,
  `position` int(11) NOT NULL
  ...
  KEY `videos_videocredit_fa26288c` (`video_id`),
  KEY `videos_videocredit_141c6eec` (`profile_id`),
  CONSTRAINT `profile_id_refs_id_31fc4a6405dffd9f` FOREIGN KEY (`profile_id`) REFERENCES `userprofile_userprofile` (`id`),
  CONSTRAINT `video_id_refs_id_4dcff2eeed362a80` FOREIGN KEY (`video_id`) REFERENCES `videos_video` (`id`)
)

Ниже приводится пошаговая инструкция для иллюстрации:

1) создать 2 пользователей:

insert into userprofile_userprofile (id, full_name) values (1, 'John Smith');
insert into userprofile_userprofile (id, full_name) values (2, 'Jane Doe');

2) пользователь загружает видео.Он еще никого не зачисляет, в том числе и себя.

insert into videos_video (id, title, uploaded_by_id) values (1, 'Hamlet', 1);

Результат должен быть следующим:

**User**     **Uploads**  **Credited_by_others**  **Total_credits**
John Smith       1                0                      1
Jane Doe         0                0                      0

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

insert into videos_videocredit (id, video_id, profile_id, position) values (1, 1, 1, 'director')

Теперь результат должен быть следующим:

**User**     **Uploads**  **Credited_by_others**  **Total_credits**
John Smith       1                0                      1
Jane Doe         0                0                      0

4) Теперь пользователь зачисляется на себя еще два раза в одном и том же видео (т. Е. У него было несколько «позиций» в видео).Кроме того, он зачисляет Джейн Доу три раза за это видео:

insert into videos_videocredit (id, video_id, profile_id, position) values (2, 1, 1, 'writer')
insert into videos_videocredit (id, video_id, profile_id, position) values (3, 1, 1, 'producer')
insert into videos_videocredit (id, video_id, profile_id, position) values (4, 1, 2, 'director')
insert into videos_videocredit (id, video_id, profile_id, position) values (5, 1, 2, 'editor')
insert into videos_videocredit (id, video_id, profile_id, position) values (6, 1, 2, 'decorator')

Результат теперь должен быть следующим:

**User**     **Uploads**  **Credited_by_others**  **Total_credits**
John Smith       1                0                      1
Jane Doe         0                1                      1

5) Теперь Джейн Доу загружает видео.Она не доверяет себе, но дважды отмечает Джона Смита в видео:

insert into videos_video (id, title, uploaded_by_id) values (2, 'Othello', 2)
insert into videos_videocredit (id, video_id, profile_id, position) values (7, 2, 1, 'writer')
insert into videos_videocredit (id, video_id, profile_id, position) values (8, 2, 1, 'producer')

Результат теперь должен быть следующим:

**User**     **Uploads**  **Credited_by_others**  **Total_credits**
John Smith       1                1                      2
Jane Doe         1                1                      2

Итак, я хотел бы найти эти триполя для каждого пользователя - Uploads, Credited_by_others и Total_credits.Данные никогда не должны быть нулевыми, а вместо этого равны 0, если поле не имеет значения.Спасибо.

Ответы [ 3 ]

1 голос
/ 23 июля 2011

Я переписал запрос с помощью объединений, чтобы оптимизировать сервер стало проще.

Первые два представления для упрощения запроса

CREATE VIEW IF NOT EXISTS vperson_videos AS
    SELECT
        v.uploaded_by_id AS id,
        COUNT(*) AS uploads
    FROM vvideo v
    GROUP BY v.uploaded_by_id;

Вышеуказанное представление просто подсчитывает количество видео, которые были загружены пользователем.

CREATE VIEW vperson_credits AS
    SELECT
        c.profile_id AS id,
        COUNT(DISTINCT c.video_id) AS credits
    FROM vcredit c
    INNER JOIN vvideo cv ON cv.id = c.video_id
    WHERE cv.uploaded_by_id <> c.profile_id
    GROUP BY c.profile_id;

Вышеуказанное представление подсчитывает количество (отдельных) видео, которые были зачислены пользователю, но игнорирует те, которые пользователь загрузил сам.

Тогда сам запрос:

SELECT
    p.id,
    p.full_name,
    IFNULL(pv.uploads,0) AS uploads,
    IFNULL(pc.credits,0) AS credits,
    IFNULL(pv.uploads,0) + IFNULL(pc.credits,0) AS total_credits
FROM vperson p
LEFT OUTER JOIN vperson_videos pv ON pv.id = p.id
LEFT OUTER JOIN vperson_credits pc ON pc.id = p.id;

Я использовал LEFT OUTER JOIN, чтобы включить тех пользователей, которые не загрузили ни одно видео или не были зачислены ни в какое. IFNULL() было необходимо, потому что я получил бы NULL вместо 0.

Окончательный результат:

+----+------------+---------+---------+---------------+
| id | full_name  | uploads | credits | total_credits |
+----+------------+---------+---------+---------------+
|  1 | John Smith |       1 |       1 |             2 | 
|  2 | Jane Doe   |       1 |       1 |             2 | 
+----+------------+---------+---------+---------------+
1 голос
/ 23 июля 2011

Во-первых, я думаю, что у вас есть пара ошибок в описании вашей проблемы.

  • На шаге 5 вы описываете, как Джейн дважды упоминала Джона в видео 2. Я думаю, что вы только что получилистолбцы неправильно упорядочены в предложении значений.Это должно быть:

    insert into videos_videocredit (id, video_id, profile_id, position) values (7, 2, 1, 'writer');
    insert into videos_videocredit (id, video_id, profile_id, position) values (8, 2, 1, 'producer');
    
  • Ваши результаты должны показать, что Джон засчитан в 2 видео, а Джейн - в 1 видео.

    +------------+---------+--------------------+---------------+
    | full_name  | Uploads | Credited_by_others | Total_credits |
    +------------+---------+--------------------+---------------+
    | John Smith |       1 |                  1 |             2 | 
    | Jane Doe   |       1 |                  1 |             1 | 
    +------------+---------+--------------------+---------------+
    

Я проверил следующий запрос на MySQL 5.1.57, и он дал вышеуказанный результат.

SELECT
  u.full_name,
  COUNT(DISTINCT myvideos.id) AS Uploads,
  COUNT(DISTINCT byothers.id) AS Credited_by_others,
  COUNT(DISTINCT credited.id) AS Total_credits
FROM userprofile_userprofile AS u
LEFT OUTER JOIN videos_video AS myvideos ON myvideos.uploaded_by_id = u.id
LEFT OUTER JOIN (
  videos_videocredit AS c USE INDEX (videocredit_profileid_videoid)
  INNER JOIN videos_video AS credited
    ON c.video_id = credited.id
) ON c.profile_id = u.id
LEFT OUTER JOIN videos_video AS byothers USE INDEX (video_up_id)
  ON c.video_id = byothers.id
  AND byothers.uploaded_by_id <> u.id
GROUP BY u.id

Я создал несколько дополнительных индексов и дал подсказки запроса для их использования.

CREATE INDEX video_up_id ON videos_video (id,uploaded_by_id);

CREATE INDEX videocredit_profileid_videoid ON videos_videocredit (profile_id,video_id);

Это обеспечивает доступ ко всем таблицам (кроме userprofile) в режиме Using index , что означает, что он может удовлетворить запрос, читая только B-деревья индекса, без необходимости чтения данных таблицы.Вот отчет EXPLAIN:

*************************** 1. row ***************************
           id: 1
  select_type: SIMPLE
        table: u
         type: index
possible_keys: NULL
          key: PRIMARY
      key_len: 4
          ref: NULL
         rows: 2
        Extra: 
*************************** 2. row ***************************
           id: 1
  select_type: SIMPLE
        table: myvideos
         type: ref
possible_keys: userprofile_video_e43a31e7
          key: userprofile_video_e43a31e7
      key_len: 4
          ref: test.u.id
         rows: 1
        Extra: Using index
*************************** 3. row ***************************
           id: 1
  select_type: SIMPLE
        table: c
         type: ref
possible_keys: videocredit_profileid_videoid
          key: videocredit_profileid_videoid
      key_len: 5
          ref: test.u.id
         rows: 1
        Extra: Using index
*************************** 4. row ***************************
           id: 1
  select_type: SIMPLE
        table: credited
         type: eq_ref
possible_keys: PRIMARY,video_up_id
          key: PRIMARY
      key_len: 4
          ref: test.c.video_id
         rows: 1
        Extra: Using index
*************************** 5. row ***************************
           id: 1
  select_type: SIMPLE
        table: byothers
         type: ref
possible_keys: video_up_id
          key: video_up_id
      key_len: 4
          ref: test.c.video_id
         rows: 1
        Extra: Using index
5 rows in set (0.00 sec)

Оптимизация может давать переменные отчеты при тестировании на тривиальное количество строк.Таким образом, мы можем увидеть разные результаты при тестировании с реальным набором данных, и тогда может оказаться ненужным давать подсказки USE INDEX.


Однако, несмотря на вышеупомянутое решение, я надеюсь,выполнять каждую задачу в отдельном запросе.Выполнение всего в одном запросе сложно разработать и протестировать, а для выполнения СУБД часто приходится обходиться дорого.Это будет еще сложнее, если вам нужно добавить еще один счет.

SELECT
  u.full_name,
  COUNT(DISTINCT myvideos.id) AS Uploads
FROM userprofile_userprofile AS u
LEFT OUTER JOIN videos_video AS myvideos ON myvideos.uploaded_by_id = u.id
GROUP BY u.id;

SELECT
  u.full_name,
  COUNT(DISTINCT byothers.id) AS Credited_by_others
FROM userprofile_userprofile AS u
LEFT OUTER JOIN videos_videocredit AS c
  USE INDEX (videocredit_profileid_videoid)
  ON c.profile_id = u.id
LEFT OUTER JOIN videos_video AS byothers
  USE INDEX (video_up_id)
  ON c.video_id = byothers.id AND byothers.uploaded_by_id <> u.id
GROUP BY u.id;

SELECT
  u.full_name,
  COUNT(DISTINCT credited.id) AS Total_credits
FROM userprofile_userprofile AS u
LEFT OUTER JOIN (
  videos_videocredit AS c
  USE INDEX (videocredit_profileid_videoid)
  INNER JOIN videos_video AS credited
    ON c.video_id = credited.id
) ON c.profile_id = u.id
GROUP BY u.id;
1 голос
/ 23 июля 2011

Общая сумма кредита - это просто сумма в виде кредита при загрузке и иностранного кредита. Так как кредит на загрузку легко, вот только иностранный кредит. Задержите дыхание для двойного подзапроса.

SELECT profile_id, COUNT(video_id) AS foreign_credit
       FROM (SELECT DISTINCT profile_id, video_id FROM videos_videocredit
             WHERE (profile_id, video_id) NOT IN (SELECT uploaded_by_id, id FROM videos_video)) AS crsq
GROUP BY profile_id;

Это становится более ощутимым при взгляде. Мы создаем представление, которое выбирает только пары (profile_id, video_id) людей, которым зачислены видео, которые они сами не загружали. Давайте назовем представление vfcredits.

CREATE VIEW vfcredits AS
  SELECT DISTINCT profile_id, video_id FROM videos_credit
  WHERE (profile_id, video_id) NOT IN (SELECT uploaded_by_id, id FROM videos_video);

Теперь мы можем с радостью вставить это в основной запрос, который объединяет иностранные кредиты:

SELECT profile_id, COUNT(video_id) AS foreign_credit
FROM vfcredits
GROUP BY profile_id;

Теперь давайте все вместе. Мы делаем еще два просмотра, один для подсчета собственных кредитов и один для подсчета иностранных кредитов:

CREATE VIEW vowncount AS
  SELECT uploaded_by_id AS profile_id, COUNT(*) AS own_credits
  FROM videos_video
  GROUP BY uploaded_by_id;

CREATE VIEW vforeigncount AS
  SELECT profile_id, COUNT(video_id) AS foreign_credits
  FROM vfcredits
  GROUP BY profile_id;

Наконец, полный выбор:

SELECT name,
       own_credits,
       foreign_credits,
       own_credits + foreign_credits AS total_credits
FROM userprofile_userprofile
JOIN vowncount ON(userprofile_userprofile.id = vowncount.profile_id)
JOIN vforeigncount ON(userprofile_userprofile.id = vforeigncount.profile_id);
...