Дизайн базы данных для маркировки

Как бы вы создали базу данных для поддержки следующих функций тегов:

  • элементы могут иметь большое количество тегов
  • поиск всех элементов, помеченных определенным набором тегов, должен быть быстрым (элементы должны иметь ВСЕ теги, поэтому это AND-поиск, а не OR-поиск)
  • Создание/запись элементов может быть медленнее для быстрого поиска/чтения

В идеале, поиск всех элементов, помеченных (по крайней мере) набором n заданных тегов, должен выполняться с использованием одного оператора SQL. Поскольку количество тегов для поиска, а также количество тегов для любого элемента неизвестны и могут быть высокими, использование JOIN непрактично.

Любые идеи?


Спасибо за все ответы.

Если я не ошибаюсь, эти ответы показывают, как выполнять OR-поиск по тегам. (Выберите все элементы, имеющие одну или несколько тегов n). Я ищу эффективный И-поиск. (Выберите все элементы, у которых есть ВСЕ теги n - и, возможно, больше.)

Ответ 1

О ANDing: похоже, что вы ищете операцию "реляционного деления". Эта статья охватывает реляционное деление сжатым и все же понятным образом.

О производительности: Растровый подход интуитивно звучит так, как будто он будет хорошо соответствовать ситуации. Тем не менее, я не убежден, что неплохо реализовать индексирование растрового изображения "вручную", как предлагает digiguru: это звучит как сложная ситуация при добавлении новых тегов (?) Но некоторые СУБД (включая Oracle) предлагают растровые индексы, которые могут как-то будут полезны, поскольку встроенная система индексирования устраняет потенциальную сложность обслуживания индекса; кроме того, СУБД, предлагающая индексы растровых изображений, должна иметь возможность правильно учитывать их при выполнении плана запроса.

Ответ 2

Вот хорошая статья о пометке схем базы данных:

http://howto.philippkeller.com/2005/04/24/Tags-Database-schemas/

и тесты производительности:

http://howto.philippkeller.com/2005/06/19/Tagsystems-performance-tests/

Обратите внимание, что сделанные там выводы очень специфичны для MySQL, которые (по крайней мере, в 2005 году в то время, когда они были написаны) имели очень слабые полнотекстовые индексирующие характеристики.

Ответ 3

Я не вижу проблемы с простым решением: Таблица для элементов, таблица для тегов, crosstable для "tagging"

Индексы на кросс-таблице должны быть достаточной для оптимизации. Выбор соответствующих элементов будет

SELECT * FROM items WHERE id IN  
    (SELECT DISTINCT item_id FROM item_tag WHERE  
    tag_id = tag1 OR tag_id = tag2 OR ...)  

И пометка будет

SELECT * FROM items WHERE  
    EXISTS (SELECT 1 FROM item_tag WHERE id = item_id AND tag_id = tag1)  
    AND EXISTS (SELECT 1 FROM item_tag WHERE id = item_id AND tag_id = tag2)  
    AND ...

что, по общему признанию, не столь эффективно для большого количества сравниваемых тегов. Если вы хотите сохранить количество меток в памяти, вы можете сделать запрос, чтобы начать с тегов, которые не часто, поэтому последовательность И будет оцениваться быстрее. В зависимости от ожидаемого количества тегов, которые должны быть сопоставлены и ожидаемого соответствия любому из них, это может быть ОК решение, если вы должны соответствовать 20 тегам и ожидать, что какой-то случайный элемент будет соответствовать 15 из них, тогда это все равно будет тяжелым в базе данных.

Ответ 4

Я просто хотел подчеркнуть, что статья, которую @Jeff Atwood ссылается на (http://howto.philippkeller.com/2005/04/24/Tags-Database-schemas/), очень тщательна (в ней обсуждаются достоинства из 3 различных схемных подходов) и имеет хорошее решение для запросов И, которые обычно будут работать лучше, чем то, что было упомянуто здесь до сих пор (т.е. для каждого термина не используется коррелированный подзапрос). Также в комментариях есть много хорошего.

ps - Подход, о котором все говорят здесь, упоминается как решение "Токси" в статье.

Ответ 5

Возможно, вам захочется поэкспериментировать с решением не-строго базы данных, например реализацией Java Content Repository (например, Apache Jackrabbit) и используйте поисковую систему, построенную поверх нее, например Apache Lucene.

Это решение с соответствующими механизмами кэширования, возможно, принесет лучшую производительность, чем самодельное решение.

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

РЕДАКТИРОВАТЬ: с вашим разъяснением представляется более целесообразным использовать JCR-подобное решение с поисковой системой. Это значительно упростит ваши программы в долгосрочной перспективе.

Ответ 6

Самый простой способ - создать таблицу тегов.
Target_Type - если вы помечаете несколько таблиц
Target - Ключ к записи, помеченной тегами Tag - текст тега

Запрос данных будет выглядеть примерно так:

Select distinct target from tags   
where tag in ([your list of tags to search for here])  
and target_type = [the table you're searching]

UPDATE
Исходя из вашего требования к условиям И, запрос выше превратился бы в что-то вроде этого

select target
from (
  select target, count(*) cnt 
  from tags   
  where tag in ([your list of tags to search for here])
    and target_type = [the table you're searching]
)
where cnt = [number of tags being searched]

Ответ 7

Я бы предпочла @@Zizzencs предложить вам что-то, что не полностью (R) DB-centric

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

Я реализовал системы тегов с использованием трех таблиц для представления отношений "многие ко многим" (Item Item Item), но, полагаю, вы будете иметь дело с тегами во многих местах, я могу сказать вам, что с помощью 3 таблицы, которые нужно манипулировать/запрашивать одновременно, все время определенно сделают ваш код более сложным.

Возможно, вам стоит подумать, стоит ли добавить дополнительную сложность.

Ответ 8

Вы не сможете избежать объединения и все еще быть в норме.

Мой подход состоит в том, чтобы иметь таблицу тегов.

 TagId (PK)| TagName (Indexed)

Затем у вас есть столбец TagXREFID в таблице ваших элементов.

Этот столбец TagXREFID является FK для третьей таблицы, я назову его TagXREF:

 TagXrefID | ItemID | TagId

Итак, для получения всех тегов для элемента будет что-то вроде:

SELECT Tags.TagId,Tags.TagName 
     FROM Tags,TagXref 
     WHERE TagXref.TagId = Tags.TagId 
         AND TagXref.ItemID = @ItemID

И чтобы получить все элементы для тега, я бы использовал что-то вроде этого:

SELECT * FROM Items, TagXref
     WHERE TagXref.TagId IN 
          ( SELECT Tags.TagId FROM Tags
                WHERE Tags.TagName = @TagName; )
     AND Items.ItemId = TagXref.ItemId;

К И вместе с кучей тэгов Вы бы немного изменили приведенный выше оператор, чтобы добавить AND Tags.TagName = @TagName1 AND Tags.TagName = @TagName2 и т.д.... и динамически построить запрос.

Ответ 9

Мне нравится делать несколько таблиц, которые представляют необработанные данные, поэтому в этом случае у вас будет

Items (ID pk, Name, <properties>)
Tags (ID pk, Name)
TagItems (TagID fk, ItemID fk)

Это работает быстро для времени записи и сохраняет все нормализовано, но вы также можете заметить, что для каждого тега вам нужно будет объединить таблицы дважды для каждого последующего тега, который вы хотите И, поэтому он медленно читается.

Решение для улучшения чтения состоит в создании таблицы кэширования в команде путем настройки хранимой процедуры, которая по существу создает новую таблицу, которая представляет данные в сплющенном формате...

CachedTagItems(ID, Name, <properties>, tag1, tag2, ... tagN)

Затем вы можете подумать о том, как часто обновлять таблицу Tagged Item, если она есть на каждой вставке, а затем вызвать хранимую процедуру в событии вставки курсора. Если это почасовая задача, затем настройте почасовую работу для ее запуска.

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

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

0000

Если всем четырем тегам присваивается объект, объект будет выглядеть так:

1111

Если только первые два...

1100

Тогда это всего лишь случай нахождения двоичных значений с 1s и нулями в нужном столбце. Используя SQL Server Побитовые операторы, вы можете проверить, что есть 1 в первом столбце, используя очень простые запросы.

Посмотрите эту ссылку, чтобы узнать подробнее.

Ответ 10

Перефразируя то, что говорили другие: трюк не находится в схеме, он в запросе.

Наивная схема Entities/Labels/Tags - правильный путь. Но, как вы видели, не сразу понятно, как выполнить запрос AND с большим количеством тегов.

Лучший способ оптимизировать этот запрос будет зависящим от платформы, поэтому я бы рекомендовал повторно пометить ваш вопрос с помощью RDBS и изменить заголовок на что-то вроде "Оптимальный способ выполнить запрос AND в базе данных тегов".

У меня есть несколько предложений для MS SQL, но воздержитесь в случае, если вы не используете платформу.

Ответ 11

Вариант вышеприведенного ответа - использовать идентификаторы тегов, сортировать их, объединять в виде отдельной строки и хешировать их. Затем просто привяжите хэш к элементу. Каждая комбинация тегов создает новый ключ. Чтобы выполнить поиск AND, просто заново создайте хэш с указанными идентификаторами тегов и поиском. Изменение тегов на элементе приведет к воссозданию хэша. Элементы с одним и тем же набором тегов используют один и тот же хэш-ключ.

Ответ 12

Если у вас есть тип массива, вы можете предварительно агрегировать необходимые данные. См. Этот ответ в отдельном потоке:

Какова полезность типа массива?