MS SQL > Oracle = True, или взгляд программиста на две СУБД

Автор: Александр Karpol   
17.12.2004

Программисты часто делятся на два клана. Одни любят Си, другие Дельфи, и спор этот, как правило, бессмыслен. Обычно кто к чему привык, тот то больше и любит, и восхваляет. Среди систем управления базами данных так же ярко выделяются два конкурента MS SQL и Oracle. Обе системы прекрасно справляются с задачами хранения и доступа к данным, и, в принципе, достаточно опытному программисту не будет проблем взять любую из них на вооружение, но... Но ряд особенностей все же позволяет сделать предположение, что MS SQL ближе к людям, менее требователен к опыту работы с системой, все лежит на поверхности.

Чтобы не быть голословным, рассмотрим несколько примеров. Отличий в выборке вы практически не найдете. Разница только будет в дополнительных функциях, специфических для систем:

Oracle:

        select  a.Numb,
                a.Date,
                a.Client,
                ap.Name Tovar,
                nvl(ap.Kolvo,0) Kolvo,
                ap.Summa * decode(ap.type,1,1.2,
                                  decode(ap.type,2,1.25,1)
                                 ) SummaWithNalog
        from    account a,
                accountpos ap
        where   a.rn = ap.prn(+)
                and a.Date >= '01.01.2004'
                and a.Date <  '15.01.2004'

То же самое в MS SQL:

        select  a.Numb,
                a.Date,
                a.Client,
                ap.Name as Tovar,
                ISNULL(ap.Kolvo,0) Kolvo,
                ap.Summa * (CASE ap.type WHEN 1 THEN 1.2
                                         WHEN 2 THEN 1.25
                                         ELSE 1 END
                            ) SummaWithNalog
        from    account a
                LEFT OUTER JOIN accountpos ap
                ON a.rn = ap.prn
        where   a.Date between '01.01.2004' and '15.01.2004'

Вообще то в Оракле можно не пользоваться специфической функцией decode, а использовать тот же CASE. И даже склейку таблиц не обязательно делать (+), можно использовать LEFT|RIGHT OUTER|INNER JOIN Правда последняя возможность появилась только в версии Oracle 9. Можно считать, что отличий в выборке нет, но есть одна особенность. Попробуйте выбрать значение в переменную:

Oracle:

     begin
       select Cost*Kolvo
       into   nSum
       from   account
       where  id = 2556
     exception
       when NO_DATA_FOUND then nSum := null;
     end;

MSSQL:

       select @nSum = (select Cost*Kolvo
                       from   account
                       where  id = 2556);

В Oracle такая конструкция сопровождается обработчиком ошибок, так как стоит запросу не вернуть ни одной записи - возникнет исключение и ваш код не выполнится. MS SQL же переварит это в легкую, наградив переменную @nSum значением NULL. По-моему это более логично, чем каждый раз делать такие проверки, нагромождая код. Можно еще пойти в обход:

Oracle:

      select (select Cost*Kolvo
              from   account
              where  id = 2556)
      into    nSum
      from    dual;

И, тем не менее, код на MS SQL мне кажется более лаконичным. Хотя если еще вспомнить, что в конструкции INTO можно указывать список переменных, то мои придирки можно свести на нет. Ну, это все мелочи на самом деле. Дело вкуса.

А теперь о серьезном. Знаете ли вы, что такое временные таблицы и зачем они нужны?

Временная таблица в MS SQL - это фиктивная таблица (не существующая физически), в которую можно выгрузить набор данных, полученный SQL-запросом, и использовать для последующего SQL запроса. Эту таблицу совсем необязательно описывать, достаточно просто задать ей имя, начинающееся с символа # если хотите, чтобы эта таблица существовала, на время выполнения процедуры, или с ## если хотите, чтобы к ней имели доступ и другие процедуры. Такая таблица разрушится автоматически после завершения сессии. Таким образом, стоит вам написать:

        select  a.Numb,
                a.Date,
                a.Client,
                ap.Name as Tovar,
                ISNULL(ap.Kolvo,0) Kolvo,
                ap.Summa * (CASE ap.type WHEN 1 THEN 1.2
                                         WHEN 2 THEN 1.25
                                         ELSE 1 END
                            ) SummaWithNalog
        INTO    #my_table
        from    account a
                LEFT OUTER JOIN accountpos ap
                ON a.rn = ap.prn
        where   a.Date between '01.01.2004' and '15.01.2004'

И у вас есть таблица #my_table которая содержит выбранный вами набор записей. Выбрали, отдышались, и теперь можете ее использовать для дальнейшей выборки:

        select  c.Client_Name,
                (select count(*)
                 from   #my_table t
                 where  t.Client = c.Client
                        and t.SummaWithNalog< 1000
                 ) Count1000,
                (select count(*)
                 from   #my_table
                 where  t.Client = c.Client
                        and SummaWithNalog between 1000 and 4999
                 ) Count5000,
                (select count(*)
                 from   #my_table
                 where  t.Client = c.Client
                        and SummaWithNalog between 5000 and 10000
                 ) Count10000
        from    Client c

Заметим, MS SQL выполнил выборку во временную таблицу, и он ее знает и помнит данные в ней. Что нам предложит на это Оракл? Можно вставить первую выборку в трех местах второго селекта, выкинув временную таблицу. Получим громоздкий и нерациональный селект, который при выполнении будет три раза получать одни и те же данные. Можно воспользоваться курсором. И вместо простой выборки написать целую программу, вспомнив if, for, заведя переменные и т.д.:

  v_cnt1000  number(10):=0;
  v_cnt5000  number(10):=0;
  v_cnt10000 number(10):=0;

        delete from my_table;

        for cl in (select c.Client_Name,
                          c.Client
                    from  Client c)
        loop
                for ac in (select  a.Numb,
                                   a.Date,
                                   a.Client,
                                   ap.Name Tovar,
                                   nvl(ap.Kolvo,0) Kolvo,
                                   ap.Summa * decode(ap.type,1,1.2,
                                                     decode(ap.type,2,1.25,1)
                                                    ) SummaWithNalog
                                   from    account a,
                                   accountpos ap
                           where   a.rn = ap.prn(+)
                                   and a.Date >= '01.01.2004'
                                   and a.Date <  '15.01.2004'
                                   and a.Client = cl.Client)
                loop

                   if ac.SummaWithNalog < 1000
                      then v_cnt1000 := v_cnt1000 + 1
                   elsif ac.SummaWithNalog >= 1000
                         and ac.SummaWithNalog < 5000
                      then v_cnt5000 := v_cnt5000 + 1
                   elsif ac.SummaWithNalog >= 5000
                         and ac.SummaWithNalog < 10000
                      then v_cnt10000 := v_cnt10000 + 1
                   end if;
        
                end loop;

                insert into my_table(Client_Name,
                                     cnt1000,
                                     cnt5000,
                                     cnt10000)
                values (cl_Client_Name,
                        v_cnt1000,
                        v_cnt5000,
                        v_cnt10000);

        end loop;

Вам понравилось это? С Oracle я работал намного больше, чем с MS SQL, но даже того малого времени общения с MS SQL оказалось достаточно, чтобы он вызвал большие симпатии. Конечно, здесь нет смысла приводить громоздкие выборки, но подумайте о простоте мышления при программировании в MS SQL. Вы можете последовательно выбрать нужные вам данные во временные таблицы, и потом завязать их по какому захотите принципу в одну результирующую выборку. При этом вам ничего не нужно кроме ключевого слово select и понимания построения запросов, то есть сам SQL. В практике программирования в MS SQL только в 10% есть необходимость сделать курсор или еще что-то, используя весь синтаксический набор языка. Разве не рай для программиста?

Но и это еще не все. Вы заметили куда вставляются данные в предыдущем примере? В какую-то таблицу my_table. А что она из себя представляет? Не иначе как физическая таблица. Она взялась не сама собой. Ее еще надо предварительно создать. Описать колонки, типы и т.п. Однако, тут стоит оговориться, что в Оракле тоже есть "временные" таблицы, GLOBAL TEMPORARY TABLE. Но только по удобству их использования они ничем не отличаются от физических таблиц. Ее так же надо создать. Причем команду CREATE TABLE нельзя выполнить непосредственно в процедуре. Правда есть возможность воспользоваться динамическим SQL, но опять же. Мы постоянно натыкаемся на грабли и пытаемся их обходить. Но не без царапин. В частности после выполнения:

EXECUTE IMMADIATE 'CREATE GLOBAL TEMPORARY TABLE TMP(i number(17));'

произойдет неявный commit и откатить изменения вы уже не сможете. Все-таки, в данном случае временные таблицы Oracle это инструмент скорее для администратора, а не для программиста, и совсем ничего не имеющий общего с временными таблицами MS SQL, кроме названия.

Заметьте, если в результате выполнения процедуры мы хотим получить набор данных, то нам нужно подготовить для него таблицу. А что нам на это предлагает MS SQL? А элементарно, он предлагает результирующую выборку в процедуре написать без ключевого слова into, то есть не вставлять этот набор куда-то в таблицу. Тогда результатом выполнения процедуры, вернется набор этих данных. Как? Да вот так. Вы вызываете процедуру

exec my_proc;

А вам прилетает с сервера набор данных, как будто вы выполнили select:

cl_Client_Name   v_cnt1000     v_cnt5000  v_cnt10000
-------------    ---------     ---------  ----------
ОАО "Витек"              5            10          15
ОАО "Молоко"            15             0          23

Не здорово ли? Если вы не можете одним селектом выбрать данные, просто пишите процедуру, в которой оформите последовательно выборки данных во временные таблицы, и завяжите их в тот набор данных, который вам нужен на выходе. И все, процедура вернет вам нужные данные.

Но здесь, конечно, не все правда - в Оракле это также реализуемо, только решение лежит не на поверхности, а кроется в дебрях Oracle. Есть такое понятие "курсорная переменная". То есть, можно в процедуре открыть курсор и вернуть через выходной параметр этот набор данных:

begin
  my_proc(:cur);
end;

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

type my_cur_type is ref cursor;

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

create or replace my_pack is
  type my_cur_type is ref cursor;
end;

create or replace procedure my_proc(cur my_pack.my_cur_type) is begin
   .....
end;

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

Возможно, поэтому я редко встречал, чтобы таким методом пользовались на практике. А в MS SQL это само напрашивается, даже представить не могу, как этим не воспользоваться. Но можно сделать еще более смелое предположение, почему не пользуется популярность такой метод в Oracle. Видимо, не всем хочется разбираться с курсорными переменными, да и наглядность теряется. Когда вы отлаживаете процедуру, подготавливающую набор данных, вы обычно не будете каждый раз открывать Клиента и смотреть что в него вернулось. Более наглядно посмотреть, что же процедура вставила в таблицу. Возможно поэтому меньше проблем все же ее создать. В MS SQL как раз напротив, при введении в текст курсоров никаких психологических барьеров преодолевать не приходится. Просто пишем селект и все. Кроме того, во время отладки можем быстро посмотреть, что у нас подготовилось в промежуточных временных таблицах, вставив в любое место процедуры выборку из этой таблицы.

select * from #my_table

Подытоживая, можно сказать, что MS SQL более простая и удобная СУБД и пользоваться ею можно не имея большого запаса знаний. И необходимости пользоваться Oracle, если вы не решаете какую-то очень специфическую задачу, или, например, хотите ставить базу не на Widows платформу - не вижу. Это избыточность, а она не всегда полезна в итоге. Остается только пожелать тем немногим программистам, кому посчастливится оказаться у истоков нового проекта, будьте благоразумны с выбором сервера. Вам с ним работать и работать.


Обсудить статью можно здесь.

 
Введите номер
вашего телефона:

Заказ услугПо всем вопросам заказа услуг вы можете обратиться к нам по телефону +7 (495) 506-97-92 или задать вопрос с помощью формы обратной связи.

Будем рады видеть вас в числе наших клиентов!