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


171

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

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

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

Любые идеи?


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

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

Ответы:


22

О ANDing: Похоже, вы ищете операцию «реляционного разделения». Эта статья охватывает реляционное деление в краткой и в то же время понятной форме.

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


4
Я должен сказать, что ответ немного недальновиден, потому что использование типа битового поля базы данных ограничивает вас определенным количеством битов. Это не означает, что каждый элемент ограничен определенным количеством тегов, но что во всей системе может быть только определенное количество уникальных тегов (обычно до 32 или 64).
Марк Ренуф

1
Предполагая реализацию 3nf (Question, Tag, Question_has_Tag) и индекс растрового изображения для Tag_id в Question_has_Tag, индекс растрового изображения должен перестраиваться каждый раз, когда в вопрос добавляется или удаляется тег. Подобный запрос select * from question q inner join question_has_tag qt where tag_id in (select tag_id from tags where (what we want) minus select tag_id from tags where (what we don't)должен быть в порядке и масштабироваться, если в средней таблице есть правильные индексы b-дерева
Адам Муш

Ссылка "Эта статья" мертва. Я хотел бы прочитать это :(
mpen

3
Марк: Это выглядит хорошо: simple-talk.com/sql/t-sql-programming/… Вероятно, это переизданная версия той, на которую я ссылался.
Троэльс Арвин

URL этой статьи больше не действителен
Себастьен Х.

77

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

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

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

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

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


1
Я также хотел бы получить более подробную техническую информацию о том, как вы внедрили систему тегирования с SO? Я думаю, что на подкасте вы сказали, что сохраняете все теги в столбце с каждым вопросом, а затем сериализуете / десериализуете их на лету? Я хотел бы узнать больше об этом и, возможно, увидеть некоторые фрагменты кода. Я искал вокруг и нашел какие-либо детали, есть ли ссылка, где вы уже сделали это, прежде чем я задам вопрос по META?
Марстон А.

5
Этот вопрос о Meta содержит некоторую информацию о схеме SO: meta.stackexchange.com/questions/1863/so-database-schema
Барретт

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

12
Несмотря на то, что написано @Jeff, это по-прежнему только ответ.
curiousdannii

13

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

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

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


13

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

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


3
Я помню, как читал эту замечательную статью, но, к сожалению, ссылка сейчас мертва. :( Кто-нибудь знает о зеркале этого?
localhost

5
ссылка была мертва: <
Аарон

6

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

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

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

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


5

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

1

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

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

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

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


0

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

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

 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 связать вместе несколько тегов, вы должны немного изменить вышеприведенный оператор, чтобы добавить AND Tags.TagName = @ TagName1 AND Tags.TagName = @ TagName2 и т. Д. ... и динамически построить запрос.


0

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

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, вы хотите передать один параметр, содержащий список тегов, которые вы хотите выбрать из базы данных, и вернуть набор записей Items. Это было бы лучше в двоичном формате, используя побитовые операторы.

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

0000

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

1111

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

1100

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

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


0

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

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

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

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


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

0

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


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

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.