Вопрос

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

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

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

Есть идеи?


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

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

Это было полезно?

Решение

О 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 ...

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

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

ps. Подход, о котором здесь все говорят, в статье называется решением «Токси».

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

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

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

РЕДАКТИРОВАТЬ:с вашими разъяснениями кажется более привлекательным использовать 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.

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

Раньше я реализовал системы тегов с использованием трех таблиц для представления отношений «многие ко многим» (Item Tags ItemTags), но я полагаю, что вы будете иметь дело с тегами во многих местах, я могу вам сказать, что с тремя таблицами придется манипулировать/запрашивать одновременно все время определенно сделает ваш код более сложным.

Возможно, вы захотите подумать, стоит ли того дополнительная сложность.

Вы не сможете избежать объединений и при этом оставаться в некоторой степени нормализованными.

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

 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 и т. д. и динамически построить запрос.

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

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

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

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

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

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

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

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

0000

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

1111

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

1100

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

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

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

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

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

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

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

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

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

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top