Получение отдельных строк из левого внешнего соединения - PullRequest
20 голосов
/ 25 апреля 2009

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

Существует три таблицы Table1, Table2 и Table1Table2Map. Table1 имеет много-много взаимосвязей с Table2 и отображается через таблицу Table1Table2Map. Но так как Таблица 1 - моя главная таблица, отношения практически похожи на один со многими.

Мое приложение генерирует sql, который в основном дает набор результатов, содержащий строки из всех этих таблиц. Предложение select и join не изменяются, тогда как предложение where генерируется на основе взаимодействия с пользователем. В любом случае я не хочу дублировать строки таблицы 1 в моем наборе результатов, так как это основная таблица для отображения результатов. Сейчас генерируемый запрос выглядит так:

select distinct Table1.Id as Id, Table1.Name, Table2.Description from Table1
left outer join Table1Table2Map on (Table1Table2Map.Table1Id = Table1.Id)
left outer join Table2 on (Table2.Id = Table1Table2Map.Table2Id)

Для простоты я исключил предложение where. Проблема в том, что в Table2 есть несколько строк для Table1, хотя я и сказал отдельно от Table1.Id в наборе результатов есть повторяющиеся строки в Table1, так как он должен выбрать все подходящие строки в Table2.

Для более подробного рассмотрения учтите, что для строки в Table1 с Id = 1 есть две строки в Table1Table2Map (1, 1) и (1, 2), сопоставляющие Table1 с двумя строками в Table2 с идентификаторами 1, 2. Выше упомянутый запрос возвращает повторяющиеся строки для этого случая. Теперь я хочу, чтобы запрос возвращал строку таблицы 1 с идентификатором 1 только один раз. Это связано с тем, что в Таблице 2 есть только одна строка, похожая на активное значение для соответствующей записи в Таблице 1 (эта информация находится в таблице отображения). Есть ли способ избежать дублирующихся строк в Таблице 1.

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

Ответы [ 6 ]

24 голосов
/ 25 апреля 2009

Попробуйте:

left outer join (select distinct YOUR_COLUMNS_HERE ...) SUBQUERY_ALIAS on ...

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

12 голосов
/ 25 февраля 2013

Вы можете использовать GROUP BY на Table1.Id, и это избавит от лишних строк. Вам не нужно беспокоиться о механике на стороне соединения.

Я придумал это решение в большом запросе, и это решение не сильно повлияло на время запроса.

ПРИМЕЧАНИЕ: я отвечаю на этот вопрос через 3 года после того, как его спросили, но это может помочь кому-то, кому я верю.

6 голосов
/ 01 мая 2009

Вы можете переписать свои левые объединения, чтобы они были внешними, чтобы вы могли использовать топ 1 и порядок следующим образом:

select Table1.Id as Id, Table1.Name, Table2.Description 
from Table1
outer apply (
   select top 1 *
   from Table1Table2Map
   where (Table1Table2Map.Table1Id = Table1.Id) and Table1Table2Map.IsActive = 1
   order by somethingCol 
) t1t2
outer apply (
   select top 1 *
   from Table2
   where (Table2.Id = Table1Table2Map.Table2Id)
) t2;

Обратите внимание, что внешнее применение без "top" или "order by" в точности эквивалентно левому внешнему объединению, оно просто дает вам немного больше контроля. (перекрестное применение эквивалентно внутреннему соединению).

Вы также можете сделать нечто подобное, используя функцию row_number ():

 select * from (
      select distinct Table1.Id as Id, Table1.Name, Table2.Description,
        rowNum = row_number() over ( partition by table1.id order by something )
      from Table1
      left outer join Table1Table2Map on (Table1Table2Map.Table1Id = Table1.Id)
      left outer join Table2 on (Table2.Id = Table1Table2Map.Table2Id)
 ) x
 where rowNum = 1;

Большая часть этого не применяется, если флаг IsActive может сузить ваши другие таблицы до одной строки, но они могут оказаться полезными для вас.

3 голосов
/ 25 апреля 2009

Чтобы уточнить один момент: вы сказали, что в Таблице 2 есть только одна «активная» строка на строку в Таблице1. Эта строка не помечена как активная, чтобы вы могли поместить ее в предложение where? Или есть какое-то волшебство в динамических условиях, предоставляемых пользователем, которые определяют, что активно, а что нет.

Если вам не нужно ничего выбирать из Таблицы2, решение относительно простое: вы можете использовать функцию EXISTS, но, поскольку вы добавили TAble2.Description в предложение, я предполагаю, что это не так.

В основном, что отличает соответствующие строки в Таблице 2 от не относящихся к делу? Это активный флаг или динамическое условие? Первый ряд? Это действительно, как вы должны удалять дубликаты.

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

2 голосов
/ 25 апреля 2009

Вы должны включить условие активности в ваше объединение (и не нужно различать):

select Table1.Id as Id, Table1.Name, Table2.Description from Table1
left outer join Table1Table2Map on (Table1Table2Map.Table1Id = Table1.Id) and Table1Table2Map.IsActive = 1
left outer join Table2 on (Table2.Id = Table1Table2Map.Table2Id)
1 голос
/ 25 апреля 2009

Если вы хотите отобразить несколько строк из таблицы2, у вас будут отображаться дубликаты данных из таблицы1. Если вы хотите, чтобы вы могли использовать агрегатную функцию (IE Max, Min) для table2, это исключило бы повторяющиеся строки из table1, но также скрыло бы некоторые данные из таблицы 2.

См. Также мой ответ на вопрос # 70161 для дополнительного объяснения

...