Задний план
Версия БД 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 миллионов строк
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
.
Почему один запрос работает, а другой нет, было объяснено в другом ответе. Поэтому я просто добавляю, как бы я написал запрос:
Вы хотите полное внешнее объединение 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);
Попробуй это -
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 не работает?
...
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
;
Новые вопросы
sql
Язык структурированных запросов (SQL) - это язык запросов к базам данных. Вопросы должны включать примеры кода, структуру таблицы, примеры данных и тег для используемой реализации СУБД (например, MySQL, PostgreSQL, Oracle, MS SQL Server, IBM DB2 и т. Д.). Если ваш вопрос относится исключительно к конкретной СУБД (использует определенные расширения / функции), используйте вместо этого тег этой СУБД. Ответы на вопросы, помеченные SQL, должны использовать стандарт ISO / IEC SQL.