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


171

Як би ви створили базу даних для підтримки таких функцій тегу:

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

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

Будь-які ідеї?


Дякую за всі відповіді поки що.

Якщо я не помиляюся, наведені відповіді показують, як зробити АБО-пошук за тегами. (Виберіть усі елементи, які містять один або більше n тегів). Я шукаю ефективний ІН-пошук. (Виберіть усі елементи, на яких є ВСЕ n тегів - і можливо більше.)

Відповіді:


22

Про ANDing: Це здається, що ви шукаєте операцію "реляційного поділу". Ця стаття висвітлює реляційний поділ у стислій, але зрозумілий спосіб.

Щодо продуктивності: підхід на основі растрових зображень інтуїтивно звучить так, ніби він добре відповідає ситуації. Однак я не переконаний, що це гарна ідея реалізувати растрову індексацію "вручну", як пропонує digiguru: Це звучить як складна ситуація, коли додаються нові теги (?), Але деякі СУБД (включаючи Oracle) пропонують растрові індекси, які можуть якось бути корисними, оскільки вбудована система індексації не усуває потенційної складності обслуговування індексу; крім того, СУБД, що пропонує растрові індекси, повинна мати можливість вважати їх належними під час виконання плану запитів.


4
Я мушу сказати, що відповідь трохи недалекоглядний, оскільки використання типу бітового поля бази даних обмежує вас певним числом біт. Це не означає, що кожен елемент обмежений певною кількістю тегів, але те, що у всій системі може бути лише певна кількість унікальних тегів (зазвичай до 32 або 64).
Марк Renouf

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-дерева існують на середній таблиці
Адам Мюш

Посилання "Ця стаття" мертве. Я хотів би прочитати це :(
10.10

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
Barrett

Оригінальні посилання були мертвими, але я думаю, що я знайшов їх нове місцезнаходження. Ви можете переконатися, що це статті, на які ви посилалися.
Бред Ларсон

12
Незважаючи на те, що написав @Jeff, це все ще є лише відповіді на посилання.
цікаводанні

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 (наприклад, 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) орієнтоване на БД

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

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

Ви можете подумати, чи варто того додаткової складності.


0

Ви не зможете уникнути приєднання і все ще будете дещо нормалізовані.

Мій підхід - мати таблицю тегів.

 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;

Щоб І кучу тегів було разом, слід трохи змінити вищезазначений вислів, щоб додати AND Tags.TagName = @ TagName1 AND Tags.TagName = @ TagName2 тощо ... та динамічно будувати запит.


0

Мені подобається робити декілька таблиць, які представляють необроблені дані, тож у цьому випадку у вас є

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

Це працює швидко в час запису і зберігає все нормалізоване, але ви також можете зауважити, що для кожного тегу вам потрібно буде двічі приєднуватися до таблиць для кожного подальшого тегу, який ви хочете зробити І, тому його читають повільно.

Рішення для поліпшення читання полягає у створенні таблиці кешування за командою, встановивши збережену процедуру, яка по суті створює нову таблицю, яка представляє дані у вирівняному форматі ...

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

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

Тепер, щоб отримати дійсно розумний пошук даних, вам потрібно створити збережену процедуру для отримання даних з тегів. Замість того, щоб використовувати вкладені запити в масовому операторі регістру, ви хочете передати єдиний параметр, що містить список тегів, які ви хочете вибрати з бази даних, і повернути набір записів Елементів. Це найкраще в двійковому форматі, використовуючи побітові оператори.

У двійковому форматі це легко пояснити. Скажімо, для елемента можна присвоїти чотири теги, у двійковій формі ми могли б це представляти

0000

Якби всі чотири теги були присвоєні об'єкту, об’єкт виглядав би так ...

1111

Якби тільки перші два ...

1100

Тоді це лише випадок знаходження двійкових значень із знаками 1 та нулі у стовпці, який ви хочете. Використовуючи бітові оператори SQL Server, ви можете перевірити наявність 1 у першому стовпці, використовуючи дуже прості запити.

Перевірте це посилання, щоб дізнатися більше .


0

Якщо перефразовувати те, що сказали інші: хитрість не в схемі , це в запиті .

Наївна схема об'єктів / міток / тегів - це правильний шлях. Але, як ви бачили, не відразу зрозуміло, як виконати запит AND з великою кількістю тегів.

Найкращий спосіб оптимізації цього запиту буде залежати від платформи, тому я рекомендую повторно позначити своє запитання вашим RDBS та змінити заголовок на щось на кшталт "Оптимальний спосіб виконання І запиту в базі даних тегів".

У мене є кілька пропозицій щодо MS SQL, але я утримаюся, якщо це не платформа, яку ви використовуєте.


6
Ви, мабуть, не повинні утримуватися від привчання до певної технології, тому що інші люди, які намагаються працювати в цій проблемній області, насправді можуть використовувати цю технологію і отримали б користь.
Брайан Рейбейн

0

Варіантом вищевказаної відповіді є взяття ідентифікаторів тегів, сортування їх, об’єднання у вигляді розділених рядків ^ та хеш-них. Тоді просто прив’яжіть хеш до елемента. Кожна комбінація тегів створює новий ключ. Щоб виконати пошук AND, просто заново створіть хеш із заданими ідентифікаторами тегів та шукайте. Зміна тегів на елементі призведе до відтворення хешу. Елементи з однаковим набором тегів мають один і той же хеш-ключ.


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

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.