Проблема с MySQL левого соединения - PullRequest
0 голосов
/ 04 марта 2011

У меня есть 3 таблицы в MySQL (5.1): account, bank_account_data и investment_account_data.

таблицы определены следующим образом: (ПРИМЕЧАНИЕ: это упрощенная версия, в реальных таблицах больше столбцов, а в банковских и инвестиционных данных содержится разная информация, которая не нужна для этого конкретного случая, но нужна в другом месте)

CREATE TABLE account
(
  id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
  type ENUM ('INVESTMENT', 'BANK') NOT NULL,
  PRIMARY KEY (id)
) ENGINE=InnoDB;

CREATE TABLE investment_account_data
(
  id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
  account_id BIGINT UNSIGNED NOT NULL,
  as_of_date TIMESTAMP NULL DEFAULT NULL,
  total_balance NUMERIC(12, 4) NOT NULL,
  PRIMARY KEY (id),
  FOREIGN KEY (account_id) REFERENCES account (id),
  CONSTRAINT unique_per_account_and_date UNIQUE (account_id, as_of_date)
) ENGINE=InnoDB;

CREATE TABLE bank_account_data
(
  id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
  account_id BIGINT UNSIGNED NOT NULL,
  as_of_date TIMESTAMP  NULL DEFAULT NULL,
  current_balance NUMERIC(12, 4) NOT NULL,
  PRIMARY KEY (id),
  FOREIGN KEY (account_id) REFERENCES account (id),
  CONSTRAINT unique_per_account_and_date UNIQUE (account_id, as_of_date)
) ENGINE=InnoDB;

В настоящее время у меня есть только одна учетная запись в таблице счетов с id = 1 и типом = 'INVESTMENT'. и у меня есть одна запись в таблице investment_account_data (id = 1, account_id = 1, total_balace = 15000, as_of_data = '2011-03-02 00:00:00')

В bank_account_data нет строк.

Выполнение следующего запроса (на удивление) не возвращает строк:

SELECT A.id as account_id,
       COALESCE(BD.as_of_date, ID.as_of_date) as as_of_date,
       COALESCE(BD.current_balance, ID.total_balance) as balance
  FROM account A
       LEFT JOIN bank_account_data BD ON (BD.account_id = A.id and A.type='BANK') 
       LEFT JOIN investment_account_data ID ON (ID.account_id = A.id and A.type='INVESTMENT')
 WHERE A.id=1

но этот возвращает одну строку (как и ожидалось):

SELECT A.id as account_id,
       COALESCE(BD.as_of_date, ID.as_of_date) as as_of_date,
       COALESCE(BD.current_balance, ID.total_balance) as balance
 FROM account A
      LEFT JOIN investment_account_data ID ON (ID.account_id = A.id and A.type='INVESTMENT')
      LEFT JOIN bank_account_data BD ON (BD.account_id = A.id and A.type='BANK') 
 WHERE A.id=1

Есть идеи, почему я вижу эти результаты?

Также, если я удалю условие A.type из объединения, оно также вернет одну строку.

Ответы [ 3 ]

1 голос
/ 04 марта 2011

Это именно тот результат, который вы должны ожидать, учитывая запросы и данные, которые вы разместили.

В вашем первом запросе первое условие соединения указывает A.type = 'BANK'. Итак, после первого LEFT JOIN у вас есть результирующий набор с нулевыми строками, который вы затем LEFT JOIN включаете снова. Если ваша «левая» сторона равна нулю строк, LEFT JOIN (или INNER JOIN) всегда будет также возвращать ноль строк.

Для второго запроса порядок обратный. Итак, после первого соединения у вас есть набор результатов из одной строки. Теперь у вас есть 1 ряд слева от вашего второго соединения.

1 голос
/ 04 марта 2011

Рискну предположить, что причиной наблюдаемого поведения является то, что вы создаете фильтр над полем типа в первом предложении объединения, который маскирует все учетные записи из второго предложения соединения.

  1. Выберите все учетные записи, соответствующие типу «BANK»
  2. Объедините их с соответствующей информацией банковского счета
  3. Выберите все учетные записи, соответствующие типу «INVESTMENT» слева от последнего набора результатов, т.е. все учетные записи, соответствующие типу «BANK»'
  4. Присоединиться к пустому набору с соответствующей информацией об инвестиционном счете.

Исправление

Грязный и плохой способ сделать это - создать другой наборусловия соединения

SELECT A.id as account_id,
       COALESCE(BD.as_of_date, ID.as_of_date) as as_of_date,
       COALESCE(BD.current_balance, ID.total_balance) as balance
 FROM account A
      LEFT JOIN investment_account_data ID ON (ID.account_id = A.id)
      LEFT JOIN bank_account_data BD ON (BD.account_id = A.id) 
 WHERE 
      A.id=1
      AND ((ID.account_id is not null and A.TYPE='INVESTMENT') 
          OR
           (BD.account_id is not null and A.TYPE = 'BANK'))

Необходимость использования ИЛИ приводит меня к мысли, что в схеме базы данных есть что-то вонючее.Вы можете сделать то же самое с UNION, который здесь гораздо лучше подходит.

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

0 голосов
/ 04 марта 2011

Проблема заключается в том, как обрабатывается левое соединение и условие A.type.UNION будет более подходящим оператором для того, что вы пытаетесь сделать.

SELECT account_id, as_of_date, current_balance as balance
       FROM bank_account_data
UNION
SELECT account_id, as_of_date, total_balance as balanceas
       FROM investment_account_data

(введите объединение, если вам нужна дополнительная информация из таблицы счетов)

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

...