2013-05-08 4 views
7

У меня есть три таблицы:Проблемы с INNER JOIN и LEFT/RIGHT OUTER JOIN

  • Заказы
    • OrderId, внутр PK
    • CustomerId, внутр FK Клиенту, NULL разрешено


  • Клиенты
    • CUSTOMERID, внутр PK
    • CompanyID, Int FK в компании, NULL не допускается


  • Companie s
    • CompanyID, Int PK
    • Имя, NVARCHAR (50)

Я хочу, чтобы выбрать все заказы, независимо от того, если у них есть клиент или нет, и если у них есть клиент, а также название компании клиента.

Если я использую этот запрос ...

SELECT Orders.OrderId, Customers.CustomerId, Companies.Name 
FROM Orders 
     LEFT OUTER JOIN Customers 
      ON Orders.CustomerId = Customers.CustomerId 
     INNER JOIN Companies 
      OM Customers.CompanyId = Companies.CompanyId 

... он возвращает только те заказы, которые имеют клиента. Если я заменяю INNER JOIN по LEFT OUTER JOIN ...

SELECT Orders.OrderId, Customers.CustomerId, Companies.Name 
FROM Orders 
     LEFT OUTER JOIN Customers 
      ON Orders.CustomerId = Customers.CustomerId 
     LEFT OUTER JOIN Companies 
      OM Customers.CompanyId = Companies.CompanyId 

... это работает, но я не понимаю, почему это необходимо, потому что отношения между Customers и Companies требуется: Клиент обязательно иметь компанию.

Альтернативный подход, который работает хорошо, кажется:

SELECT Orders.OrderId, Customers.CustomerId, Companies.Name 
FROM Companies 
     INNER JOIN Customers 
      ON Companies.CompanyId = Customers.CompanyId 
     RIGHT OUTER JOIN Orders 
      OM Customers.CustomerId Orders.CustomerId 

Этот запрос имеет ряд внутренних и внешних соединений, что я ожидал, но проблема в том, что трудно читать для меня, потому что у меня есть мой запрос в качестве запроса заказов в виду, где заказ является «корнем» выбора, а не компанией. Кроме того, использование RIGHT OUTER JOIN для меня довольно незнакомо.

Последний запрос представляет собой небольшую часть запроса, созданного конструктором для отчетов отчетов служб SQL Server. Я пытаюсь написать запрос вручную без поверхности конструктора, потому что он очень переполнен, и у меня возникают проблемы с сохранением запроса после многих изменений, и в будущем ожидается больше изменений. Итак, я хочу как-то дать запрос понятной структуре.

Вопросы:

  1. Почему не запрашивает 1 работу, как я ожидал?
  2. Является ли запрос 2 правильным решением, хотя (или потому что?) Он использует два LEFT OTHER JOINS?
  3. Является ли запрос 3 правильным решением?
  4. Есть ли лучший способ написать запрос?
  5. Есть ли общие правила и практики, как написать запрос с большим количеством внешних и внутренних соединений в удобной для чтения форме?
+0

Я не говорю, что вы не правы, но как может заказ не иметь клиента. Заказ - это комбинация продукта и клиента, безусловно, – DavidB

+0

@DavidB: Не настоящая модель. Просто подумайте о «анонимных» заказах, где производственная компания имеет внутренние заказы на производство на складе без ссылки на клиентов ... или что-то в этом роде. – Slauma

+1

Одним из решений здесь является наличие «анонимной» записи клиента, которую вы можете сопоставить для них, а не без клиента. Скорее всего, этот подход поможет и многим другим отчетам. –

ответ

11

Семантически, соединения обрабатываются в порядке их появления в предложении from. (Они не могут быть выполнены на самом деле в этом порядке из-за оптимизации SQL, но порядок важен для определения результирующего набора.)

Итак, когда вы делаете:

from orders left outer join customers inner join companies 

(Я ухожу из в on положения, которые являются развлечением для этой цели)

SQL, интерпретируется как:.

from (orders left outer join customers) inner join companies 

вы делаете inner join, поэтому значения должны отображаться с обеих сторон. В вашем случае это отменяет эффект left outer join.

Вы хотите:

from orders left outer join (customers inner join companies) 

Вот некоторые решения.

Мое предпочтительное решение - использовать left outer join для всех соединений. На самом деле, для удобства чтения и обслуживания, почти каждый запрос, который я пишу, будет только left outer join или [inner] join, соединяющий таблицы. Необходимо проанализировать запрос, чтобы понять семантику объединений, кажется, это ненужное усилие, если вы можете писать запросы в согласованной форме.

Другим решением является использование круглых скобок:

from orders left outer join (customers inner join companies) 

Другим решением является подзапрос:

from orders left outer join (select . . . from customers inner join companies) cc 
+0

Спасибо, отличное объяснение! Подзапрос имеет какое-либо влияние на производительность по сравнению с решением круглых скобок или же план запроса будет одинаковым? – Slauma

+2

@Slauma. , , В SQL Server я не думаю, что это влияет. Двигатель оптимизирует весь запрос. В других базах данных это может иметь значение (MySQL, например, создает подзапросы, так что план запроса будет отличаться в этой базе данных). –

2

запроса 1 имеет INNER JOIN на компании, что означает заказ необходимо иметь непросроченный клиент (CompanyID) Если вы хотите использовать INNER JOIN, это может быть, как это

SELECT Orders.OrderId, a.CustomerId, a.Name 
FROM Orders 
LEFT JOIN (
    SELECT Customers.CustomerId, Companies.Name 
    FROM Customers 
    INNER JOIN Companies 
      OM Customers.CompanyId = Companies.CompanyId 
) a 
    ON Orders.CustomerId = a.CustomerId 
4
  1. Запрос 1: Поскольку у вас есть INNER JOIN на Клиентах, LEFT JOIN фактически является INNER JOIN.
  2. Запрос 2 является правильным, потому что вы хотите видеть все Заказы независимо от качества/состояния данных.
  3. Мне нравится избегать RIGHT JOIN s в целом, поскольку это сбивает с толку некоторые разработчики и поэтому менее читабельны.Вы можете написать свой запрос таким образом, чтобы сделать то же самое с эффективным использованием LEFT JOIN s.
  4. Query 2 моя рекомендация для чего-то просто, как это.
  5. Одно общее правило ... После того, как вы введете OUTER JOIN в свой запрос, JOIN s, которые должны следовать, также должны быть OUTER JOIN s. В противном случае вы MAY исключить строки, которые вы не намеревались.
+1

Я начал писать ответ, но после того, как он появился, я остановился, потому что это именно то, что я собирался сказать по всем вопросам. Возможно, я мог бы использовать больше объяснений, но общее правило должно быть: сначала используйте свои INNER JOINs (для фильтрации строк) и LEFT JOINs (для получения дополнительных данных). –

2

1) Это не работает, потому что, когда вы INNER JOIN к Companies вы сделаете это необходимо существовать в полноте объединения, но так как Customer не существует для того, что нет никакого способа, чтобы связать Companies запись вернемся к порядку и, следовательно, он не будет возвращен.

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

3) Третий запрос хорошо, но некрасиво. Вы присоединяетесь к таблицам компаний и клиентов, а затем говорите, что независимо от того, что находится в этом наборе результатов, я хочу все от Orders.

4) Я бы, вероятно, присоединиться к клиентам и компаниям в подзапрос и оставили присоединиться к его обратно к заказам.

Запрос:

SELECT Orders.OrderId, 
     Subquery.CustomerId, 
     Subquery.Name 
FROM Orders 
LEFT OUTER JOIN 
     (Select Customers.CustomerID, 
       Companies.Name 
     From Customers 
     INNER JOIN Companies 
       ON Customers.CompanyId = Companies.CompanyId) Subquery 
     On Orders.CustomerID = Subquery.CustomerID 

5) Это намного более легко ответить с помощью поиска Google. Я уверен, что есть более полная информация, которую я мог бы написать через пару минут.

3

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

SELECT Orders.OrderId, Customers.CustomerId, Companies.Name 
FROM Orders 
LEFT OUTER JOIN Customers 
    INNER JOIN Companies 
     ON Customers.CompanyId = Companies.CompanyId 
    ON Orders.CustomerId = Customers.CustomerId 
+0

Действительно, круглые скобки, похоже, не требуются, я только что протестировал их. Благодаря! – Slauma