Как превратить 2 запроса с общими столбцами (A, B) и (A, C) в один (A, B, C)? - PullRequest
3 голосов
/ 24 ноября 2010

В настоящее время у меня есть 2 запроса, которые возвращают

PRODUCER                       FirstQueryColumn       
------------------------------ ---------------------- 
aaaaaaaaaaaa                   1                      
bbbbbbbbbbb                    1                      

PRODUCER                       SecondQueryColumn      
------------------------------ ---------------------- 
aaaaaaaaaaaa                   2                      
bbbbbbbbbbb                    1                      

Что я хотел бы знать, так это как мне сделать так, чтобы в одном запросе я мог иметь одни и те же данные, то естьЯ хочу что-то, что даст (Producer, FirstQueryColumn, SecondQueryColumn).

Как я могу это сделать?

Вот мои текущие запросы:

select Producers.name Prod, count(Animals.idanimal) AnimalsBought
from AnimalsBought, Animals, Producers
where (AnimalsBought.idanimal = Animals.idanimal) and (Animals.owner = Producers.nif) group by Producers.name;

select Producers.name Prod, count(Animals.idanimal) AnimalsExploration
from AnimalsExploration, Animals, Producers
where (AnimalsExploration.idanimal = Animals.idanimal) and (Animals.owner = Producers.nif) group by Producers.name;

Как видите,в этом случае объединение мало что даст:

select Producers.name Prod, count(AnimalsBought.idanimal) AnimalsBought, count(AnimalsExploration.idanimal) AnimalsExploration
from Producers, Animals, AnimalsBought, AnimalsExploration
where (AnimalsExploration.idanimal = Animals.idanimal) and (Animals.owner = Producers.nif) group by Producers.name;

или я что-то не так делаю?

Ответы [ 9 ]

5 голосов
/ 24 ноября 2010

Сначала представьте, что 2 запроса были просто таблицами.Вы могли бы сделать это:

select a.producer, a.firstquerycolumn, b.secondquerycolumn
from table1 a
join table2 b on b.producer = a.producer

Вы можете заменить каждую таблицу запросом (также называемым встроенным представлением):

select a.Prod, a.AnimalsBought, b.AnimalsExploration
from
( select Producers.name Prod, count(Animals.idanimal) AnimalsBought
  from AnimalsBought, Animals, Producers
  where (AnimalsBought.idanimal = Animals.idanimal) 
  and (Animals.owner = Producers.nif) 
  group by Producers.name
) a
join
( select Producers.name Prod, count(Animals.idanimal) AnimalsExploration
  from AnimalsExploration, Animals, Producers
  where (AnimalsExploration.idanimal = Animals.idanimal) 
  and (Animals.owner = Producers.nif)
  group by Producers.name
) b
on a.Prod = b.Prod;

Возможно, вам потребуется изменить мое "объединение"в «полное внешнее соединение», если один запрос может вернуть данные для производителя, а другой нет.Я также был бы склонен реструктурировать запрос следующим образом, сделав основной запрос к внешнему источнику, присоединенному к двум подзапросам (без удаленных производителей):

select Producers.name Prod, a.AnimalsBought, b.AnimalsExploration
from Producers
left outer join ( select Animals.owner, count(AnimalsBought.idanimal) AnimalsBought
                    from AnimalsBought, Animals
                   where AnimalsBought.idanimal = Animals.idanimal
                   group by Animals.owner
                ) a
           on a.owner = Producers.nif
left outer join ( select Animals.owner, count(Animals.idanimal) AnimalsExploration
                    from AnimalsExploration, Animals
                   where AnimalsExploration.idanimal = Animals.idanimal
                   group by Animals.owner
                ) b
           on b.owner = Producers.nif;

(именно этот тип запроса я проверялпроизводительность ниже).


Вместо того, чтобы раздувать этот ответ с информацией, которая, вероятно, не представляет интереса для OP, мои замечания об относительной производительности скалярных подзапросов и встроенных представлений в Oracle (запрошенные PerformanceDBA) теперьв автономном режиме здесь: Примечания по производительности

4 голосов
/ 24 ноября 2010
select

tab1.producer
tab1.cola
tab2.colb

from
     (query a) tab1
inner join
     (query b) tab2
on
      tab1.producer = tab2.producer

Возможно, вы захотите изменить соединение на полное внешнее объединение, если в каждом запросе нет каждого производителя.

3 голосов
/ 25 ноября 2010

Редакция от 28 ноября 2010 года

или я что-то не так делаю?

Да.Вам известны два простых набора, которые вы знаете, и вы сосредоточены на создании третьего набора из этих двух наборов.

  1. Использование реляционного подхода (реляционная модель; код и дата)к проблеме означает думать это с точки зрения множеств.Оба ваших исходных набора являются проекциями данных в базе данных.Поэтому вместо того, чтобы объединять их, результаты, как есть, в третий набор, вам нужно думать только о новом наборе, о новой проекции непосредственно из данных в базе данных.

  2. Во-вторых, после того, как вы это сделаете, легко перейти к прямому подходу к языку SQL, чтобы получить результат.Если (1) понят, требуемое программирование - простая логическая проблема.Конечно, стандарт SQL ISO / IEC / ANSI разрешает различные конструкции кода, и есть много способов получить один и тот же результат;я говорю о логике программы;простота;эффективность;а не нет.

    Это будет выполняться быстрее на любой платформе, совместимой с ISO / IEC / ANSI SQL.

    • Общеизвестно, что Oracle несовместим или сильно расширяет определения стандартов, чтобы казаться совместимыми, поэтому я принимаю, что этот код может не выполняться быстрее в Oracle,В частности, люди из Oracle, кажется, знают, что скалярные подзапросы (которые здесь требуют логики и простоты) не работают должным образом, и вместо этого они используют другие конструкции.
    • Любая такая альтернативная конструкция является отклонением от (1) и (2).).(Например, ответ Тони Эндрюса буквально принимает вопрос ОП, использует два известных набора; объединяет их; передает их в 3-й набор; затем извлекает из этого 3 столбца.)

Попробуйте это, это ориентированный на наборы метод манипулирования реляционными базами данных, производящий новый отдельный набор непосредственно из данных (1), независимо от двух известных наборов.Затем он использует язык SQL простым, логичным способом (2), чтобы получить это требование.Да, это два скалярных подзапроса (возвращает одно значение):

<code>SELECT  Producers.name Prod, 
        (SELECT COUNT(Animals.idanimal)
            FROM Animals,
                 AnimalsBought
            WHERE p.nif = Animals.owner
            AND   Animals.idanimal = AnimalsBought.idanimal
            ) AnimalsBought,
        (SELECT COUNT(Animals.idanimal)
            FROM Animals,
                 AnimalsExploration
            WHERE p.nif = Animals.owner
            AND   Animals.idanimal = AnimalsExploration.idanimal
            ) AnimalsExploration
    FROM Producers p

Сравнение 29 ноября 2010 г.

Этот раздел актуален только для людей, интересующихсяВ тестировании участвовали мы с Тони и их выводы

Меня беспокоило, что Тони сообщил, что Oracle выполняет свой запрос за 2 секунды против моего запроса за 5 секунд.Я не мог смириться с мыслью, что код вдвое длиннее;который использует в 3 раза больше наборов, может работать быстрее.

Я провел тест на Sybase.У меня уже есть доступная структура бенчмарков, которая позволила бы тестировать очень похожий код (тест на моих таблицах не должен был объединять две таблицы, но я включил его, чтобы он был как можно ближе к вопросу OP).2.0M CustomerTransactions (Animals) распространяется на 100 клиентов (производителей).То есть оба запроса выдают один и тот же набор результатов (100 клиентов (производителей) по 3 столбцам) из 2,0 млн точек данных или 100 х 20000 скаляров.Давайте точно определим их, чтобы мы могли лучше понять и сравнить их.Сначала используется DDL для используемых структур:

Ссылка на DDL

1_Set_Scalar_Subquery

Точныйэквивалент моего кода, приведенного выше, для доступных структур бенчмарка:

  • Из описания OP, структура требования (1) представляет собой список клиентов (производителей).
  • Это легко перевести (2) в простой запрос: SELECT ... FROM Customer (Producer)
  • Далее нам понадобятся еще два столбца, специфические вещи (1) re Customer (Producer), которые представляют собой данные, которые могут бытьполучено (не доступно в виде физических столбцов) из базы данных
  • Это легко сделать (2) с помощью двух скалярных запросов подзапросов

Ссылка на 1_Set_Scalar_Subquery Code &Синхронизация

  • 1083, 1073, 1103, 1073, 1080 мс

  • Статистика Sybase показывает 3 сканирования:
    Клиент (Производитель) сканируется один раз
    CustomerTransaction (Животное) сканируется дважды (один раз для каждого скалярного подзапроса)

3_Set_Inline_Query

Точный эквивалент кода Тони, отображаемый для доступных структур эталонных тестов:

  • Насколько я понимаю, из комментариев Тони он взялОП буквально;поместил два известных набора в предложение FROM;присоединился к ним;создание 3-го набора (3-й внешний SELECT), который выставляется

    • Когда я набирал код, я понял, что 1_Set_Scalar_Subquery гораздо более эффективен в дополнительном аспектепотому что он не имеет 2 x GROUP BYs, которые присутствуют в 2 исходных наборах (т. е. исходные наборы были неэффективными с самого начала; их можно улучшить)

Ссылка на 3_Set_Inline_Query Code & Timings

  • 1820, 1850, 1846, 1843, 1850 мс
    То есть На 70% медленнее .

  • Статистика Sybase показывает 9 сканирований:
    Клиент (производитель) сканируется дважды (внешний запрос) CustomerTransaction (животное) сканируется дважды (2 встроенных набора)
    Рабочий стол 2 сканируется 3раз (объединенный набор)
    Рабочие таблицы 1 и 3 сканируются по одному разу (для GROUP BYs)

Конечно, 70% разница будет преувеличена на занятом сервере.

3_Set_Inline_Query_Improved

Поскольку исходные наборы, предоставленные OP, были неэффективными, т.е.это может быть легко улучшено, 2 x GROUP BYs, которые требуют рабочих столов, могут быть удалены, и т.д .;чтобы создать что-то более разумное, оставаясь встроенным, по сравнению со скалярным подзапросом, я это сделал.Конечно, это означает, что вместо манипулирования большим объединенным набором, а затем GROUP BY для получения агрегатов;использовать (1) и (2) для манипулирования только требуемым набором и заполнять агрегаты с помощью скалярных подзапросов.

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

Ссылка на 3_Set_Inline_Query_Improved Code & Timings

  • 1103, 1073, 1103, 1073, 1080 мс
    Это практически идентично 1_Set_Scalar_Subquery

  • Статистика Sybase показывает 4 сканирования:
    Клиент (Производитель) сканируется один раз
    CustomerTransaction (Животное) сканируется дважды (один раз для каждого скалярного подзапроса; даже при наличии 2 встроенных наборов)
    Клиент (Производитель) сканируется один разmore (для самого внешнего запроса)

  • Поэтому (в этом простом тесте) доказано, что Sybase выполняет встроенные запросы точно так же, как и скалярные подзапросы.Учитывая, что в данном случае это 3 набора против 1 набора, я бы сказал, что Оптимизатор проделал большую работу в Нормализации запроса (не сглаживая, как спросил Тони, и я ответил), чтобыпроизводить идентичные результаты производительности.

  • Другая вещь, доказанная этим упражнением, заключается в важности подхода (1), (2).Предполагая даже разумную нормализацию, к данным в реляционной базе данных лучше всего подходить с помощью реляционного, ориентированного на множество подхода.Это приводит к более простому и эффективному коду.Конечно, меньший, более простой, нормализованный код работает лучше.

За исключением Oracle, где из-за его особенностей и ограничений люди должны избегать этого и использовать конструкции, которые, как известно, не работают плохо.

Сравнение 01 декабря 2010

Тони опубликовал результаты своих тестов.Они говорят сами за себя, дело закрыто.

Однако он представил проблему, заключающуюся в том, что, хотя в данном конкретном случае (этот вопрос) Oracle выполняет 1_Set_Scalar_Subquery почти в два раза быстрее, чем 3_Set_Inline_Query, поскольку COUNT () обслуживается из индекса без доступа к таблице, что в общем случае для Oracleвыполняет встроенные запросы намного лучше, чем скалярные подзапросы, и протестировал SUM (), чтобы изучить эффект посещения таблиц.

Поэтому я выполнил тот же тест в Sybase, изменив COUNT () на SUM ().Как и в Oracle, COUNT () был закрытым запросом, но теперь SUM () требует посещения таблиц.

  • (Выводы позже, когда Тони публикует некоторые недостающие деталиМеня беспокоит то, что Sybase обрабатывает 2,0 млн строк менее чем за 2 секунды, а Oracle, даже в лучшем случае, не может справиться с той же работой со 1000 ++ строками за 6 секунд (ожидается заполнение таблиц)).)

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

  • Пока что в аналогичных тестах (недостаточно близко для меня) на аналогичных системах (нормально, система Sybase имеет 2 дополнительных/ w слоев и не является сервером):

    • Sybase работает со строками COUNT 4.0M за 1.1 и 1.5 секунды ;
      Oracle выполняет аналогичную работу в 3,5 и 5,7 секунд .

    • Sybase выполняет SUM за 2,1 и 3,0 с ;
      Oracle не завершил подзапрос и выполнил инлайн в 79 секунд
      (ожидается запрос дополнительной информации).

    • В лучшем случае (подзапрос не выполнен, поэтому мы не можем это подсчитать), используя плохой кодкак требуется для Oracle, Oracle в 26 раз медленнее, чем Sybase .

    • И в 40 раз медленнее, когда кодер Sybase использовал теорию реляционных множеств.

1_Set_Scalar_Subquery_SUM

То же, что и 1_Set_Scalar_Subquery_SUM, с COUNT (), измененным на SUM ().

Ссылка на 1_Set_Scalar_Subquery_SUM Code & Timings

  • 2173, 2153, 2163, 2153, 2173 мс

  • Статистика Sybase показывает 3 сканирования:
    Заказчик (Производитель) сканируется один раз
    CustomerTransaction (Animal)сканируется дважды (один раз для каждого скалярного подзапроса)

3_Set_Inline_Query_SUM

То же, что 3_Set_Inline_Query, с COUNT (), измененным на SUM ().

Ссылка на код 3_Set_Inline_Query_SUM и время

  • 3033, 2993, 3033, 2993, 3013 мс
    То есть 38% медленнее .

  • Статистика Sybase показывает 9 сканирований:
    Клиент (производитель) сканируется дважды (внешний запрос) CustomerTransaction (животное) сканируется дважды (2 встроенных набора)
    Рабочий стол 2 сканируется 3раз (объединенный набор)
    Рабочие таблицы 1 и 3 сканируются по одному разу (для GROUP BYs)

Конечно, разница в 38% будет преувеличена на занятом сервере.

3_Set_Inline_Query_Improved_SUM

То же, что 3_Set_Inline_Query_Improved, с COUNT () изменено на SUM ().

Ссылка на 3_Set_Imp_Inline_Inline

  • 2183, 2180, 2183, 2143, 2143 мс
    Это практически идентично 1_Set_Scalar_Subquery

  • Статистика Sybase показывает 4 сканирования:
    Клиент (Производитель) сканируется один раз
    CustomerTransaction (Animal) сканируется дважды (один раз для каждого скалярного подзапроса; даже при наличии 2 встроенных наборов)
    Клиент (производитель) сканируется еще раз (для внешней query)

  • Одна вещь в контексте Sybase.Мое утверждение о том, что Sybase Optimizer нормализует запросы и, следовательно, обрабатывает скалярные подзапросы, а также встроенные представления, еще раз доказано.Плохой код (3_Set_Inline_Query не улучшен ) работает медленнее, но только пропорционально медленнее, без существенной разницы.

3 голосов
/ 24 ноября 2010

Полагаю, animals.idanimal - это первичный ключ. Если это так, можно написать запрос, используя левое внешнее соединение и count в целевом столбце, чтобы обрезать NULLs.

select producers.name prod,
       count(animalsbought.idanimal) animalsbought,
       count(animalsexploration.idanimal) animalsexploration
from producers
  join animals on  animals.owner = producers.nif
  left join animalsbought on animalsbought.idanimal = animals.idanimal
  left join animalsexploration on animalsexploration.idanimal = animals.idanimal
group by producers.name;
1 голос
/ 29 ноября 2010

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

Пожираемый, в результате моих упражнений с Тони, которые требовали проверки ваших двух оригинальных наборов, стало ясно, что они были неэффективными и могли быть улучшены, как в (1), (2) в моем предыдущем ответе. Рассмотрение и устранение препятствий Oracle, которые необходимо обойти, мешает и препятствует ориентированному на наборы подходу к решению запросов.

  1. Таким образом, мы отложим это на мгновение и примем подход (1), (2). Другими словами, если вы начинаете с плохих яблок и просто расширяете их (это ваш конкретный вопрос), то вредность увеличивается в четыре раза, как доказано в тестах.

  2. Я отложу в сторону проекцию большого набора результатов, а затем GROUPing, как у вас. Поскольку вам нужен список Producers ++, я использую его в качестве базовой структуры запроса. Затем я заполняю агрегаты скалярными подзапросами. GROUP BY и рабочие столы, необходимые для его обработки, исключаются.

  3. Еще один способ заявить, что я нормализовал ваши исходные запросы.

  4. Я полагаю, вам нужно соединение с AnimalsBought для проверки существования; в противном случае (поскольку на него нет ссылки в COUNT()), его можно удалить.

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

<code>SELECT  Producers.name Prod, 
        ( SELECT count(Animals.idanimal)
              FROM  Animals,
                    AnimalsBought
              WHERE (p.nif = Animals.owner)
              AND   (Animals.idanimal = AnimalsBought.idanimal) 
              ) AnimalsBought
    FROM  Producers p;<br>
SELECT  Producers.name Prod, 
        ( SELECT count(Animals.idanimal)
              FROM  Animals,
                    AnimalsExploration
              WHERE (p.nif = Animals.owner)
              AND   (Animals.idanimal = AnimalsExploration.idanimal) 
              ) AnimalsExploration
    FROM  Producers p;

Если это работает для вас, это, естественно, приведет к ответу на заданный вами вопрос:

<code>SELECT  Producers.name Prod, 
        ( SELECT count(Animals.idanimal)
              FROM  Animals,
                    AnimalsBought
              WHERE (p.nif = Animals.owner)
              AND   (Animals.idanimal = AnimalsBought.idanimal) 
              ) AnimalsBought,
        ( SELECT count(Animals.idanimal)
              FROM  Animals,
                    AnimalsExploration
              WHERE (p.nif = Animals.owner)
              AND   (Animals.idanimal = AnimalsExploration.idanimal) 
              ) AnimalsExploration
    FROM  Producers p;

Если вы страдаете от Null и Spinsters, тогда mcha ответ будет лучшим.

0 голосов
/ 24 ноября 2010

Вы хотите объединить таблицы?(положите столы друг на друга)
или вы хотите присоединиться к столам?(расположите таблицы рядом)

И объединение, и объединение могут создать желаемый формат вывода, но это очень разные вещи.

Поскольку их столбцы не совпадают, вам потребуетсявставьте нулевые метки в объединение.

0 голосов
/ 24 ноября 2010

Как я понял, вам нужен запрос, который покажет вам:

PRODUCER            FirstQueryColumn       SecondQueryColumn      
------------------------------------------------------------
aaaaaaaaaaaa         1                      2
bbbbbbbbbbb          1                      1

вы можете попробовать что-то вроде:

SELECT (SELECT Producers.name Prod
          FROM AnimalsBought, Animals, Producers
         WHERE (AnimalsBought.idanimal = Animals.idanimal)
           AND (Animals.owner = Producers.nif)
         GROUP BY Producers.name) as Producers,
       (SELECT COUNT(Animals.idanimal) AnimalsBought
          FROM AnimalsBought, Animals, Producers
         WHERE (AnimalsBought.idanimal = Animals.idanimal)
           AND (Animals.owner = Producers.nif)
         GROUP BY Producers.name) as firstQuery,
       (SELECT COUNT(Animals.idanimal) AnimalsExploration
          FROM AnimalsExploration, Animals, Producers
         WHERE (AnimalsExploration.idanimal = Animals.idanimal)
           AND (Animals.owner = Producers.nif)
         GROUP BY Producers.name) as secondQuery
  FROM DUAL

Конечно, вы можете оптимизировать его, это простоидея:)

0 голосов
/ 24 ноября 2010
SELECT A.producer, COUNT(A.firstquerycolumn), COUNT(B.secondquerycolumn) FROM 
TAB1 A, TAB2 B
WHERE A.producer = B.producer 
GROUP BY A.firstquerycolumn, B.secondquerycolumn

Это то, что вы просите?

0 голосов
/ 24 ноября 2010

Не вдаваясь в детали запросов, я думаю, что следующее может помочь.

Select A.Producer, A.FirstQueryColumn, B.SecondQueryColumn
From
(
   Select Producer, FirstQueryColumn, '' As SecondQueryColumn
   From TableA
) AS A
Inner Join
(
   Select Producer, '' as FirstQueryColumn, SecondQueryColumn
   From TableA
) AS B ON A.Producer = B.Producer
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...