Проверка типа данных в БД

Anjey aka PM
Дата: 30.09.2005 20:42:40
Есть у меня такая таблица:

CREATE TABLE CONFIG(
  ID INTEGER NOT NULL,
  NAME VARCHAR(32) NOT NULL,
  DATATYPE CHAR(31) CHARACTER SET UNICODE_FSS NOT NULL, // как в системных таблицах
  CONFIG_VALUE VARCHAR(1024)

Надо бы на стороне БД проверку типов данных замутить чтобы потом в СП-хах кастить было безопасно, чтоб не покоцали, введя несовместимое значение в поле. Проблема в том что все конфиги в варчаре держать надобно вот потому и проверку типов организировать нужно.

Наваял такое чудовище:

CREATE TRIGGER TR_CONFIG_BIU_CHECK FOR T_CONFIG
ACTIVE BEFORE INSERT OR UPDATE POSITION 1010
AS
DECLARE VARIABLE TEST VARCHAR(1024) CHARACTER SET WIN1251;
DECLARE VARIABLE SQL VARCHAR(2048) CHARACTER SET WIN1251;
BEGIN
    IF (NEW.DATATYPE <> 'BLOB' AND NEW.DATATYPE <> 'VARCHAR' AND NEW.DATATYPE <> 'CHAR') THEN
    BEGIN
        TEST = NEW.CONFIG_VALUE;
        /* TODO: Add Quotes Escaping */
        SQL = 'SELECT FIRST 1 CAST(CAST(''' || TEST || ''' AS ' || NEW.DATATYPE ||
            ') AS VARCHAR(1024)) FROM RDB$DATABASE';
        EXECUTE STATEMENT :SQL INTO :TEST;
        NEW.CONFIG_VALUE = TEST;
        WHEN ANY DO
            EXCEPTION E_INTERNAL__DATATYPE
                'Invalid configuration value for datatype ' || NEW.DATATYPE;
    END
END
^

Работает, проверочку делает...

Вопрос: может кто подскажет другой способ реализировать сабж? Что будет быстрее: такой способ или проверка с помощью УДФ-ки?
Anjey aka PM
Дата: 30.09.2005 21:43:13
да, еще в догонку: как упразднить:

IF (A <> B OR NOT (A IS NULL AND B IS NULL)) THEN

(аналог a <> b с обходом того что если одни аргумент NULL то результат NULL и IF не исполняется)
AndriyKo
Дата: 30.09.2005 21:46:24
Anjey aka PM
да, еще в догонку: как упразднить:

IF (A <> B OR NOT (A IS NULL AND B IS NULL)) THEN

(аналог a <> b с обходом того что если одни аргумент NULL то результат NULL и IF не исполняется)

Coalesce
Anjey aka PM
Дата: 30.09.2005 22:05:26
AndriyKo
Anjey aka PM
да, еще в догонку: как упразднить:

IF (A <> B OR NOT (A IS NULL AND B IS NULL)) THEN

(аналог a <> b с обходом того что если одни аргумент NULL то результат NULL и IF не исполняется)

Coalesce


Вопрос не в количестве написанного (что при использовании Coalesce конечно меньше), вопрос в количестве операций и быстроте проверки.

В моем случае (учитывая что FB не делает Complete Boolean Evaluation или как оно там правильно пишется) если A<>B даст результат TRUE вторая часть условия проверяться не будет что уменьшает количество проверок, произведенных сервером. И если сравнивать, то реализация

IF (COALESCE (A,'') <> COALESCE (B,'') ) THEN

имеет два недостатка.

вопервых:
Проверка на NULL будет производиться для обоих переменных после чего будет проведена операция сравнения (3 атомарных действия в этом случае даже когда A<>B И ни один из аргументов не NULL а не 1 как в моем примере)
вовторых:
разница между NULL и '' теряется и условие не будет срабатывать при

A = NULL
B = ''

Так что этот пример нельзя считать удачным
AndriyKo
Дата: 30.09.2005 22:15:00
Anjey aka PM
И если сравнивать, то реализация

IF (COALESCE (A,'') <> COALESCE (B,'') ) THEN

имеет два недостатка.

вопервых:
Проверка на NULL будет производиться для обоих переменных после чего будет проведена операция сравнения (3 атомарных действия в этом случае даже когда A<>B И ни один из аргументов не NULL а не 1 как в моем примере)
вовторых:
разница между NULL и '' теряется и условие не будет срабатывать при

A = NULL
B = ''

Так что этот пример нельзя считать удачным

Во первых, не понимаю, тебе что, существенна (критична) разница между 2 и 3 атомарными действиями ? Во вторых, обычно при подобной проверке вовсе не обязательно совать '' в Coalesce. Просто какое нибудь заведомо несуществующее значение, например 'Anjey aka PM' :). Я использую обычно -1.
Anjey aka PM
Дата: 30.09.2005 22:28:51
1. К сожалению важна (хотелось упростить до 1-й проверки во всех случаях). Это одно из узких мест кода, через него будет проходить очень много запросов по 500-5000 в секунду у конечного клиента. К сожалению 50% из них будут иметь одно из двух значений = NULL. Вспоминается оптимизация умножения на 320 на 386-х машинах путом сложения и сдвигов :)

x*320 = x << 8 + x << 6; (если точно помню, но это не суть важно)
давало оптимизацию в 3 раза почти... так и тут

2. Заведомо неповторяющихся значений нету (к сожалению)
AndriyKo
Дата: 30.09.2005 22:36:23
Anjey aka PM
Это одно из узких мест кода, через него будет проходить очень много запросов по 500-5000 в секунду у конечного клиента.

Заинтриговал. А разве сервак с такой частотой справится ? В процедурке ж не только проверка на нуллы, селекты(апдейты, инсерты) тоже ж есть. Даже если только селекты из кеша и один клиент, всё одно не верится. Шо ж за задача такая самашечая, можешь рассказать ? Аж зудит, интересно :))
Anjey aka PM
Дата: 30.09.2005 22:50:33
AndriyKo
Anjey aka PM
Это одно из узких мест кода, через него будет проходить очень много запросов по 500-5000 в секунду у конечного клиента.

Заинтриговал. А разве сервак с такой частотой справится ? В процедурке ж не только проверка на нуллы, селекты(апдейты, инсерты) тоже ж есть. Даже если только селекты из кеша и один клиент, всё одно не верится. Шо ж за задача такая самашечая, можешь рассказать ? Аж зудит, интересно :))


Нет, просто в одной процедуре таких проверок порядка 5-20 а селектов нету :)

есть вызов УДФ-ки в зависимости от результирующего параметра...

кстати сравним процедуры:
количество циклов 10 000 000

Просто сравнение без проверки на нул:

CREATE PROCEDURE NEW_PROCEDURE (
    INPUT_VALUE INTEGER)
RETURNS (
    OUTPUT_VALUE INTEGER)
AS
DECLARE VARIABLE A INTEGER;
DECLARE VARIABLE B INTEGER;
BEGIN
    /* Procedure Text */
    A = 5;
    B = 1;
    OUTPUT_VALUE = 0;
    WHILE (OUTPUT_VALUE < INPUT_VALUE) DO
    BEGIN
        IF (A <> B) THEN
          OUTPUT_VALUE = OUTPUT_VALUE + 1;
    END
    SUSPEND;
END
Время выполнения: 8,5 - 9,5с

использование COALESCE для проверки на нул:

CREATE PROCEDURE NEW_PROCEDURE (
    INPUT_VALUE INTEGER)
RETURNS (
    OUTPUT_VALUE INTEGER)
AS
DECLARE VARIABLE A INTEGER;
DECLARE VARIABLE B INTEGER;
BEGIN
    /* Procedure Text */
    A = 5;
    B = 1;
    OUTPUT_VALUE = 0;
    WHILE (OUTPUT_VALUE < INPUT_VALUE) DO
    BEGIN
        IF (COALESCE(A,-1) <> COALESCE(B,-1)) THEN
          OUTPUT_VALUE = OUTPUT_VALUE + 1;
    END
    SUSPEND;
END

Время выполнения: 13-14 с.

Мой способ проверки при A <> NULL AND B <> NULL:
CREATE PROCEDURE NEW_PROCEDURE (
    INPUT_VALUE INTEGER)
RETURNS (
    OUTPUT_VALUE INTEGER)
AS
DECLARE VARIABLE A INTEGER;
DECLARE VARIABLE B INTEGER;
BEGIN
    /* Procedure Text */
    A = 5;
    B = 1;
    OUTPUT_VALUE = 0;
    WHILE (OUTPUT_VALUE < INPUT_VALUE) DO
    BEGIN
        IF (A <> B OR (NOT (A IS NULL AND B IS NULL))) THEN
          OUTPUT_VALUE = OUTPUT_VALUE + 1;
    END
    SUSPEND;
END

Время выполнения: 8,5 - 9,5с (как в первом примере)

CREATE PROCEDURE NEW_PROCEDURE (
    INPUT_VALUE INTEGER)
RETURNS (
    OUTPUT_VALUE INTEGER)
AS
DECLARE VARIABLE A INTEGER;
DECLARE VARIABLE B INTEGER;
BEGIN
    /* Procedure Text */
    A = 5;
    B = NULL;
    OUTPUT_VALUE = 0;
    WHILE (OUTPUT_VALUE < INPUT_VALUE) DO
    BEGIN
        IF (A <> B OR (NOT (A IS NULL AND B IS NULL))) THEN
          OUTPUT_VALUE = OUTPUT_VALUE + 1;
    END
    SUSPEND;
END

Время выполнения: 9,5 - 11с (даже быстрее чем с COALESCE хотя те-же 3 операции в идеале)

Для уверенности я проверил использование COALESCE для проверки на нул при одном из значений NULL:
Время выполнения 15-16 с (странно имхо)

Вывод: Прирост производительности 20-25% без COALESCE.

Оптимизация рулит :)

Согласен, разница небольшая да и задержки то не значительные, но если в в СП-хах сравниваются входные параметры а не результаты выборки и работы с даными нет вовсе (только вызов УДФ) то это ощутимо имхо.
AndriyKo
Дата: 30.09.2005 23:22:53
Anjey aka PM
IF (A <> B OR (NOT (A IS NULL AND B IS NULL))) THEN
          OUTPUT_VALUE = OUTPUT_VALUE + 1;

А такой вариант ещё быстрее :)) Пусть сервак сам трахаеццо :)
IF (A = B) THEN A=A;
Else обработка;
Anjey aka PM
Дата: 30.09.2005 23:32:59
Всеравно суть вопроса - про триггер :)