MySQL: как написать запрос для отношения, основанного на диапазоне, а не на внешнем ключе? - PullRequest
2 голосов
/ 26 мая 2009

У меня есть три таблицы в MySQL, которые связаны, но технически не связаны между собой внешним ключом. Это: пользователи , уровни и классы .

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

  • уровень 1 (минимальная карма: 0)
  • уровень 2 (минимальная карма: 100)
  • уровень 3 (минимальная карма: 500)
  • ...

Так, если у пользователя есть карма 400, возвращаемое значение должно быть 2. Чтобы сделать вещи немного более сложными, номер уровня указывает класс пользователя пользователя, определения которого хранятся в классах. Таблица. Еще раз это диапазон отношение:

  • Муравей класса (минимальный уровень 1)
  • Сова класса (минимальный уровень 5)
  • класс лев (минимальный уровень 100)
  • ...

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

Для одного пользователя мне удалось написать этот запрос, который отлично работает:

SELECT u.*,  lv.num as level, lvc.title as class, lvc.id as classid
FROM user as u, level as lv, levelclass as lvc
WHERE u.id = ? AND lv.min_karma <= u.karma AND lv.num <= lvc.minlevel_num
ORDER BY lv.num DESC LIMIT 1;

Однако, если бы я расширил набор результатов, оставив WHERE u.id =? и удалив LIMIT 1, таким образом запрашивая список пользователей, я получаю комбинацию всех трех таблиц. Как правило, вы убираете строки, выполняя внутреннее соединение клавиш, но так как это проверка диапазона, это не работает. Я попытался использовать проверку диапазона в условии внутреннего соединения, но это дает тот же результат. Даже используя группировку, я не могу получить желаемый результат.

В отчаянной попытке я придумал этот запрос, который работает:

SELECT usr.*, 
(SELECT lv.num as level
FROM user as u, level as lv, levelclass as lvc
WHERE u.id = usr.id AND lv.min_karma <= u.karma AND lv.num <= lvc.minlevel_num
ORDER BY lv.num DESC LIMIT 1) as level,
(SELECT lvc.title as class
FROM user as u, level as lv, levelclass as lvc
WHERE u.id = usr.id AND lv.min_karma <= u.karma AND lv.num <= lvc.minlevel_num
ORDER BY lv.num DESC LIMIT 1) as class,
(SELECT lvc.image as class_image
FROM user as u, level as lv, levelclass as lvc
WHERE u.id = usr.id AND lv.min_karma <= u.karma AND lv.num <= lvc.minlevel_num
ORDER BY lv.num DESC LIMIT 1) as class_image,
(SELECT lvc.id as classid
FROM user as u, level as lv, levelclass as lvc
WHERE u.id = usr.id AND lv.min_karma <= u.karma AND lv.num <= lvc.minlevel_num
ORDER BY lv.num DESC LIMIT 1) as classid
FROM user as usr
ORDER BY usr.$sortby $direction LIMIT ?,?

Однако, мне это кажется крайне неэффективным. По сути, здесь я пишу подзапрос для каждого столбца (!) , который мне нужен из таблиц уровней и классов. Если бы я запросил несколько столбцов таблиц уровня или класса в одном подзапросе, он снова возвращает комбинации всех значений. Я чувствую пробел в моих навыках работы с БД, что-то очевидное, чего мне не хватает, функцию, о которой я не знаю ...

Вы можете мне помочь? Как написать эффективный запрос для набора строк, который объединяет столбцы из трех таблиц, но не связан ключами (диапазонами)?

PS: Я знаю, что мог бы значительно упростить сценарий, если бы денормализовал эту схему для объединения уровней и классов в таблицу пользователей, но есть конкретная причина, почему мне это нужно, поверьте мне.

Ответы [ 3 ]

5 голосов
/ 26 мая 2009

Если вы можете изменить таблицы уровней и классов, чтобы иметь действительный диапазон в строке (т. Е. Иметь minkarma и maxkarma в таблице уровней, minlevel и maxlevel в таблице классов), вы должны иметь возможность просто использовать их в своем условие соединения, и оно будет возвращать только одну строку для каждого пользователя.

Пример:

SELECT * FROM user as u
INNER JOIN level as lv on
    u.karma >= lv.min_karma AND u.karma <= lv.max_karma
INNER JOIN levelclass as lvc on
    lv.num >= lvc.min_level AND lv.num <= lvc.max_level

Причина, по которой вы получаете несколько строк, состоит в том, что вы сопоставляете несколько уровней и классов, фильтруя только значение min.

0 голосов
/ 26 мая 2009

Вы хотите использовать объединение для casemnet statemnet

SELECT 
 * 
FROM 
 users u
JOIN 
 levels l
ON 
 l.id = 
CASE 
    WHEN u.karma <= 10
    THEN 1
    WHEN u.karma > 11 & u.karma <= 100
    THEN 2
    WHEN u.karma > 100 & u.karma <= 1000
    THEN 3
    WHEN u.karma > 1000 
    THEN 4
END
JOIN 
 class c
ON 
 c.id is
CASE 
    WHEN l.id <= 5
    THEN 'owl'
    WHEN l.id > 5 & u.karma <= 10
    THEN 'lion'
END

Сезон по вкусу.

0 голосов
/ 26 мая 2009

Мне нужно было бы увидеть вашу точную структуру данных, чтобы быть уверенным, но как насчет чего-то подобного?

SELECT
    *
FROM
    [User] u
LEFT OUTER JOIN
    [Level] lv
ON
    lv.num =
(
    SELECT
        MAX(lv2.num)
    FROM
        [Level] lv2
    WHERE
        lv2.min_karma <= u.karma
)
LEFT OUTER JOIN
    [LevelClass] lvc
ON
    lvc.id =
(
    SELECT
        MAX(lvc2.id)
    FROM
        [LevelClass] lvc2
    WHERE
        lvc2.minlevel_num <= lv.num
)
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...