Игорь Филимонов

ЛАСУ ТРИНИТИ, г. Троицк

тел. (095) 334-0408


"DECODE - это способ сломать головы программистам. Почему ORACLE и ANSI не включат в стандарт SQL условную функцию подобную IIF(A,B,C) из dBASE or (A?B:C) из Cи ???" Такие или похожие мысли наверняка неоднократно посещали головы не только новобранцев Oracle, но и бывалых ораклоидов.

Время от времени после долгого программирования, используя DECODE, появлялись на ствет громадные монстры, которые после оптимизации становились абсолютно не читаемыми. И, через какое-то время даже сам перестаешь понимать, что же ты пытался здесь выбрать. Но со временем выработался определенный алгоритм действий. И сейчас мне хотелось бы поделиться им. "Молодым в науку, а старикам в утеху (вспомни дни своих первых selectов)".

Посмотрим, во что может превратиться не самый сложный условный оператор: IIF( a=d or not (e<5 or f>5) , ‘True’, ‘False’ )

1. Использование DECODE

Замечание: DECODE не поддерживается в PL/SQL. Следовательно этот алгоритм ТОЛЬКО для SQL операторов.

Условие Использование DECODE Альтернативный вариант
A Decode( Sign(A-B), -1, 1, 0 )
A<=B Decode( Sign(A-B), 1, 0, 1 )
A>B Decode( Sign(A-B), 1, 1, 0 )
A>=B Decode( Sign(A-B), -1, 0, 1 )
A=B Decode( A, B, 1, 0 )
A between B and C Decode( Sign(A-B), -1, 0, Decode(Sign(A-C), 1, 0, 1 ))
A is null Decode(A,null,1,0)
A is not null Decode(A,null,0,1)
A in (B1,B2,...,Bn) Decode(A,B1,1,B2,1,...,Bn,1,0)
nor LogA Decode( LogA, 0, 1, 0 ) (1-Sign(LogA))
LogA and LogB LogA * LogB
LogA or LogB LogA + LogB
LogA xor LogB Decode(Sign(LogA), Sign(LogB), 0, 1) Mod( Sign(LogA), Sign(LogB), 2 )

И все это охватывает внешний DECODE( <условие>,0, <ложь>,<истина>).

Наш пример выглядит следующим образом:

Decode( Decode(Sign(a-b),-1,1,0) * Decode(Sign(c-d),-1,0,1) + Decode( Decode(Sign(e-5),-1,1,0) + Decode(Sign(f-5),1,1,0),0,1,0), 0,’False’, ‘True’ )

Если соблюдать порядок True и False это несколько изменится: DECODE( Sign(<условие>), 1,<истина>, <ложь> ).

Decode(Sign( Decode(Sign(a-b),-1,1,0) * Decode(Sign(c-d),-1,0,1) + Decode( Decode(Sign(e-5),-1,1,0) + Decode(Sign(f-5),1,1,0),0,1,0) ), 1,‘True’,’False’ )

2. Второй способ (Польская запись?)

Главное его преимущество в том, что он применим как в PL/SQL так и в SQL. Хотя он не более нагляден, чем первый, и уж точно менее производителен (Все-таки DECODE - built-in).

Нужно создать ряд функций (функции сравнения и итоговая функция создаются по три экземпляра для NUMBER, DATE и VARCHAR2).

-- 1) Функции Сравнения:

-- A < B for NUMBER, DATE, VARCHAR2 CREATE OR REPLACE FUNCTION Lt( a DATE, b DATE ) RETURN NUMBER IS

BEGIN IF a < b THEN RETURN 1; ELSE RETURN 0; END IF; END Lt;

/

CREATE OR REPLACE FUNCTION Lt( a NUMBER, b NUMBER ) RETURN NUMBER IS

BEGIN IF a < b THEN RETURN 1; ELSE RETURN 0; END IF; END Lt;

/

CREATE OR REPLACE FUNCTION Lt( a VARCHAR2, b VARCHAR2 ) RETURN NUMBER IS

BEGIN IF a < b THEN RETURN 1; ELSE RETURN 0; END IF; END Lt; ....

-- повторите для Lq( A, B ) /* A<=B */, Gt( A, B ) /* A>B */, Gq( A, B ) /* A>=B */,

-- Eq(A, B ) /* A=B */, Ne(A, B ) /* A!=B */, Bw(A,B,C) /* B between A and C */

-- IsN(A) /* A is null */, IsNN(A) /* A is not null */,

-- а вот in будет эмулировать Decode(A,B1,1,B2,1,...,Bn,1,0) /* A in (B1,B2,...,Bn) */

-- 2) Логические функции

-- AND

CREATE OR REPLACE FUNCTION Land( a NUMBER, b NUMBER ) RETURN NUMBER

BEGIN IF a * b != 0 THEN RETURN 1; ELSE RETURN 0; END IF; END Land;

-- OR

CREATE OR REPLACE FUNCTION Lor( a NUMBER, b NUMBER ) RETURN NUMBER

BEGIN IF a + b != 0 THEN RETURN 1; ELSE RETURN 0; END IF; END Lor;

-- XOR

CREATE OR REPLACE FUNCTION Lxor( a NUMBER, b NUMBER ) RETURN NUMBER

BEGIN IF a != b THEN RETURN 1; ELSE RETURN 0; END IF; END Lxor;

-- NOT

CREATE OR REPLACE FUNCTION Lnot( a NUMBER ) RETURN NUMBER BEGIN IF a = 0 THEN RETURN 1; ELSE RETURN 0; END IF; END Lnot;

-- 3) Итоговая функция

-- IIF CREATE OR REPLACE FUNCTION Iif( l NUMBER, a DATE, b DATE ) RETURN DATE IS

BEGIN IF l=0 THEN RETURN b; ELSE RETURN a; END IF; END Iif;

/

CREATE OR REPLACE FUNCTION Iif( l NUMBER, a NUMBER, b NUMBER ) RETURN NUMBER IS

BEGIN IF l=0 THEN RETURN b; ELSE RETURN a; END IF; END Iif;

/

CREATE OR REPLACE FUNCTION Iif( l NUMBER, a VARCHAR2, b VARCHAR2 ) RETURN VARCHAR2 IS

BEGIN IF l=0 THEN RETURN b; ELSE RETURN a; END IF; END Iif;

/

Посмотрим на наш пример:

Iif( Lor( Land(Lt(a,b), Gq(c,d)), Lnot( Lor(Lt(e,5),Gt(f,5)) ) ), ‘True’, ‘False’ )

3. Третий способ (IIF?) наиболее очевиден. Стоит только увидеть оператор CREATE FUNCTION, как появляется желание написать собственный оператор IIF ни в чем не уступающий dBASEвскому

CREATE OR REPLACE function IIF(b BOOLEAN,e1 NUMBER,e2 NUMBER) return NUMBER IS

begin IF b THEN return e1; ELSE return e2; END IF; END;

CREATE OR REPLACE function IIF(b BOOLEAN,e1 VARCHAR2,e2 VARCHAR2) return VARCHAR2 IS

begin IF b THEN return e1; ELSE return e2; END IF; END;

CREATE OR REPLACE function IIF(b BOOLEAN,e1 DATE,e2 DATE) return DATE IS

begin IF b THEN return e1; ELSE return e2; END IF; END;

Естественно его вид ни чем не отличается от исходного:

IIF( a=d or not (e<5 or f>5) , ‘True’, ‘False’ )

Однако скоро убеждаешься, что работает он только в PL/SQL (что само по себе очень даже неплохо). И тут наступает момент разочарования: в SQL приходиться выбирать между двумя первыми способами.