За последние 24 часа нас посетили 19279 программистов и 1646 роботов. Сейчас ищут 919 программистов ...

Хранение множеств

Тема в разделе "MySQL", создана пользователем greenbanan, 24 июл 2013.

  1. greenbanan

    greenbanan Активный пользователь

    С нами с:
    21 июн 2011
    Сообщения:
    33
    Симпатии:
    0
    Здравствуйте, передо мной стала задача хранения множеств в бд. Я думал хранить сериализованный массив, но когда понадобилось осуществлять выборку по одному (нескольким ключам) дело повисло.

    Итак, у меня есть пользователь. Каждому пользователю соответствует на выбор 18 типов увлечений (как пример), может быть выбрано как одно, так и несколько (включая все).

    Вопрос: Как организовать хранение и быстрый поиск по этим значениям?

    Пример:
    Id hobby
    1 Вязание, Плавание, Спорт, Музыка
    2 Еда
    3 Музыка, Вязание

    Выборка по 'музыка, вязание' или 'вязание, музыка' должна дать Id 1,3.

    Спасибо.

    Добавлено спустя 5 минут 31 секунду:
    Уже познакомился с типом SET, который кажется решает мою задачу более чем полностью.
     
  2. igordata

    igordata Суперстар
    Команда форума Модератор

    С нами с:
    18 мар 2010
    Сообщения:
    32.408
    Симпатии:
    1.768
    Тебе просто нужно три таблицы:
    Таблицы с говорящим названием: users, hobbies
    И таблица их связей с полями userid, hobbyid

    и усё
     
  3. greenbanan

    greenbanan Активный пользователь

    С нами с:
    21 июн 2011
    Сообщения:
    33
    Симпатии:
    0
    Я думал об этом. Спасибо за ответ. Только подскажите как тогда в таком случае будет осуществлятся выборка по хобби спорт,рыбалка,охота? Т.е. каждый выданный в результате пользователь был и спортсменом и рыбаком и охотникам.
     
  4. shelestov

    shelestov Активный пользователь

    С нами с:
    25 авг 2011
    Сообщения:
    148
    Симпатии:
    0
    Адрес:
    Россия, Арзамас
    Почитайте про связь many to many, сразу все станет ясно.
     
  5. artoodetoo

    artoodetoo Суперстар
    Команда форума Модератор

    С нами с:
    11 июн 2010
    Сообщения:
    11.131
    Симпатии:
    1.251
    Адрес:
    там-сям
    язык SQL отлично приспособлен для работы со множествами. никакие трюки не понадобятся, всё штатно - сущности, атрибуты, отношения.
     
  6. igordata

    igordata Суперстар
    Команда форума Модератор

    С нами с:
    18 мар 2010
    Сообщения:
    32.408
    Симпатии:
    1.768
    в третьей таблице надо держать несклько записей на юзера. на каждое хобби каждого юзера.
     
  7. greenbanan

    greenbanan Активный пользователь

    С нами с:
    21 июн 2011
    Сообщения:
    33
    Симпатии:
    0
    Код (Text):
    1. SELECT users.*, COUNT(*) AS c
    2. FROM users, work, userwork
    3. WHERE userwork.wid = work.id AND userwork.uid = users.id
    4. AND work.name IN ('Мент','Пожарный')
    5. GROUP BY users.id
    6. HAVING c = 2
    Я придумал такое, может ли быть проще?
     
  8. artoodetoo

    artoodetoo Суперстар
    Команда форума Модератор

    С нами с:
    11 июн 2010
    Сообщения:
    11.131
    Симпатии:
    1.251
    Адрес:
    там-сям
    "Вязание, Плавание, Спорт, Музыка" -- это же т.н. поисковые теги. Реализовано много где. Отношение многие-ко-многим делается через промежуточную таблицу-связку. Первичный ключ таблицы из двух полей, типа (ид_пользователя, ид_тега).
    Поиски и фильтры делаются только через джойны с этой таблицей-связкой. А вот для оптимизации вывода можно оставить текстовое поле с этими тегами через запятую или еще с какими разделителями. Чтобы просто показать теги не придется ничего джойнить.

    Для примера приведу куски из базы stackoverflow.com : есть таблиц Посты, Теги и Теги_Постов. Я сокращю ненужные здесь поля.

    Код (Text):
    1. CREATE TABLE `Posts` (
    2.   `Id` int(11) NOT NULL,
    3.   `ParentID` int(11) DEFAULT NULL,
    4.   `CreationDate` datetime NOT NULL,
    5.   `Body` text,
    6.   `OwnerUserId` int(11) DEFAULT NULL,
    7.   `Title` varchar(500) DEFAULT NULL,
    8.   `Tags` varchar(300) DEFAULT NULL,
    9.   PRIMARY KEY (`Id`),
    10.   KEY `IX_Posts_ParentId` (`ParentID`)
    11. )
    В поле Posts.Tags хранятся теги в виде "<alpha><beta><gama>" -- для отображения поста не надо джойнить другие таблицы. Элементарно одной регуляркой преобразовать это поле в список ссылок по тегам.

    А вот "настоящие" теги:
    Код (Text):
    1. CREATE TABLE `Tags` (
    2.   `Id` int(11) NOT NULL AUTO_INCREMENT,
    3.   `TagName` varchar(50) NOT NULL,
    4.   PRIMARY KEY (`Id`),
    5.   UNIQUE KEY `TagName` (`TagName`)
    6. );
    Таблица-связка м-м:
    Код (Text):
    1. CREATE TABLE `PostTags` (
    2.   `PostId` int(11) NOT NULL,
    3.   `TagId` int(11) NOT NULL,
    4.   PRIMARY KEY (`PostId`,`TagId`),
    5.   KEY `TagId` (`TagId`,`PostId`)
    6. );
    Выбрать записи с тегом, например, "php" можно так:
    Код (Text):
    1. SELECT p.*
    2. FROM `Posts` AS p INNER JOIN
    3. `PostTags` AS pt ON pt.`PostId`=p.`Id` INNER JOIN
    4. `Tags` AS t ON pt.`TagId`=t.`Id`
    5. WHERE t.`TagName`='php'
    6. LIMIT 100
     
  9. greenbanan

    greenbanan Активный пользователь

    С нами с:
    21 июн 2011
    Сообщения:
    33
    Симпатии:
    0
    Спасибо. Я с джоинами мельком знаком, можете прокоментировать мое (работающее) творение?
    Код (Text):
    1. SELECT users.*, COUNT(*) AS c
    2. FROM users, work, userwork
    3. WHERE userwork.wid = work.id AND userwork.uid = users.id
    4. AND work.name IN ('Мент','Пожарный')
    5. GROUP BY users.id
    6. HAVING c = 2
     
  10. artoodetoo

    artoodetoo Суперстар
    Команда форума Модератор

    С нами с:
    11 июн 2010
    Сообщения:
    11.131
    Симпатии:
    1.251
    Адрес:
    там-сям
    Необязательно было цитировать всю простыню )))
    Отношения можно оформить и во фразе WHERE -- как ты сделал. Слово JOIN это всего-лишь синтаксический сахар, не принципиально. Например в диалекте Oracle до недавних пор не было слова JOIN.

    А вот user.* в сочетании с GROUP BY это логическая ошибка.
    1. users.id это ведь первичный ключ? то есть ЗНАЧЕНИЕ УНИКАЛЬНО ВО ВСЕЙ ТАБЛИЦЕ, какой смысл делать по нему GROUP BY ?
    2. если ты группируешь записи, несколько записей сжимаются в одну, какие по твоему значения должны остаться? я не знаю и сервер БД тоже не знает.

    Я не понимаю что этот запрос должен означать и не факт, что он вообще выполнится. Зависит от настроек. Варианты от непредсказуемого набора данных до синтаксической ошибки.
     
  11. greenbanan

    greenbanan Активный пользователь

    С нами с:
    21 июн 2011
    Сообщения:
    33
    Симпатии:
    0
    Если я правильно понял, то вы предлагаете просто убрать группировку? У меня выдает нужный результат на нескольких примерах и серверах.
     
  12. artoodetoo

    artoodetoo Суперстар
    Команда форума Модератор

    С нами с:
    11 июн 2010
    Сообщения:
    11.131
    Симпатии:
    1.251
    Адрес:
    там-сям
    Я предлагаю сформулировать цель по русски. Если получится это сделать кратко и конкретно, то почти дословно это можно перевести на SQL.
     
  13. greenbanan

    greenbanan Активный пользователь

    С нами с:
    21 июн 2011
    Сообщения:
    33
    Симпатии:
    0
    Получить из базы всех пользователей, у которых должность соответствует заданному массиву должностей.
     
  14. artoodetoo

    artoodetoo Суперстар
    Команда форума Модератор

    С нами с:
    11 июн 2010
    Сообщения:
    11.131
    Симпатии:
    1.251
    Адрес:
    там-сям
    Код (Text):
    1. SELECT users.*
    2. FROM
    3.   users INNER JOIN
    4.   userwork ON userwork.uid = users.id INNER JOIN
    5.   work ON userwork.wid = work.id
    6. WHERE work.name IN ('Мент','Пожарный')
    то же самое, просто синтаксис немного другой:
    Код (Text):
    1. SELECT users.*
    2. FROM
    3.   users, userwork, work
    4. WHERE
    5.    userwork.uid = users.id AND
    6.    userwork.wid = work.id AND
    7.    work.name IN ('Мент','Пожарный')
    здесь не нужна группировка

    Добавлено спустя 6 минут 21 секунду:
    если я тебя правильно понял. это если ЛЮБАЯ из указанных профессий подойдет. если надо ОБЕ профессии у каждого человека, то понадобится одно из двух:
    - два набора work и userwork, один для ментов, один для пожарных
    - вложенный запрос. внутри объединение людей и их профессий из массива (мент, пожарный), а в оболочке group by и having count(*)=2
     
  15. greenbanan

    greenbanan Активный пользователь

    С нами с:
    21 июн 2011
    Сообщения:
    33
    Симпатии:
    0
    Мне нужно четкое соответствие, если Мент и Пожарный, значит нужно выбрать только тех пользователей, у кого и мент и пожарный одновременно включены. И таких атрибутов выборки может быть до 16. Для этого я и считал количество и проверял его.
     
  16. artoodetoo

    artoodetoo Суперстар
    Команда форума Модератор

    С нами с:
    11 июн 2010
    Сообщения:
    11.131
    Симпатии:
    1.251
    Адрес:
    там-сям
    ну значит с группировкой, да.
    только нельзя во фразе SELECT при этом писать "*". перечисли конкретно поля и агрегатные функции от полей что надо получить.
    у меня есть данные с stackoverflow, я вот такой запрос проверил. думаю ты аналогию найдешь
    Код (Text):
    1.   SELECT pt.`PostId`, COUNT(*)
    2.   FROM
    3.     `PostTags` AS pt INNER JOIN
    4.     `Tags` AS t ON pt.`TagId`=t.`Id`
    5.   WHERE t.`TagName` IN('php', 'mysql')
    6.   GROUP BY pt.`PostId`
    7.   HAVING COUNT(*) = 2
    8.   LIMIT 100
    чтобы получить список подходящих Posts.Id не нужна сама таблица Posts :D
    результат можно заджойнить с Posts и получить остальные поля, если надо
     
  17. greenbanan

    greenbanan Активный пользователь

    С нами с:
    21 июн 2011
    Сообщения:
    33
    Симпатии:
    0
    Спасибо за ответы.