ЛАСУ ТРИНИТИ, г. Троицк
тел. (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 приходиться выбирать между двумя первыми способами.