Рекомендуемый дизайн базы данных SQL для тегов или тегов

Я слышал о нескольких способах реализации тегов; использование таблицы сопоставления между TagID и ItemID (имеет смысл для меня, но масштабируется ли она?), добавление фиксированного количества возможных столбцов TagID в ItemID (кажется плохой идеей), сохранение тегов в текстовом столбце, разделенном запятыми (звучит сумасшедший но мог работать). Я даже слышал, как кто-то рекомендовал разреженную матрицу, но как же тогда имена тегов изящно растут?

Мне не хватает лучших практик для тегов?


person dlamblin    schedule 21.08.2008    source источник
comment
Хорошо, это вопрос №20856, (почти) тот же вопрос №48475 задают по крайней мере через две недели после того, как этот вопрос был задан.   -  person dlamblin    schedule 07.10.2008
comment
Еще один интересный вопрос: как SO реализует теги?   -  person Mostafa    schedule 28.11.2011
comment
Другой интересный вопрос: а если да, то как?   -  person DanMan    schedule 03.12.2013
comment
Интересное сравнение (специфично для Postgres): databasesoup.com/2015/01/tag -all-things.html   -  person a_horse_with_no_name    schedule 11.05.2015
comment
см. также stackoverflow.com/questions/48475/database-design-for- теги   -  person Ian Ringrose    schedule 22.12.2015
comment
если вы используете РСУБД, вы должны использовать технику второстепенных-основных тегов, иначе она не масштабируется, когда пользователи фактически будут использовать теги.   -  person Stefanos Zilellis    schedule 18.06.2021


Ответы (6)


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

Table: Item
Columns: ItemID, Title, Content

Table: Tag
Columns: TagID, Title

Table: ItemTag
Columns: ItemID, TagID
person Yaakov Ellis    schedule 21.08.2008
comment
Это решение известно как «Toxi», дополнительную информацию о нем можно найти здесь: howto.philippkeller.com/2005/04/24/Tags-Database-schemas - person The Pixel Developer; 28.06.2009
comment
Здесь не показано иерархических тегов или категорий в таблице тегов. Это обычно необходимо на сайтах, которые имеют категории и подкатегории, но нуждаются в гибкости тегов. Например, сайты рецептов, сайты автозапчастей, бизнес-каталоги и т. Д. Эти типы данных обычно не вписываются только в одну категорию, поэтому тегирование - это ответ, но вам нужно использовать что-то вроде модели вложенного набора или модели списка смежности. в вашей таблице тегов. - person HK1; 21.01.2011
comment
Я согласен с HK1, возможно ли это с приведенной выше структурой + Таблица: Столбцы TagGroup: TagGropuId, Таблица заголовков: Столбцы тегов: TagID, Title, TagGroupId - person Thunder; 11.02.2011
comment
когда я хочу добавить столбец css в таблицу, я добавлю столбец css в таблицу тегов? - person Amitābha; 11.08.2015
comment
Я думал, что для таблицы ItemTag нужен идентификатор столбца ItemTagID. Я ошибся? я что-то пропускаю? - person Nrc; 21.11.2016
comment
Я думаю, что тег таблицы должен иметь только заголовок как PK, наличие отдельного идентификатора в качестве первичного ключа, особенно с автоматическим увеличением, приведет к тому, что тег таблицы будет заполнен повторяющимися значениями при вставке / обновлении (в отношении ManyToMany). - person tutak; 03.01.2017
comment
@ftvs: ссылка снова не работает, новая ссылка - howto.philippkeller. ru / 2005/04/24 / Теги-базы-схемы - person hansaplast; 12.11.2017
comment
это стандартный и лучший подход РСУБД, если вы используете 2 таблицы ItemTag, разделяющие теги на второстепенные и основные, вы также хорошо масштабируете - person Stefanos Zilellis; 18.06.2021

Обычно я соглашусь с Яаковом Эллисом, но в этом частном случае есть другое жизнеспособное решение:

Используйте две таблицы:

Table: Item
Columns: ItemID, Title, Content
Indexes: ItemID

Table: Tag
Columns: ItemID, Title
Indexes: ItemId, Title

У этого есть несколько основных преимуществ:

Во-первых, это значительно упрощает разработку: в решении с тремя таблицами для вставки и обновления item вам нужно найти Tag таблицу, чтобы увидеть, есть ли уже записи. Затем вы должны присоединить к ним новые. Это нетривиальная задача.

Затем он делает запросы проще (и, возможно, быстрее). Вам нужно выполнить три основных запроса к базе данных: вывести все Tags для одного Item, нарисовать облако тегов и выбрать все элементы для одного заголовка тега.

Все теги для одного элемента:

3-таблица:

SELECT Tag.Title 
  FROM Tag 
  JOIN ItemTag ON Tag.TagID = ItemTag.TagID
 WHERE ItemTag.ItemID = :id

2-таблица:

SELECT Tag.Title
FROM Tag
WHERE Tag.ItemID = :id

Облако тегов:

3-таблица:

SELECT Tag.Title, count(*)
  FROM Tag
  JOIN ItemTag ON Tag.TagID = ItemTag.TagID
 GROUP BY Tag.Title

2-таблица:

SELECT Tag.Title, count(*)
  FROM Tag
 GROUP BY Tag.Title

Элементы для одного тега:

3-таблица:

SELECT Item.*
  FROM Item
  JOIN ItemTag ON Item.ItemID = ItemTag.ItemID
  JOIN Tag ON ItemTag.TagID = Tag.TagID
 WHERE Tag.Title = :title

2-таблица:

SELECT Item.*
  FROM Item
  JOIN Tag ON Item.ItemID = Tag.ItemID
 WHERE Tag.Title = :title

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

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

Аргумент непоследовательности тоже немного спорен. Теги - это поля с произвольным текстом, и здесь нет ожидаемой операции типа «переименовать все теги с« foo »в« bar »».

Итак, tldr: я бы выбрал решение с двумя столами. (На самом деле я собираюсь. Я нашел эту статью, чтобы узнать, есть ли веские аргументы против нее.)

person Scheintod    schedule 20.09.2013
comment
Означает ли Index: ItemId, Title индекс для каждого или один индекс, содержащий и то, и другое? - person DanMan; 03.12.2013
comment
Обычно два индекса. Однако это может зависеть от используемой вами базы данных. - person Scheintod; 04.12.2013
comment
В таблице тегов есть ItemId, а тег - составной ключ? или ПК у тебя тоже есть? - person Rippo; 21.03.2014
comment
Я думаю, это зависит от того, какой фреймворк вы используете / как вы привыкли писать код. Иногда проще иметь первичный ключ, а иногда нет. В моем случае у меня есть ПК, потому что мне больше нравится использовать что-то вроде DELETE? Id = 5, чем DELETE? Idemid = 3 & tag = Foo в веб-клиенте. С другой стороны, с точки зрения согласованности данных я бы не стал использовать дополнительный ПК. И в учебных целях я бы не рекомендовал это :) - person Scheintod; 22.03.2014
comment
Что касается решения Scheintod с двумя таблицами, оно должно работать, но есть несколько ограничений. 1. Если с вашими тегами связаны какие-либо данные (например, если у вашего тега есть описание или некоторые другие данные), для объектов с несколькими тегами нельзя будет последовательно редактировать их метаданные без выполнения большой работы по их синхронизации. 2. При многократном использовании длинных заголовков и многократного использования тегов требуется дополнительное пространство для хранения. В решении с двумя таблицами заголовок будет вводиться повторно много раз. Возможно, некоторые БД будут оптимизировать данные как часть их хранения / индексации, но я не уверен. В моем случае, - person ajl; 10.11.2014
comment
Привет, вы сказали - с учетом экономии за счет отсутствия присоединения и того факта, что вы можете построить хороший индекс - «маленькая» таблица также будет проиндексирована, размер индекса будет значительно меньше. присоединение к FK, которое имеет тип int, должно быть намного быстрее, чем запрос огромного индекса nvarchar (n). С учетом сказанного, мне нравится эта идея, и, вероятно, я буду использовать ее производные для объединения многих различных типов элементов (таблиц) с общими тегами. - person h.alex; 24.11.2014
comment
Привет, h.alex. Дело в том, что вам нужно делать меньше соединений, которые могут оказаться быстрее, чем их соединение с помощью int FK. Но учитывая, что обычно таблицы тегов имеют тенденцию быть небольшими, разница в скорости (в каком направлении) может быть вообще не заметна. Вот еще одна статья, в которой примерно сравниваются оба метода: dba.stackexchange.com/questions/15897/ - person Scheintod; 27.11.2014
comment
поправьте меня, если я ошибаюсь, но когда я удаляю элемент, в строке не будет нулевого значения, и если я удалю все элементы, не будет ли много повторяющегося набора строк, все с нулевыми значениями для itemid? - person Dheeraj; 24.12.2015
comment
@deez: Я не совсем понимаю, о чем вы говорите (но рано утром). Если вы удаляете элемент, вы должны удалить вместе с ним теги. В зависимости от фактической реализации базы данных (ограничения внешнего ключа) база данных не позволит вам удалить ее, пока вы не удалите теги. - person Scheintod; 25.12.2015
comment
в моем приложении у меня есть некоторые теги, которые поставляются предварительно загруженными, я не осмелюсь удалить эти теги, но я могу выполнить каскадное обнуление, которое оставляет много повторяющихся бесполезных строк, хотя мне нравится идея устранения таблицы сопоставления, хотя - person Dheeraj; 25.12.2015
comment
где хранится исходный список тегов? страница в Интернете ? - person windmaomao; 19.01.2016
comment
таким образом вы не можете создавать неиспользуемые теги, поэтому для элемента необходимо выполнить функцию добавления тега. В другом методе функция добавления тега может выполняться независимо. - person Gianluca Ghettini; 22.04.2017
comment
Привет, мне интересно, по-прежнему ли вы верите, что решение с двумя таблицами лучше решения с тремя столами после всех этих лет? - person Qiulang; 28.09.2018
comment
@Quilang. Я до сих пор верю, что это зависит от того, чем вы занимаетесь :) Я реализовал это обоими способами в разных проектах. В моем последнем случае я получил решение с тремя таблицами, потому что мне нужен был тип тега (или какая-то другая метаинформация о теге) и я мог повторно использовать некоторый код из близкого родственника тегов: параметры. Но в том же проекте я использовал именно этот метод для еще более близкого родственника: flags (например, 'sold', 'new', 'hot') - person Scheintod; 24.10.2018

Если вы используете базу данных, которая поддерживает map-reduce, например couchdb, хранение тегов в текстовом поле или поле списка действительно лучший способ. Пример:

tagcloud: {
  map: function(doc){ 
    for(tag in doc.tags){ 
      emit(doc.tags[tag],1) 
    }
  }
  reduce: function(keys,values){
    return values.length
  }
}

Выполнение этого с помощью group = true сгруппирует результаты по имени тега и даже вернет количество встреч с этим тегом. Это очень похоже на подсчет вхождений слова в текст.

person Nick Retallack    schedule 07.09.2008
comment
+1 Приятно видеть и некоторые реализации NoSQL. - person Xeoncross; 18.03.2011
comment
@NickRetallack Ссылка не работает. Если можно, обновите этот ответ. - person xralf; 18.02.2012
comment
Хорошо, я заменил ссылку на archive.org - person Nick Retallack; 20.02.2012

Используйте один столбец форматированного текста [1] для хранения тегов и используйте способную систему полнотекстового поиска для его индексации. В противном случае вы столкнетесь с проблемами масштабирования при попытке реализовать логические запросы.

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

[1] Некоторые СУБД даже предоставляют собственный тип массива, который может даже лучше подходить для хранения, поскольку не требует этапа синтаксического анализа, но может вызвать проблемы с полнотекстовым поиском.

person David Schmitt    schedule 07.09.2008
comment
Знаете ли вы о какой-либо системе полнотекстового поиска, которая не находит вариантов слова? Например, поиск книги возвращает книги? Кроме того, что вы делаете с такими тегами, как c ++? SQL Server, например, удалит знаки плюса в индексе. Спасибо. - person Jonathan Wood; 18.01.2011
comment
Попробуйте Sphinx - sphinxsearch.com - person Roman; 09.02.2011
comment
Этот учебник из 3 частей может быть полезен тем, кто идет по этому маршруту (полнотекстовый поиск). Он использует собственные средства PostgreSQL: shisaa.jp/postset/postgresql- full-text-search-part-1.html - person Will; 15.05.2014
comment
это лучше, чем выбранный ответ с точки зрения производительности? - person ; 22.10.2017
comment
как насчет сохранения с использованием varchar 255, тегов, разделенных запятыми, и добавления к нему текстового индекса kfull? - person ; 22.10.2017

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

Наличие таблицы «тегов» и таблицы карты делает довольно тривиальным создание облаков тегов и тому подобного, поскольку вы можете легко собрать SQL, чтобы получить список тегов с подсчетом того, как часто каждый тег используется.

person Mark Biek    schedule 21.08.2008
comment
Это еще проще, если вы не используете таблицу сопоставления :) - person Scheintod; 27.09.2013

Я бы предложил следующий дизайн: Таблица элементов: Itemid, taglist1, taglist2 - это будет быстро и упростит сохранение и извлечение данных на уровне элемента.

Параллельно создайте другую таблицу: тег тегов не делает тег уникальным идентификатором, и если у вас заканчивается место во 2-м столбце, который содержит, скажем, 100 элементов, создайте еще одну строку.

Теперь поиск предметов по тегу будет очень быстрым.

person user236575    schedule 28.11.2015
comment
en.wikipedia.org/wiki/First_normal_form, хотя есть исключения из этого, вы можете денормализовать , но не здесь - person Dheeraj; 24.12.2015