Дизайн базы данных для тегов

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

  • элементы могут иметь большое количество тегов
  • поиск всех элементов, помеченных заданным набором тегов, должен быть быстрым (элементы должны иметь все теги, поэтому это и-поиск, а не или-поиск)
  • создание / запись элементов может быть медленнее, чтобы включить быстрый поиск / чтение

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

какие идеи?


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

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

12 ответов


о ANDing: похоже, вы ищете операцию "реляционное разделение". в этой статье охватывает реляционное разделение в сжатом и все же понятном виде.

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


вот хорошая статья о тегировании схем базы данных:

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

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

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

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


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

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

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 из них, тогда это все равно будет тяжело для базы данных.


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

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


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

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

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

EDIT: с вашим разъяснением кажется более убедительным использовать JCR-подобное решение с поисковой системой. Это значительно упростит ваши программы в долгосрочной перспективе.


самый простой способ-создать теги таблица.
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]

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

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]

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

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

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

возможно, вы захотите рассмотреть, стоит ли добавлять сложность.


вы не сможете избежать соединений и все еще быть несколько нормализованы.

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

 TagId (PK)| TagName (Indexed)

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

этот столбец TagXREFID является FK для 3-й таблицы, я назову его 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;

в И куча тегов вместе, вы должны немного изменить приведенное выше утверждение, чтобы добавить и теги.TagName = @TagName1 и теги.У tagName = @TagName2 и т. д...и динамически построить запрос.


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

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

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

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

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

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

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

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

0000

если все четыре тега назначены объекту, Объект будет выглядеть следующим образом...

1111

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

1100

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

, проверьте эту ссылку, чтобы узнать больше.


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

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

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

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


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


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

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