Избегайте использования одного и того же подзапроса несколько раз в запросе - PullRequest
3 голосов
/ 18 июля 2009

На сервере 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);

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

РЕДАКТИРОВАТЬ: NULL spellid_n считается действительным, поскольку это просто означает, что у предмета нет заклинания в этом слоте.

Ответы [ 4 ]

2 голосов
/ 18 июля 2009

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

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

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

1 голос
/ 22 июля 2009

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

Однако механизм хранения оптимизирует длинный 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 пока не поддерживает предложение 'with' (см. этот вопрос ).

0 голосов
/ 22 июля 2009

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

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

РЕДАКТИРОВАТЬ: ах думал, что было только 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 голосов
/ 22 июля 2009

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

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

EDIT:

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...