У меня есть 2 запроса SQL (Oracle 11g):

select x1,x2,x3
from X
where x1 = a and x2 = b;

select x1,x2,x3
from X
where x1 = a and x2 = b and x3 = c; 

Они выбирают те же столбцы в таблице X, но с разными условиями. Я использую UNION для результата слияния:

select x1,x2,x3,'Q1' as QueryCode
from X
where x1 = a and x2 = b
  UNION
select x1,x2,x3,'Q2' as QueryCode
from X
where x1 = a and x2 = b and x3 = c; 

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

2
Hana 17 Окт 2015 в 10:21

2 ответа

Лучший ответ

Мы можем получить все необходимые строки, добавив OR между первым и вторым фильтрами, а затем разделить их в UNION. И используя подсказку /*+ materialize */, мы гарантируем, что данные из original_table выбираются только один раз, а результаты фильтрации сохраняются в памяти как sub_table для текущего выполнения запроса.

Да, дублировать код (x1 = a AND x2 = b) и (x1 = a AND x2 = b AND x3 = c) некрасиво, но в этом случае слишком большие данные мы делаем еще один хороший компромисс: небольшое дублирование для отличной производительности. .

WITH
  sub_table AS (SELECT /*+ materialize */ x1, x2, x3
                  FROM original_table
                 WHERE (x1 = a AND x2 = b)             -- first filter
                    OR (x1 = a AND x2 = b AND x3 = c)  -- second filter
  )
SELECT  x1, x2, x3, 'Q1' AS querycode
  FROM sub_table
 WHERE x1 = a AND x2 = b              -- first filter (repeated)

UNION

SELECT x1, x2, x3, 'Q2' AS querycode
  FROM sub_table
 WHERE x1 = a AND x2 = b AND x3 = c;  -- second filter (repeated)

Если нас не заботит порядок строк, есть другой подход без UNION:

SELECT x1, x2, x3,
       CASE
       WHEN x1 = a AND x2 = b THEN 'Q1'
       WHEN x1 = a AND x2 = b AND x3 = c THEN 'Q2'
       END AS marker
  FROM original_table
 WHERE CASE
       WHEN x1 = a AND x2 = b THEN 'Q1'
       WHEN x1 = a AND x2 = b AND x3 = c THEN 'Q2'
       END IS NOT NULL;

По-прежнему есть несовершенство дублирования кода, но это плата за запросы к таблице с большими данными. Другими словами, для небольшой таблицы мы могли бы использовать краткий код с подзапросом, который требует больших затрат памяти:

SELECT *
  FROM (SELECT x1, x2, x3,
               CASE
               WHEN x1 = a AND x2 = b THEN 'Q1'
               WHEN x1 = a AND x2 = b AND x3 = c THEN 'Q2'
               END AS marker
          FROM original_table) t
 WHERE t.marker IS NOT NULL;

И, наконец, в Oracle 12c мы можем инкапсулировать этот дублированный CASE в функцию:

WITH
  FUNCTION get_marker(x1 CHAR, x2 CHAR, x3 CHAR) RETURN CHAR DETERMINISTIC
  IS
    BEGIN

      RETURN CASE
             WHEN x1 = a AND x2 = b THEN 'Q1'
             WHEN x1 = a AND x2 = b AND x3 = c THEN 'Q2'
             END;

    END
SELECT x1, x2, x3,
       get_marker(x1, x2, x3) AS marker
  FROM original_table
 WHERE get_marker(x1, x2, x3) IS NOT NULL;
1
diziaq 19 Фев 2016 в 04:22

Если для X3='c' вам действительно нужны 2 строки, одна с Q1, а другая с Q1, то запрос объединения является лучшим.

Я попытался создать CTE с помощью X1=a and X2=b, а потом сделал объединение. Стоимость была немного больше, чем обычный запрос на объединение.

Так что вперед с union.

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

1
Utsav 17 Окт 2015 в 08:01