Здравствуйте, передо мной стала задача хранения множеств в бд. Я думал хранить сериализованный массив, но когда понадобилось осуществлять выборку по одному (нескольким ключам) дело повисло. Итак, у меня есть пользователь. Каждому пользователю соответствует на выбор 18 типов увлечений (как пример), может быть выбрано как одно, так и несколько (включая все). Вопрос: Как организовать хранение и быстрый поиск по этим значениям? Пример: Id hobby 1 Вязание, Плавание, Спорт, Музыка 2 Еда 3 Музыка, Вязание Выборка по 'музыка, вязание' или 'вязание, музыка' должна дать Id 1,3. Спасибо. Добавлено спустя 5 минут 31 секунду: Уже познакомился с типом SET, который кажется решает мою задачу более чем полностью.
Тебе просто нужно три таблицы: Таблицы с говорящим названием: users, hobbies И таблица их связей с полями userid, hobbyid и усё
Я думал об этом. Спасибо за ответ. Только подскажите как тогда в таком случае будет осуществлятся выборка по хобби спорт,рыбалка,охота? Т.е. каждый выданный в результате пользователь был и спортсменом и рыбаком и охотникам.
язык SQL отлично приспособлен для работы со множествами. никакие трюки не понадобятся, всё штатно - сущности, атрибуты, отношения.
Код (Text): SELECT users.*, COUNT(*) AS c FROM users, work, userwork WHERE userwork.wid = work.id AND userwork.uid = users.id AND work.name IN ('Мент','Пожарный') GROUP BY users.id HAVING c = 2 Я придумал такое, может ли быть проще?
"Вязание, Плавание, Спорт, Музыка" -- это же т.н. поисковые теги. Реализовано много где. Отношение многие-ко-многим делается через промежуточную таблицу-связку. Первичный ключ таблицы из двух полей, типа (ид_пользователя, ид_тега). Поиски и фильтры делаются только через джойны с этой таблицей-связкой. А вот для оптимизации вывода можно оставить текстовое поле с этими тегами через запятую или еще с какими разделителями. Чтобы просто показать теги не придется ничего джойнить. Для примера приведу куски из базы stackoverflow.com : есть таблиц Посты, Теги и Теги_Постов. Я сокращю ненужные здесь поля. Код (Text): CREATE TABLE `Posts` ( `Id` int(11) NOT NULL, `ParentID` int(11) DEFAULT NULL, `CreationDate` datetime NOT NULL, `Body` text, `OwnerUserId` int(11) DEFAULT NULL, `Title` varchar(500) DEFAULT NULL, `Tags` varchar(300) DEFAULT NULL, PRIMARY KEY (`Id`), KEY `IX_Posts_ParentId` (`ParentID`) ) В поле Posts.Tags хранятся теги в виде "<alpha><beta><gama>" -- для отображения поста не надо джойнить другие таблицы. Элементарно одной регуляркой преобразовать это поле в список ссылок по тегам. А вот "настоящие" теги: Код (Text): CREATE TABLE `Tags` ( `Id` int(11) NOT NULL AUTO_INCREMENT, `TagName` varchar(50) NOT NULL, PRIMARY KEY (`Id`), UNIQUE KEY `TagName` (`TagName`) ); Таблица-связка м-м: Код (Text): CREATE TABLE `PostTags` ( `PostId` int(11) NOT NULL, `TagId` int(11) NOT NULL, PRIMARY KEY (`PostId`,`TagId`), KEY `TagId` (`TagId`,`PostId`) ); Выбрать записи с тегом, например, "php" можно так: Код (Text): SELECT p.* FROM `Posts` AS p INNER JOIN `PostTags` AS pt ON pt.`PostId`=p.`Id` INNER JOIN `Tags` AS t ON pt.`TagId`=t.`Id` WHERE t.`TagName`='php' LIMIT 100
Спасибо. Я с джоинами мельком знаком, можете прокоментировать мое (работающее) творение? Код (Text): SELECT users.*, COUNT(*) AS c FROM users, work, userwork WHERE userwork.wid = work.id AND userwork.uid = users.id AND work.name IN ('Мент','Пожарный') GROUP BY users.id HAVING c = 2
Необязательно было цитировать всю простыню ))) Отношения можно оформить и во фразе WHERE -- как ты сделал. Слово JOIN это всего-лишь синтаксический сахар, не принципиально. Например в диалекте Oracle до недавних пор не было слова JOIN. А вот user.* в сочетании с GROUP BY это логическая ошибка. 1. users.id это ведь первичный ключ? то есть ЗНАЧЕНИЕ УНИКАЛЬНО ВО ВСЕЙ ТАБЛИЦЕ, какой смысл делать по нему GROUP BY ? 2. если ты группируешь записи, несколько записей сжимаются в одну, какие по твоему значения должны остаться? я не знаю и сервер БД тоже не знает. Я не понимаю что этот запрос должен означать и не факт, что он вообще выполнится. Зависит от настроек. Варианты от непредсказуемого набора данных до синтаксической ошибки.
Если я правильно понял, то вы предлагаете просто убрать группировку? У меня выдает нужный результат на нескольких примерах и серверах.
Я предлагаю сформулировать цель по русски. Если получится это сделать кратко и конкретно, то почти дословно это можно перевести на SQL.
Код (Text): SELECT users.* FROM users INNER JOIN userwork ON userwork.uid = users.id INNER JOIN work ON userwork.wid = work.id WHERE work.name IN ('Мент','Пожарный') то же самое, просто синтаксис немного другой: Код (Text): SELECT users.* FROM users, userwork, work WHERE userwork.uid = users.id AND userwork.wid = work.id AND work.name IN ('Мент','Пожарный') здесь не нужна группировка Добавлено спустя 6 минут 21 секунду: если я тебя правильно понял. это если ЛЮБАЯ из указанных профессий подойдет. если надо ОБЕ профессии у каждого человека, то понадобится одно из двух: - два набора work и userwork, один для ментов, один для пожарных - вложенный запрос. внутри объединение людей и их профессий из массива (мент, пожарный), а в оболочке group by и having count(*)=2
Мне нужно четкое соответствие, если Мент и Пожарный, значит нужно выбрать только тех пользователей, у кого и мент и пожарный одновременно включены. И таких атрибутов выборки может быть до 16. Для этого я и считал количество и проверял его.
ну значит с группировкой, да. только нельзя во фразе SELECT при этом писать "*". перечисли конкретно поля и агрегатные функции от полей что надо получить. у меня есть данные с stackoverflow, я вот такой запрос проверил. думаю ты аналогию найдешь Код (Text): SELECT pt.`PostId`, COUNT(*) FROM `PostTags` AS pt INNER JOIN `Tags` AS t ON pt.`TagId`=t.`Id` WHERE t.`TagName` IN('php', 'mysql') GROUP BY pt.`PostId` HAVING COUNT(*) = 2 LIMIT 100 чтобы получить список подходящих Posts.Id не нужна сама таблица Posts результат можно заджойнить с Posts и получить остальные поля, если надо