Оставить соединение с пунктом «Где»

162

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

Но этот запрос извлекает только те настройки, где символ = 1, а не настройки по умолчанию, если пользователь никого не установил.

SELECT `settings`.*, `character_settings`.`value`
FROM (`settings`)
LEFT JOIN `character_settings` 
ON `character_settings`.`setting_id` = `settings`.`id`
WHERE `character_settings`.`character_id` = '1'  

Поэтому мне нужно что-то вроде этого:

array(
    '0' => array('somekey' => 'keyname', 'value' => 'thevalue'),
    '1' => array('somekey2' => 'keyname2'),
    '2' => array('somekey3' => 'keyname3')
)

Где ключи 1 и 2 являются значениями по умолчанию, когда ключ 0 содержит значение по умолчанию со значением символа.

Сейн Крафт
источник

Ответы:

346

Предложение whereотфильтровывает строки, где left joinне удается. Переместите его в соединение:

SELECT  `settings`.*, `character_settings`.`value`
FROM    `settings`
LEFT JOIN 
       `character_settings` 
ON     `character_settings`.`setting_id` = `settings`.`id`
        AND `character_settings`.`character_id` = '1'  
Andomar
источник
55

При создании ВНЕШНИХ СОЕДИНЕНИЙ (ANSI-89 или ANSI-92) местоположение фильтрации имеет значение, поскольку критерии, указанные в ONпредложении, применяются до создания СОЕДИНЕНИЯ . Критерии для таблицы OUTER JOINed, представленной в WHEREпредложении, применяются после создания JOIN . Это может привести к очень разным наборам результатов. Для сравнения, ВНУТРЕННИЕ СОЕДИНЕНИЯ не имеют значения, если критерии указаны в предложениях ONили WHERE- результат будет таким же.

  SELECT  s.*, 
          cs.`value`
     FROM SETTINGS s
LEFT JOIN CHARACTER_SETTINGS cs ON cs.setting_id = s.id
                               AND cs.character_id = 1
OMG пони
источник
21

Если я правильно понимаю ваш вопрос, вы хотите получить записи из базы данных настроек, если у них нет соединения по сравнению с таблицей character_settings или если у этой объединенной записи есть character_id = 1.

Поэтому вы должны сделать

SELECT `settings`.*, `character_settings`.`value`
FROM (`settings`)
LEFT OUTER JOIN `character_settings` 
ON `character_settings`.`setting_id` = `settings`.`id`
WHERE `character_settings`.`character_id` = '1' OR
`character_settings`.character_id is NULL
отметка
источник
7
Это рискует возвращать ложные срабатывания
OMG Ponies
1
@OMGPonies Я не понимаю, в каких случаях это может быть связано с риском. В моем случае я применил AND с объединением, и никакого результата не было, но когда я использовал вышеуказанное решение, у меня были результаты. Но я хочу быть уверенным в проблемах, с которыми я могу столкнуться с этой опцией.
Четан Шарма
2

Возможно, вам будет легче понять, используя простой подзапрос

SELECT `settings`.*, (
    SELECT `value` FROM `character_settings`
    WHERE `character_settings`.`setting_id` = `settings`.`id`
      AND `character_settings`.`character_id` = '1') AS cv_value
FROM `settings`

Подзапросу разрешено возвращать ноль, поэтому вам не нужно беспокоиться о JOIN / WHERE в основном запросе.

Иногда это работает быстрее в MySQL, но сравните его с формой LEFT JOIN, чтобы увидеть, что работает лучше для вас.

SELECT s.*, c.value
FROM settings s
LEFT JOIN character_settings c ON c.setting_id = s.id AND c.character_id = '1'
RichardTheKiwi
источник
1

Результат правильный на основании оператора SQL. Левое соединение возвращает все значения из правой таблицы и только совпадающие значения из левой таблицы.

Столбцы ID и NAME находятся в правой части таблицы, поэтому возвращаются.

Оценка берется из левой таблицы, и возвращается 30, так как это значение относится к имени «Поток». Другие имена имеют значение NULL, поскольку они не относятся к имени «Поток».

Ниже приведен результат, который вы ожидали:

    SELECT  a.*, b.Score
FROM    @Table1 a
    LEFT JOIN @Table2 b
       ON a.ID = b.T1_ID 
WHERE 1=1
AND a.Name = 'Flow'

SQL применяет фильтр к правой таблице.

Дэн
источник
0

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

with settings_for_char as (
  select setting_id, value from character_settings where character_id = 1
)
select
  settings.*,
  settings_for_char.value
from
  settings
  left join settings_for_char on settings_for_char.setting_id = settings.id;
Р-Оп
источник