2009-07-18 1 views
3

На сервере MMORPG я рефакторинг, у меня есть две таблицы. Один для предметов и один для заклинаний. Каждый предмет имеет до 5 заклинаний, поэтому я пошел с разреженным матричным форматом, имея 5 столбцов для идентификаторов заклинаний.Избегайте использования одного и того же подзапроса несколько раз в запросе

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

До сих пор я был в состоянии придумать только это:

SELECT COUNT(*) 
    FROM items 
WHERE spellid_1 NOT IN (SELECT entry FROM research.spell) 
    OR spellid_2 NOT IN (SELECT entry FROM research.spell) 
    OR spellid_3 NOT IN (SELECT entry FROM research.spell) 
    OR spellid_4 NOT IN (SELECT entry FROM research.spell) 
    OR spellid_5 NOT IN (SELECT entry FROM research.spell); 

Есть ли более элегантный способ сделать это?

EDIT: NULL spellid_n считается как действительным, так как это просто означает, что элемент не имеет заклинание в этом слоте.

ответ

2

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

SELECT COUNT(DISTINCT item_id) 
    FROM item_spells 
WHERE spell_id NOT IN (SELECT entry FROM research.spell); 

Как это, вы вынуждены выполнять проверку в 5 раз.

+0

Правда, но никогда не будет больше, чем 5 заклинаний за единицу, а также совместное использование регистра является извлечение элемента в полном комплекте, так что я до сих пор считаю, разреженный матрица лучше подходит для этого. – MoshiBin

+0

@Spidey В отличие от воды, характеристики редко замерзают. Я бы посоветовал следовать совету Тони о нормализации и будущем. –

+1

Проголосовали. Не отвечает на вопрос. – hobodave

0

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

Однако механизм хранения оптимизирует длинный sql с 5 идентичными подзапросами, это не должно вызывать проблем с производительностью. Альтернативой фразировка бы, используя SQL '99 стандартный «с» п:

WITH spellids(entry) AS SELECT entry FROM research.spell 
SELECT COUNT(*) 
FROM items 
WHERE  spellid_1 NOT IN spellids OR spellid_2 NOT IN spellids 
     OR spellid_3 NOT IN spellids OR spellid_4 NOT IN spellids 
     OR spellid_5 NOT IN spellids ; 

не намного короче, и, к сожалению, MySQL не поддерживает «с» положение (см this question) еще.

0

Spidey, хороший вопрос. Попробуйте следующее:

SELECT COUNT(*) 
FROM items i 
LEFT JOIN research.spell spell1 ON i.spellid_1 = spell1.entry 
LEFT JOIN research.spell spell2 ON i.spellid_2 = spell2.entry 
LEFT JOIN research.spell spell3 ON i.spellid_3 = spell3.entry 
LEFT JOIN research.spell spell4 ON i.spellid_4 = spell4.entry 
LEFT JOIN research.spell spell5 ON i.spellid_5 = spell5.entry 
WHERE spell1.entry IS NULL 
OR spell2.entry IS NULL 
OR spell3.entry IS NULL 
OR spell4.entry IS NULL 
OR spell5.entry IS NULL 

Ключевым моментом здесь является то, что вы хотите LEFT JOIN таблицу research.spell, так что она включает в себя элементы, которые не имеют соответствующей строки для данного РЕГИСТРИРУЙТЕСЬ условия. Затем вы фильтруете этот табличный план, где правая сторона соединения IS NULL. Это дает вам строки из левой таблицы (элементов) без соответствующей строки в правой таблице (research.spell).

EDIT:

Также обратите внимание, что я оставил свой первоначальный SELECT COUNT (*) без изменений. Это даст вам общее количество предметов с 1 или более недействительными заклинаниями. Вам нужно будет изменить это на SELECT i.id или что-то подобное, чтобы получить идентификаторы элементов, имеющих недопустимые заклинания.

+0

Это близко к тому, что мне нужно. Я обновил таблицу элементов, потому что предыдущие разработчики БД думали, что использование комбинации 0 и -1 вместо NULL было лучше. Однако этот запрос также возвращает элементы с столбцами NULL spellid_n. Похоже, я забыл упомянуть, что я не считаю их недействительными, за что я извиняюсь. – MoshiBin

0

попробовать "реверс не", как это называется:

SELECT COUNT(*) 
FROM items 
WHERE (SELECT entry FROM research.spell) NOT IN (spellid_1, spellid_2, 
               spellid_3, spellid_4, 
               spellid_5) 

EDIT: ах думал, что только 1 значение. то вы можете сделать это внутреннее соединение:

SELECT COUNT(*) 
FROM items i 
    join (SELECT entry FROM research.spell) t 
      on t.entry NOT IN (spellid_1, spellid_2, spellid_3, spellid_4, spellid_5) 
+0

Ошибка 1242: Подзапрос возвращает более 1 строки. Я не вижу, как это будет работать в любом случае, поскольку проверяет, находится ли большая группа в небольшой группе. – MoshiBin

+0

Для возврата с использованием нового запроса потребовалось MySQL 5:53 минут. Против 3-х секунд моего первоначального запроса, я думаю, что-то не так – MoshiBin

+0

lol. нормально, тогда это определенно не полезно :) –