Необходимо создать выражение во внешнем соединении, которое возвращает только одну строку - PullRequest
4 голосов
/ 27 октября 2008

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

SELECT a.user_id
FROM table1 a
    ,table2 b
WHERE a.user_id = b.user_id
AND a.sub_id = (
    SELECT min(c.sub_id)
    FROM table2 c
    WHERE b.sub_id = c.sub_id
    )

Подзапрос находит минимальное значение в таблице один ко многим для этого конкретного пользователя.

Это работает, но я боюсь, что делать коррелированные подзапросы, когда таблицы 1 и 2 становятся очень большими, противно. Есть ли способ лучше? Я пытаюсь придумать способ получить соединения, чтобы сделать это, но я не вижу этого. Также не помогает мне сказать, «где rowcount = 1» или «top 1», потому что я не пытаюсь исправить вышеупомянутый запрос, я ДОБАВЛЯЮ вышеупомянутое к уже сложному запросу.

Ответы [ 6 ]

1 голос
/ 27 октября 2008

Несколько быстрых пунктов:

  1. Вы должны иметь четкие бизнес-правила. Если запрос возвращает более одной строки, вам нужно подумать о том, почему (помимо просто «это отношение 1: много - ПОЧЕМУ это отношение 1: много?). Вы должны придумать бизнес-решение, а не просто использовать» min ", потому что это дает вам 1 строку. Бизнес-решение может быть просто" возьми первый ", и в этом случае min может быть ответом, но вам нужно убедиться, что это осознанное решение.
  2. Вы действительно должны попытаться использовать синтаксис ANSI для объединений. Не только потому, что он является стандартным, но и потому, что ваш синтаксис на самом деле не делает то, что, как вы думаете, он делает (это не внешнее соединение) и некоторые вещи просто невозможно сделать с вашим синтаксисом.

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

SELECT
     a.user_id, b.*
FROM
     dbo.Table_1 a
LEFT OUTER JOIN dbo.Table_2 b ON b.user_id = a.user_id AND b.sub_id = a.sub_id
LEFT OUTER JOIN dbo.Table_2 c ON c.user_id = a.user_id AND c.sub_id < b.sub_id
WHERE
     c.user_id IS NULL

Вам нужно будет проверить это, чтобы увидеть, действительно ли он дает то, что вы хотите, и вам может понадобиться настроить его, но основная идея состоит в том, чтобы использовать второй LEFT OUTER JOIN, чтобы убедиться, что нет строк, которые существуют с меньший sub_id, чем найденный в первом левом внешнем соединении (если таковой найден). Вы можете настроить критерии во втором левом внешнем соединении в зависимости от окончательных бизнес-правил.

1 голос
/ 27 октября 2008

В MySql вы можете убедиться, что любой запрос возвращает максимум X строк, используя

select *
from foo
where bar = 1
limit X;

К сожалению, я вполне уверен, что это специфичное для MySQL расширение SQL. Однако поиск в Google для чего-то вроде «mysql sybase limit» может найти эквивалент для Sybase.

0 голосов
/ 27 октября 2008

Ну, у вас уже есть запрос, который работает. Если вы обеспокоены скоростью, вы можете

  • Добавить поле в table2, которое определяет, какой sub_id является «первый» или

  • Отслеживать первичный ключ таблицы 2 в таблице table1 или в другой таблице

0 голосов
/ 27 октября 2008

Прежде всего, я считаю, что запрос, который вы пытаетесь написать в качестве примера:

select a.user_id 
from table1 a, table2 b 
where a.user_id = b.user_id 
and b.sub_id = (select min(c.sub_id) 
                from table2 c 
                where b.user_id = c.user_id)

За исключением того, что вы хотели внешнего соединения (которое, я думаю, кто-то отредактировал синтаксис Oracle).

select a.user_id 
from table1 a
left outer join table2 b on a.user_id = b.user_id 
where b.sub_id = (select min(c.sub_id) 
                from table2 c 
                where b.user_id = c.user_id)
0 голосов
/ 27 октября 2008

Как насчет:

select a.user_id 
from table1 a
where exists (select null from table2 b 
              where a.user_id = b.user_id 
             )
0 голосов
/ 27 октября 2008

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

SELECT
  a.user_id 
FROM 
  table1 a
    LEFT OUTER JOIN table2 b ON (a.user_id = b.user_id)
GROUP BY
  a.user_id

Боюсь, что единственный способ использовать вложенные запросы:

Разница между этим запросом и вашим примером заключается в том, что «вспомогательная таблица» генерируется только один раз, однако в вашем примере вы генерируете «вспомогательную таблицу» для каждой строки в table1 (но может зависеть от компилятора, поэтому вы использовать анализатор запросов для проверки производительности).

SELECT
  a.user_id,
  b.sub_id
FROM 
  table1 a
    LEFT OUTER JOIN (
      SELECT
        user_id,
        min(sub_id) as sub_id,
      FROM
        table2
      GROUP BY
        user_id
    ) b ON (a.user_id = b.user_id)

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

Пример временной таблицы:

SELECT
  user_id
INTO
  #table1
FROM 
  table1
WHERE
  .....

SELECT
  a.user_id,
  min(b.sub_id) as sub_id,
INTO
  #table2
FROM
  #table1 a
    INNER JOIN table2 b ON (a.user_id = b.user_id)
GROUP BY
  a.user_id

SELECT
  a.*,
  b.sub_id
from
  #table1 a
    LEFT OUTER JOIN #table2 b ON (a.user_id = b.user_id)
...