Логические операции над результатами mysql

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

[block_value]

  • id_block_value
  • file_id

[метаданные]

  • id_metadata
  • имя_метаданных

[metadata_value]

  • мета_ид
  • стоимость
  • blockvalue_id

В этих таблицах есть пары: metadata_name = value И список пар заключен в блоки (id_block_value)

(A) Если мне нужна высота = 1080:

SELECT DISTINCT file_id 
FROM metadata_value MV 
     INNER JOIN metadata M ON MV.meta_id = M.id_metadata 
     INNER JOIN block_value BV ON MV.blockvalue_id = BV.id_block_value 
WHERE (metadata_name = "height" and value = "1080");

+---------+
| file_id |
+---------+
|      21 | 
|      22 |
(...)
|    6962 |
(...)
|    8146 | 
|    8147 | 
+---------+
794 rows in set (0.06 sec)

(B) Если мне нужно расширение файла = mpeg:

SELECT DISTINCT file_id 
FROM metadata_value MV 
     INNER JOIN metadata M ON MV.meta_id = M.id_metadata 
     INNER JOIN block_value BV ON MV.blockvalue_id = BV.id_block_value 
WHERE (metadata_name = "file extension" and value = "mpeg");

+---------+
| file_id |
+---------+
|    6889 | 
|    6898 | 
|    6962 | 
+---------+
3 rows in set (0.06 sec)

НО, если я хочу:

  • А и Б
  • A or B
  • А а не Б

Тогда я не знаю, что лучше.

Для A or B я попробовал A union B, который, кажется, помогает.

SELECT DISTINCT file_id 
FROM metadata_value MV 
     INNER JOIN metadata M ON MV.meta_id = M.id_metadata 
     INNER JOIN block_value BV ON MV.blockvalue_id = BV.id_block_value 
WHERE (metadata_name = "height" and value = "1080")
UNION
SELECT DISTINCT file_id 
FROM metadata_value MV 
     INNER JOIN metadata M ON MV.meta_id = M.id_metadata 
     INNER JOIN block_value BV ON MV.blockvalue_id = BV.id_block_value 
WHERE (metadata_name = "file extension" and value = "mpeg");
+---------+
| file_id |
+---------+
|      21 | 
|      22 | 
|      34 |
(...)
|    6889 | 
|    6898 | 
+---------+
796 rows in set (0.13 sec)

Для A and B, поскольку в Mysql нет intersect, я попробовал A and file_id in(B), но посмотрите на производительность (> 4mn)...

SELECT DISTINCT file_id 
FROM metadata_value MV 
     INNER JOIN metadata M ON MV.meta_id = M.id_metadata 
     INNER JOIN block_value BV ON MV.blockvalue_id = BV.id_block_value 
WHERE (metadata_name = "height" and value = "1080")
and file_id in(
SELECT DISTINCT file_id 
FROM metadata_value MV 
     INNER JOIN metadata M ON MV.meta_id = M.id_metadata 
     INNER JOIN block_value BV ON MV.blockvalue_id = BV.id_block_value 
WHERE (metadata_name = "file extension" and value = "mpeg"));

+---------+
| file_id |
+---------+
|    6962 | 
+---------+
1 row in set (4 min 36.22 sec)

Я тоже пробовал B and file_id in(A), он намного лучше, но я никогда не узнаю, какой из них поставить первым.

SELECT DISTINCT file_id 
FROM metadata_value MV 
     INNER JOIN metadata M ON MV.meta_id = M.id_metadata 
     INNER JOIN block_value BV ON MV.blockvalue_id = BV.id_block_value 
WHERE (metadata_name = "file extension" and value = "mpeg")
and file_id in(
SELECT DISTINCT file_id 
FROM metadata_value MV 
     INNER JOIN metadata M ON MV.meta_id = M.id_metadata 
     INNER JOIN block_value BV ON MV.blockvalue_id = BV.id_block_value 
WHERE (metadata_name = "height" and value = "1080"));

+---------+
| file_id |
+---------+
|    6962 | 
+---------+
1 row in set (0.75 sec)

Итак... что мне теперь делать? Есть ли лучший способ для логических операций? Любой совет? Я что-то пропустил?

EDIT: как выглядят данные:

Эта база данных содержит строку в таблице FILE для каждого вставленного аудио/видеофайла:

  • 10, /путь/к/файлу.тс
  • 11, /путь/к/file2.mpeg

Для каждой потенциальной информации есть строка в таблице METADATA:

  • 301, высота
  • 302, расширение файла

Затем строка в таблице BLOCK определяет контейнер:

  • 101, Видео
  • 102, Аудио
  • 104, Генерал

Файл может иметь несколько блоков метаданных, таблица BLOCK_VALUE содержит экземпляры БЛОКОВ:

  • 402, 101, 10 // Видео 1
  • 403, 101, 10 // Видео 2
  • 404, 101, 10 // Видео 3
  • 405, 102, 10 // Аудио
  • 406, 104, 10 // Общие

В этом примере файл 10 имеет 5 блоков: 3 видео (101) + 1 аудио (102) + 1 общий (104)

Значения хранятся в METADATA_VALUE

  • 302, 406, "ts" // расширение файла, общее
  • 301, 402, "1080" // высота, Видео 1
  • 301, 403, "720" // высота, Видео 2
  • 301, 404, "352" // высота, Видео 3

person Cyrille    schedule 13.05.2013    source источник


Ответы (3)


Я открываю новый пост только для того, чтобы сохранить «правильное» решение в чистоте.

Хорошо, извините, похоже, я сделал неправильное предположение. Я никогда не думал о том, что два блока определяются одинаково.

Итак, поскольку я подражатель, и мне нравится получать решение И из ИЛИ (:P), я добрался до этих двух решений.

ОРинг: Мне больше нравится решение Криса...

SELECT DISTINCT file_id 
  FROM metadata_value MV 
    INNER JOIN metadata M ON MV.meta_id = M.id_metadata 
    INNER JOIN block_value BV ON MV.blockvalue_id = BV.id_block_value 
   WHERE (metadata_name = "height" and value = "1080") 
      OR (metadata_name = "file extension" and value = "mpeg")

ANDing: Я буду использовать вашу версию ORing (та, где UNION all

  SELECT FILE_ID FROM (
     SELECT DISTINCT 1, file_id 
             FROM metadata_value MV 
       INNER JOIN metadata M ON MV.meta_id = M.id_metadata 
       INNER JOIN block_value BV ON MV.blockvalue_id = BV.id_block_value 
              WHERE (metadata_name = "height" and value = "1080")
     UNION ALL
     SELECT DISTINCT 2, file_id 
             FROM metadata_value MV 
       INNER JOIN metadata M ON MV.meta_id = M.id_metadata 
       INNER JOIN block_value BV ON MV.blockvalue_id = BV.id_block_value 
              WHERE (metadata_name = "file extension" and value = "mpeg")
   ) IHATEAND
   GROUP BY FILE_ID
   HAVING COUNT(1)>1

Который дает:

+---------+
| FILE_ID |
+---------+
|    6962 |
+---------+
1 row in set (0.24 sec)

это должно быть немного менее быстро, чем ORing, видя производительность, которую вы вставили и добыли (я в 3 раза медленнее, время для обновления -.-), но все же значительно быстрее, чем предыдущие запросы;)

В любом случае, как работает AND? Проще говоря, он просто выполняет два отдельных запроса и называет записи в соответствии с веткой, из которой они исходят, а затем подсчитывает различные идентификаторы файлов, исходящие от них.

ОБНОВЛЕНИЕ: другой способ сделать это без необходимости "называть" ветки:

SELECT FILE_ID FROM (
    SELECT file_id 
        FROM metadata_value MV 
        INNER JOIN metadata M ON MV.meta_id = M.id_metadata 
        INNER JOIN block_value BV ON MV.blockvalue_id = BV.id_block_value 
            WHERE (metadata_name = "height" and value = "1080")
    GROUP BY FILE_ID
    UNION ALL
    SELECT file_id 
        FROM metadata_value MV 
        INNER JOIN metadata M ON MV.meta_id = M.id_metadata 
        INNER JOIN block_value BV ON MV.blockvalue_id = BV.id_block_value 
    WHERE (metadata_name = "file extension" and value = "mpeg")
    GROUP BY FILE_ID
    ) IHATEAND
GROUP BY FILE_ID
HAVING COUNT(1)>1

Здесь результаты одинаковы (и производительность тоже), и я использую тот факт, что в то время как UNION автоматически сортирует дубликаты и удаляет дубликаты, UNION ALL этого не делает... что идеально, так как я не хочу, чтобы они удалялись ( и вообще union all тоже быстрее, чем union :) ), так я могу забыть об именовании.

person Davide    schedule 14.05.2013
comment
Впечатляющий. Хотел бы я дать вам больше очков репутации! Огромное спасибо за ваше время и ваши навыки (и спасибо ChrisCamp, который тоже старался) - person Cyrille; 15.05.2013

Для «ИЛИ», почему бы не попробовать без СОЮЗА... я что-то упустил?

SELECT DISTINCT file_id 
FROM metadata_value MV 
     INNER JOIN metadata M ON MV.meta_id = M.id_metadata 
     INNER JOIN block_value BV ON MV.blockvalue_id = BV.id_block_value 
WHERE (metadata_name = "height" and value = "1080") 
OR (metadata_name = "file extension" and value = "mpeg")

Для «И» дважды используйте внутреннее соединение в таблице метаданных, чтобы получить только те идентификаторы файлов, которые соответствуют обоим условиям...

SELECT DISTINCT file_id 
FROM metadata_value MV 
     INNER JOIN metadata M ON MV.meta_id = M.id_metadata 
     AND (M.metadata_name = "height" and MV.value = "1080")
     INNER JOIN metadata M2 ON MV.meta_id = M2.id_metadata
     AND (M2.metadata_name = "file extension" and MV.value = "mpeg")
     INNER JOIN block_value BV ON MV.blockvalue_id = BV.id_block_value 

"A", а не "B", используйте левое соединение, а не внутреннее соединение для условия "B". Добавьте предложение WHERE, указывающее, что вы не ожидаете результатов для «B».

SELECT DISTINCT file_id 
FROM metadata_value MV 
     INNER JOIN metadata M ON MV.meta_id = M.id_metadata 
     AND (M.metadata_name = "height" and MV.value = "1080") 
     LEFT JOIN metadata M2 ON MV.meta_id = M2.id_metadata
     AND (M2.metadata_name = "file extension" and MV.value = "mpeg")
     INNER JOIN block_value BV ON MV.blockvalue_id = BV.id_block_value 
WHERE M2.id_metadata is NULL
person ChrisCamp    schedule 13.05.2013
comment
metadata_name одновременно имеет высоту и расширение файла? (кстати, вы пробовали использовать во втором блоке и существует ли (выберите 1... вместо идентификатора файла и в (выберите отдельный...)? - person Davide; 13.05.2013
comment
Действительно, 2 metadata_name в одном запросе возвращают пустой набор (но вы правы для OR: UNION здесь не нужен). Я попробую exists, как только разберусь с правильным синтаксисом. - person Cyrille; 13.05.2013
comment
Хороший вопрос, Давиде, как насчет двойного соединения в таблице метаданных, указывающего, что существуют условия A и B? Изменил мой ответ... - person ChrisCamp; 13.05.2013
comment
ОШИБКА 1054 (42S22): Неизвестный столбец «M.value» в «предложении on»: я только что заменил M и M2 на MV и получил 794 строки. - person Cyrille; 13.05.2013
comment
Ах, простите за мою ошибку. Вы имели в виду BV для псевдонима, а не MV? я так понимаю, 794 строки все еще неправильно... - person ChrisCamp; 13.05.2013
comment
К сожалению, моя ошибка тоже, value из metadata_value, я собираюсь отредактировать свой вопрос. - person Cyrille; 13.05.2013
comment
Не беспокойтесь, я изменил свой ответ обратно с правильным псевдонимом MV. Приближает ли это вас к решению? - person ChrisCamp; 13.05.2013
comment
К сожалению, у меня все еще есть 794 строки. - person Cyrille; 13.05.2013
comment
Есть ли какие-либо образцы данных, которые вы могли бы предоставить в своем вопросе? - person ChrisCamp; 13.05.2013
comment
Если у вас еще есть свободное время, вот дамп отладки: docs.google.com/file/d/0Bwul1yIuuF1SQUhxY0d1WmUyYjQ/ - person Cyrille; 13.05.2013

ИЛИ версия: (бесстыдное копирование и вставка из ответа ChrisCamp)

 SELECT distinct file_id 
   FROM metadata_value MV 
      INNER JOIN metadata M ON MV.meta_id = M.id_metadata 
      INNER JOIN block_value BV ON MV.blockvalue_id = BV.id_block_value 
WHERE (metadata_name = "height" and value = "1080") 
   OR (metadata_name = "file extension" and value = "mpeg") 

И Версия:

SELECT file_id 
  FROM metadata_value MV 
   INNER JOIN metadata M ON MV.meta_id = M.id_metadata 
   INNER JOIN block_value BV ON MV.blockvalue_id = BV.id_block_value 
   WHERE (metadata_name = "height" and value = "1080") 
      OR (metadata_name = "file extension" and value = "mpeg") 
  group by file_id having count(1)>1

2 кратких примечания о версии AND:

На самом деле это способ определить это пересечение с точки зрения предыдущего ИЛИ.

Когда ANDind у вас есть 3 возможности:

  • ни одно из запрошенных условий не выполнено (в операции ИЛИ оно не появится)
  • выполняется только одно из них (в операции ИЛИ оно появится один раз)
  • оба удовлетворены (в операции ИЛИ оно появится дважды, если не указано отличное).

Поэтому я просто удалил отдельное предложение, поместил группу и выбрал записи, присутствующие дважды.

Или просто продолжайте использовать предложение exists :)


Отредактируйте следующие комментарии:

окей, стараюсь не усложнять... id_block_values ​​удовлетворяет одному из двух условий:

SELECT BLOCK_VALUE_ID
   FROM METADATA_VALUE MV
     INNER JOIN 
        METADATA M
     ON MV.META_ID=M.METADATA_ID
  WHERE (METADATA_NAME='height' AND VALUE='1080')
     OR (METADATA_NAME='file extension' AND VALUE='mpeg')

если у вас здесь более 2 записей, у вас проблема (дублирование метаданных).

Теперь И

SELECT FILE_ID
  FROM BLOCK_VALUE BV
    INNER JOIN   
      (   SELECT BLOCK_VALUE_ID
            FROM METADATA_VALUE MV
            INNER JOIN 
                 METADATA M
              ON MV.META_ID=M.METADATA_ID
           WHERE (METADATA_NAME='height' AND VALUE='1080')
              OR (METADATA_NAME='file extension' AND VALUE='mpeg')
      ) X
  ON BV.ID_BLOCK_VALUE=X.BLOCK_VALUE_ID
 GROUP BY FILE_ID HAVING COUNT(1)>1

Тем не менее, я не могу понять, почему предыдущий запрос не работал. Я боюсь, что если вы удалите предложение DIstinct в запросе или, вы увидите некоторые записи более двух раз, что не имеет смысла. Кстати, просто чтобы быть уверенным, не могли бы вы сказать мне, каковы первичные ключи таблиц?

person Davide    schedule 13.05.2013
comment
Что ж, с версией AND я получаю: 12 строк в наборе (0,00 сек). Быстрый! Но набор неправильный (хотя он содержит 6962). - person Cyrille; 13.05.2013
comment
(file_id | count(1)) : (148 | 3) (154 | 3) (155 | 4) (169 | 6) (317 | 2 ) (332 | 3) (333 | 3) ( 486 | 4) ( 487 | 4) (490 | 4) (525 | 3) (6962 | 2) - person Cyrille; 13.05.2013
comment
Хорошо, спасибо за информацию, я обновил свой ответ. Вы уверены, что у вас нет дублирования метаданных, верно? - person Davide; 14.05.2013
comment
По-прежнему 12 строк вместо 1. Ну и метаданные дублируются (например, file_id может иметь несколько значений метаданных). Я обновил свой вопрос и добавил пример в конце. - person Cyrille; 14.05.2013
comment
мммм... это странно... не могли бы вы запустить этот запрос, чтобы я увидел, где происходит дублирование? SELECT * FROM BLOCK_VALUE BV INNER JOIN ( SELECT BLOCK_VALUE_ID FROM METADATA_VALUE MV INNER JOIN METADATA M ON MV.META_ID=M.METADATA_ID WHERE (METADATA_NAME='height' AND VALUE='1080') OR (METADATA_NAME='file extension' AND VALUE= 'mpeg') ) X ON BV.ID_BLOCK_VALUE=X.BLOCK_VALUE_ID AND FILE_ID=169 - person Davide; 14.05.2013
comment
Конечно: (id_block_value | block_id | file_id | blockvalue_id): (2228 | 10 | 169 | 2228), (2240 ​​| 10 | 169 | 2240), (2244 | 10 | 169 | 2244), (2255 | 10 | 169 | 2255 ), (2259 | 10 | 169 | 2259), (2271 | 10 | 169 | 2271). В этом файле есть несколько видеоблоков, каждый из которых содержит метаданные высоты. - person Cyrille; 14.05.2013
comment
правильно ли предположить, что эти блоки имеют одинаковое значение высоты? - person Davide; 14.05.2013
comment
Нет, каждый блок видео может иметь разные значения высоты. И вообще говоря, каждый блок может иметь разный набор метаданных (в одном блоке видео могут отсутствовать метаданные о высоте, а высота может быть найдена в другом блоке, например, в изображении). - person Cyrille; 14.05.2013
comment
Что я могу придумать, так это то, что для одного и того же файла у вас есть два разных блока (например, изображение и видео) (таблица BLOCK отсутствует в запросе) с одинаковой высотой... Последний запрос, я обещаю... SELECT * FROM METADATA_VALUE MV INNER JOIN BLOCK_VALUE BV ON BV.ID_BLOCK_VALUE=MV.BLOCK_VALUE_ID INNER JOIN METADATA M ON MV.META_ID=M.METADATA_ID WHERE ((METADATA_NAME='height' AND VALUE='1080') OR (METADATA_NAME='file extension' AND VALUE=' mpeg')) И BLOCK_VALUE_ID IN (2228,2240,2244,2255,2259,2271) и file_id=169 - person Davide; 14.05.2013
comment
6 строк в наборе: (meta_id | blockvalue_id | value | id_block_value | block_id | file_id | id_metadata | metadata_name) = (36 | ‹blockvalue_id› | 1080 | ‹id_block_value› | 10 | 169 | 36 | высота) с ‹blockvalue_id› = ‹ id_block_value› = (2228, 2240, 2244, 2255, 2259, 2271). И вы можете отправить столько запросов, сколько хотите. Я очень благодарен, что вы и ChrisCamp помогаете мне! если вы предпочитаете дамп mysql, вы можете найти его здесь - person Cyrille; 14.05.2013