Смешение неявных и явных JOINs

У меня проблема с Hibernate, генерирующим недопустимый SQL. В частности, смешивание и сопоставление неявных и явных объединений. Кажется, это ошибка .

Однако я не уверен, почему это недопустимый SQL. Я придумал небольшой пример игрушек, который генерирует одно и то же исключение синтаксиса.

Схема

CREATE TABLE Employee (
    employeeID INT,
    name VARCHAR(255),
    managerEmployeeID INT   
)

Данные

INSERT INTO Employee (employeeID, name) VALUES (1, 'Gary')
INSERT INTO Employee (employeeID, name, managerEmployeeID) VALUES (2, 'Bob', 1)

Рабочий SQL

Оба эти запроса работают. Я понимаю, что есть декартово произведение; это намеренно.

Явный JOIN:

SELECT e1.name,
       e2.name,
       e1Manager.name
  FROM Employee e1
 CROSS JOIN Employee e2
 INNER JOIN Employee e1Manager
    ON e1.managerEmployeeID = e1Manager.employeeID

Неявный JOIN:

SELECT e1.name,
       e2.name,
       e1Manager.name
  FROM Employee e1,
       Employee e2,
       Employee e1Manager
 WHERE e1.managerEmployeeID = e1Manager.employeeID

Неверный SQL

Этот запрос НЕ работает в MSSQL 2000/2008 или MySQL:

SELECT e1.name, 
       e2.name, 
       e1Manager.name
  FROM Employee e1,
       Employee e2
 INNER JOIN Employee e1Manager 
    ON e1.managerEmployeeID = e1Manager.employeeID

В MS2000 я получаю сообщение об ошибке:

Префикс столбца 'e1' не соответствует с именем таблицы или псевдонимом в запросе.

В MySQL ошибка:

Неизвестный столбец 'e1.managerEmployeeID' в разделе "on".

Вопрос (ы)

  • Почему этот синтаксис недействителен?
  • Бонус: Есть ли способ заставить Hibernate использовать только явные JOINs?

Ответ 1

Это приводит к ошибке, поскольку в соответствии со стандартом SQL ключевое слово JOIN имеет более высокий приоритет, чем запятая. Липкой точкой является то, что псевдонимы таблицы не используются до тех пор, пока соответствующая таблица не будет оценена в предложении FROM.

Поэтому, когда вы ссылаетесь на e1 в своем выражении JOIN...ON, e1 еще не существует.

Пожалуйста, подождите, пока я исследую Hibernate и узнаю, можете ли вы убедить его использовать JOIN во всех случаях.


Хм. Кажется, что все на Hibernate.org перенаправляется на jboss.org. Так что сейчас нет возможности читать онлайн-документацию по HQL. Я уверен, что они в конечном итоге найдут свое имя.

Ответ 2

PostgreSQL также дает ошибку:

ERROR:  invalid reference to FROM-clause entry for table "e1"
LINE 7:     ON e1.managerEmployeeID = e1Manager.employeeID;
               ^
HINT:  There is an entry for table "e1", but it cannot be referenced from this part of the query.

Я думаю, проблема заключается в том, что когда вы присоединяетесь к двум таблицам a и b (e2 и e1Manager в этом случае), вы можете ссылаться только на эти две таблицы в предложении "ON". Таким образом, вы можете ссылаться на e2 и e1Manager в этом предложении ON, но не на e1.

Я думаю, что это распространяется так, что если у вас есть цепочка операторов "JOIN", вы можете ссылаться на другие таблицы в той же цепочке в предложениях "ON", но вы не можете пересечь ",". Итак, что-то вроде `a JOIN b ON a.a_id = b.a_id JOIN c ON c.b_id = b.b_id И c.a_id = a.a_id" разрешено.

Какой HQL вы используете для создания этого SQL? Что-то вроде "выберите e1.name, e2.name, e1.manager.name из Employee e1, Employee e2"?

Ответ 3

Это может быть немного не по теме, потому что это вообще не касается спящего режима, но комментарий от Bill Karwin действительно открыл мне глаза. Вместо того чтобы сначала писать неявное присоединение, сначала нужно выполнить явное соединение. Этот синтаксис особенно интересен, если у вас несколько неявных объединений.

Посмотрите следующий пример в MS SQL. Не все контакты имеют код страны, но все контакты имеют атрибут val, который будет отображаться в таблице Tbl. Таким образом, интуитивное решение не будет работать:

SELECT * FROM 
contacts, Tbl
LEFT OUTER JOIN country ON CtryCod = country.CtryCod 
WHERE val = Tbl.val

Вместо этого вы скорее захотите использовать следующий синтаксис:

SELECT * FROM 
contacts LEFT OUTER JOIN country ON CtryCod = country.CtryCod, 
Tbl
WHERE val = Tbl.val