Задний план

Версия БД Oracle:

SELECT * FROM v$version
WHERE banner LIKE 'Oracle%';
-- OUTPUT
Oracle Database 12c Enterprise Edition Release 12.1.0.2.0 - 64bit Production

Цель

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

Столы

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

ИЗМЕРЕНИЕ

Первичный ключ = ID

|  ID  |    MEAS_NAME    |
|------|-----------------|
| 1000 | "Measurement 1" |

MEASUREMENT_AREA

Первичный ключ = (ID, NAME)
Внешний ключ ID = MEASUREMENT.ID

|  ID  |    NAME   | AREA |
|------|-----------|------|
| 1000 | "Point 1" |   10 |
| 1000 | "Point 2" |   20 |

ОБЪЕМ ИЗМЕРЕНИЯ

Первичный ключ = (ID, NAME)
Внешний ключ ID = MEASUREMENT.ID

|  ID  |    NAME   | VOLUME |
|------|-----------|--------|
| 1000 | "Point 1" |    100 |
| 1000 | "Point 3" |    200 |

Ожидаемый результат

То, что я хочу, это следующий вывод:

|  ID  |    MEAS_NAME    |    NAME   | AREA | VOLUME |
|------|-----------------|-----------|------|--------|
| 1000 | "Measurement 1" | "Point 1" | 10   | 100    |
| 1000 | "Measurement 1" | "Point 2" | 20   | NULL   |
| 1000 | "Measurement 1" | "Point 3" | NULL | 200    |

Это означает, что если для определенного MEASUREMENT.ID и определенного NAME есть данные как в AREA, так и в VOLUME, поместите их в одну строку. В противном случае просто оставьте поле AREA или VOLUME пустым.

Запрос 1

Я придумал следующий оператор SQL, который не работает , он отбрасывает результаты из MEASUREMENT_VOLUME:

SELECT meas.ID AS "ID",
    meas.MEAS_NAME AS "MEAS_NAME",
    COALESCE (area.NAME, vol.NAME) as "NAME",
    area.AREA, vol.VOLUME
FROM MEASUREMENT meas
  LEFT JOIN MEASUREMENT_AREA area
    ON meas.ID = area.ID
  FULL JOIN MEASUREMENT_VOLUME vol
    ON meas.ID = vol.ID AND area.NAME = vol.NAME
WHERE meas.ID = 1000;

Запрос 2

Если я поставлю MEASUREMENT в последнюю очередь, это сработает, но запрос очень медленный :

SELECT meas.ID AS "ID",
    meas.MEAS_NAME AS "MEAS_NAME",
    COALESCE (area.NAME, vol.NAME) as "NAME",
    area.AREA, vol.VOLUME
FROM MEASUREMENT_AREA area
    FULL JOIN MEASUREMENT_VOLUME vol
        ON area.ID = vol.ID AND area.NAME = vol.NAME
    JOIN MEASUREMENT meas
        ON meas.ID = vol.ID OR meas.ID = area.ID
WHERE meas.ID = 1000;

Вопросы

  • Почему запрос 1 не работает?
  • Почему запрос 2 работает?
  • Какой самый эффективный способ добиться моего результата?

Ваша помощь очень ценится, я не эксперт по SQL.

Дополнительная информация

  • Одна строка в MEASUREMENT содержит метаданные только для одного измерения
  • Одно измерение может содержать сотни точек измерения, которые различаются по своему ИМЯ.
  • MEASUREMENT_AREA и MEASUREMENT_VOLUME намного больше, чем MEASUREMENT, каждая из них содержит более 10 миллионов строк
0
gucce 21 Авг 2018 в 16:13

4 ответа

Лучший ответ

Я в основном объединил ответы @dandarc и @ thorsten-kettner (большое спасибо за ваш ценный вклад):

Поскольку MEASUREMENT_VOLUME и MEASUREMENT_AREA намного больше, чем MEASUREMENT, я разделил JOIN:

SELECT *
FROM 
(
  SELECT *
  FROM MEASUREMENT
  JOIN MEASUREMENT_AREA
    USING(ID)
  WHERE ID = 1000
)
FULL JOIN
(
  SELECT *
  FROM MEASUREMENT
  JOIN MEASUREMENT_VOLUME
    USING(ID)
  WHERE ID = 1000
) USING (ID, MEAS_NAME, NAME);

Для моих целей важно, чтобы большие таблицы сначала объединялись в MEASUREMENT, а затем эти результаты комбинировались (также может работать с UNION ALL и GROUP BY, как предложено @dandarc).

Это эффективно решает мою проблему. FULL JOIN для трех таблиц занял более 3 минут с запросом 2. При таком решении это занимает секунды.

Обратите внимание, что моя реальная проблема более сложна, так как я выбрал десятки столбцов и не могу просто использовать SELECT *. Таким образом, я не могу использовать USING(ID, MEAS_NAME, NAME), но мне нужно придерживаться синтаксиса ON.

0
gucce 29 Авг 2018 в 12:08

Почему один запрос работает, а другой нет, было объяснено в другом ответе. Поэтому я просто добавляю, как бы я написал запрос:

Вы хотите полное внешнее объединение measurement_area и measurement_volume. Сделайте это в подзапросе и присоединитесь к таблице measurement:

select id, m.meas_name, data.name, data.area, data.volume
from measurement m
join 
(
  select id, name, ma.area, mv.volume
  from measurement_area ma
  full outer join measurement_volume mv using (id, name)
) data using(id);
6
Thorsten Kettner 21 Авг 2018 в 17:19

Попробуй это -

SELECT meas.ID AS "ID",
meas.MEAS_NAME AS "MEAS_NAME",
COALESCE (area.NAME, vol.NAME) as "NAME",
area.AREA, vol.VOLUME
FROM MEASUREMENT meas
LEFT JOIN MEASUREMENT_AREA area
ON meas.ID = area.ID
LEFT JOIN MEASUREMENT_VOLUME vol
ON meas.ID = vol.ID
WHERE meas.ID = 1000;

Просто удалите area.NAME = vol.NAME из вашего первого запроса.

-1
Ankit Bajpai 21 Авг 2018 в 14:38

Почему запрос 1 не работает?

...
ON meas.ID = vol.ID AND area.NAME = vol.name
...
where meas.ID = 1000

Ваше условие полного соединения имеет area.name = vol.name, что означает, что строка в таблице MEAS_VOLUME с именем «Точка 3» не совпадает. С помощью только объединения вы получаете строку из этой таблицы, но поскольку она не соответствует условию, значение имеют только поля из этой таблицы - measure.ID равно null вместе с MEAS_NAME и AREA. Но затем вы отфильтровываете строки, где ID не равен 1000. Если вы удалите предложение where в этом запросе, вы получите:

ID      MEAS_NAME       NAME    AREA    VOLUME
1000    Measurement 1   Point 1 10      100
                        Point 3         200
1000    Measurement 1   Point 2 20  

Почему работает запрос 2?

В основном потому, что это правильно для ответа на вопрос. Кажется, вы узнали, что area.ID и vol.ID не всегда доступны, поэтому вы сопоставляете ИЗМЕРЕНИЕ с любым из них в соединении, что означает, что ваш запрос работает.

Какой самый эффективный способ добиться результата?

Без дополнительной информации трудно ответить - как выглядит ваш план выполнения? Какие индексы доступны? Что используется?

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

Отредактировано для добавления - вот еще одна правильная версия вашего запроса, которая может выполняться быстрее, чем Query 2. Получает OR из условий соединения, что иногда усложняет жизнь оптимизатору.

with MEASUREMENT as
(
  select 1000 as ID, 'Measurement 1' as MEAS_NAME from dual
), MEASUREMENT_AREA as
(
   select 1000 as ID, 'Point 1' as NAME, 10 as AREA from dual union all
   select 1000 as ID, 'Point 2' as NAME, 20 as AREA from dual
), MEASUREMENT_VOLUME as
(
   select 1000 as ID, 'Point 1' as NAME, 100 as VOLUME from dual union all
   select 1000 as ID, 'Point 3' as NAME, 200 as VOLUME from dual
),
base_qry as (
    select meas.ID, meas_name, area.name, area, null as volume
    FROM MEASUREMENT meas
      LEFT JOIN MEASUREMENT_AREA area
        ON meas.ID = area.ID
    WHERE meas.ID = 1000

    union all 

    select meas.ID, meas_name, vol.name, null, volume
    FROM MEASUREMENT meas
      LEFT JOIN MEASUREMENT_VOLUME vol
        ON meas.ID = vol.ID
    WHERE meas.ID = 1000)
select ID, MEAS_NAME, NAME,
    max(AREA) as AREA,
    max(VOLUME) as VOLUME
from base_qry
group by ID, MEAS_NAME, NAME
order by 1,2,3
;
4
dandarc 21 Авг 2018 в 16:59
51949532