Админ

понедельник, 25 апреля 2016 г.

Firebird SQL: форматирование даты, склонение размерности и другие полезные скрипты и процедуры

|
За годы работы у меня скопилось множество различных готовых решений для часто возникающих задач. Про замысловатые вещи (генератор паролей и SQL для описания структуры таблиц) я уже писал, а в этой заметке собраны некоторые простые полезные SQL-запросы, тексты хранимых процедур и скрипты для Firebird, решающие различные задачи. С одной стороны, эти скрипты слишком короткие и простые, чтобы посвящать им отдельные статьи или заметки в блоге, с другой — они могут быть полезны не только мне, но и кому-нибудь ещё.

Оглавление

Форматирование даты в SQL-запросах Firebird

Постановка задачи

Как известно, при попытке произвести в SQL-запросе конкатенацию строки и даты Firebird преобразует дату в фиксированный формат YYYY-MM-DD (по международному стандарту ISO 8601), который в России не используется. Поэтому как раз для таких случаев я написал пару хранимых процедур, которые приводят дату к нужному формату.

Решение № 1 (простое, формат ГОСТ Р 6.30-2003)

Описание: эта хранимая процедура осуществляет форматирование даты в привычный для России и соответствующий отечественному стандарту ГОСТ Р 6.30-2003 вид DD.MM.YYYY.

Версия СУБД: поддерживается в СУБД Firebird 1.5 и выше.

Входные параметры: 
  • A_DATE date — дата
  • LEADING_ZERO smallint [0/1] — признак, нужно ли ставить лидирующий ноль, если день содержит всего одну цифру (1, по умолчанию — по ГОСТ) или нет (0), то есть если LEADING_ZERO = 1, то 1 января 2001 года выглядит как 01.01.2001 (по ГОСТ, так что годится для всех официальных документов), а если 0, то 1.01.2001 (используется в неофициальных текстах).
Выходной параметр:
  • RESULT varchar(10) — отформатированная дата.
Примеры:
(select RESULT from DATE2STR('1.01.2015', 1)) = '01.01.2015';
(select RESULT from DATE2STR('09-07-15')) = '7.09.2015';
(select RESULT from DATE2STR('12/7/15', 1)) = '07.12.2015';
Комментарии: достоинством этого решения является его простота и эффективность, а недостатком — опять-таки простота: поддерживается только один жёстко заданный формат, а для любого другого нужно писать отдельную процедуру.

Текст процедуры (дважды щёлкните на нём мышью, чтобы выделить для копирования):
create or alter procedure DATE2STR (
  A_DATE date,
  LEADING_ZERO smallint = 1)
returns (
  RESULT varchar(10))
as
declare variable D_YEAR smallint;
declare variable D_MONTH smallint;
declare variable D_DAY smallint;
begin
  if (A_DATE is null) then
    RESULT = null;
  else
  begin
    D_YEAR = extract(year from A_DATE);
    D_MONTH = extract(month from A_DATE);
    D_DAY = extract(day from A_DATE);
    if (coalesce(LEADING_ZERO, 0) = 1) then
      RESULT = lpad(cast(D_DAY as varchar(2)), 2, '0');
    else
      RESULT = cast(D_DAY as varchar(2));
    RESULT = RESULT || '.' || lpad(cast(D_MONTH as varchar(2)), 2, '0') || '.' || cast(D_YEAR as varchar(4));
    suspend;
  end
end

Решение № 2 (универсальное)

Описание: на вход эта процедура получает дату и строку формата, в которой буквы Y, M и D обозначают позицию для элементов даты (год, месяц, день) в различных видах (подробнее см. описание входных параметров ниже).

Версия СУБД: поддерживается в СУБД Firebird 2.1 и выше.

Входные параметры: 
  • A_DATE date — дата
  • FORMAT varchar(1000) — строка, содержащая шаблон форматирования даты. Заменяются следующие комбинации символов:
    • D — день без лидирующего пробела
    • DD — день с лидирующим пробелом
    • DDD — день недели, краткое название
    • DDDD — день недели, длинное название
    • M — месяц без лидирующего пробела
    • MM — месяц с лидирующим пробелом
    • MMM — месяц, краткое название
    • MMMM — месяц, полное название
    • YY — год, 2 последние цифры
    • YYYY — год, 4 цифры.
  • LONG_DAY_NAMES varchar(100) — полные названия дней недели через запятую (с понедельника по воскресенье)
  • SHORT_DAY_NAMES varchar(50) — сокращённые названия дней недели через запятую
  • LONG_MONTH_NAMES varchar(200) — полные названия месяцев через запятую
  • SHORT_MONTH_NAMES varchar(200) — сокращённые названия месяцев через запятую
Выходной параметр:
  • RESULT varchar(1000) — отформатированная дата.
Примеры:
(select RESULT from FORMAT_DATE('1.01.2015', 'D MMMM YYYY')) = '1 января 2015';
(select RESULT from FORMAT_DATE('7.09.2015', 'год YY от начала нынешнего столетия, день D MMMM месяца')) = 'год 15 от начала нынешнего столетия, день 7 сентября месяца';
(select RESULT from FORMAT_DATE('7.09.2015', 'M месяц года называется "MMMM" (сокращённо "MMM")', 'январь,февраль,март,апрель,май,июнь,июль,август,сентябрь,октябрь,ноябрь,декабрь') = '9 месяц года называется "сентябрь (сокращённо "сен")';
(select RESULT from FORMAT_DATE() = '7.09.2015' -- (зависит от текущей даты)
Комментарии: достоинством этого решения является его универсальность, а недостатком — сложность и низкая скорость выполнения.

Текст процедуры (дважды щёлкните на нём мышью, чтобы выделить для копирования):
create or alter procedure FORMAT_DATE (
  A_DATE date = current_date,
  FORMAT varchar(1000) = 'D.MM.YYYY',
  LONG_DAY_NAMES varchar(100) = 'понедельник,вторник,среда,четверг,пятница,суббота,воскресенье',
  SHORT_DAY_NAMES varchar(50) = 'пн,вт,ср,чт,пт,сб,вс',
  LONG_MONTH_NAMES varchar(200) = 'января,февраля,марта,апреля,мая,июня,июля,августа,сентября,октября,ноября,декабря',
  SHORT_MONTH_NAMES varchar(200) = 'янв,фев,мар,апр,мая,июн,июл,авг,сен,окт,ноя,дек')
returns (
  RESULT varchar(1000))
as
declare variable D_YEAR smallint;
declare variable D_MONTH smallint;
declare variable D_DAY smallint;
declare variable M smallint;
declare variable D smallint;
declare variable L smallint;
declare variable S smallint;
declare variable L_MONTH varchar(15);
declare variable S_MONTH varchar(10);
declare variable L_DAY varchar(15);
declare variable S_DAY varchar(10);
begin
  if (A_DATE is null) then
    RESULT = null;
  else
  begin
    D_YEAR = extract(year from A_DATE);
    D_MONTH = extract(month from A_DATE);
    D_DAY = extract(day from A_DATE);
    RESULT = FORMAT;

    if (FORMAT is null) then
      RESULT = D_DAY || '.' || lpad(cast(D_MONTH as varchar(2)), 2, '0') || '.' || D_YEAR;
    else
    begin
      RESULT = replace(replace(RESULT, 'YYYY', D_YEAR), 'YY', mod(extract(year from current_date), 100));
      S_MONTH = '';
      L_MONTH = '';
      if (RESULT like '%MMM%') then
      begin
        M = D_MONTH;
        while (M > 0) do
        begin
          L = position(',', LONG_MONTH_NAMES);
          S = position(',', SHORT_MONTH_NAMES);
          if (M = 1) then
          begin
            L_MONTH = substring(LONG_MONTH_NAMES from 1 for L - 1);
            S_MONTH = substring(SHORT_MONTH_NAMES from 1 for S - 1);
          end
          else
          begin
            LONG_MONTH_NAMES = substring(LONG_MONTH_NAMES from L + 1);
            SHORT_MONTH_NAMES = substring(SHORT_MONTH_NAMES from S + 1);
          end
          M = M - 1;
        end
        RESULT = replace(replace(RESULT, 'MMMM', coalesce(L_MONTH, '')), 'MMM', coalesce(S_MONTH, ''));
      end
      if (RESULT like '%DDD%') then
      begin
        D = coalesce(nullif(extract(weekday from A_DATE), 0), 7);
        while (D > 0) do
        begin
          L = position(',', LONG_DAY_NAMES);
          S = position(',', SHORT_DAY_NAMES);
          if (D = 1) then
          begin
            L_DAY = substring(LONG_DAY_NAMES from 1 for L - 1);
            S_DAY = substring(SHORT_DAY_NAMES from 1 for S - 1);
          end
          else
          begin
            LONG_DAY_NAMES = substring(LONG_DAY_NAMES from L + 1);
            SHORT_DAY_NAMES = substring(SHORT_DAY_NAMES from S + 1);
          end
          D = D - 1;
        end
        RESULT = replace(replace(RESULT, 'DDDD', coalesce(L_DAY, '')), 'DDD', coalesce(S_DAY, ''));
      end
      RESULT = replace(RESULT, 'MM', lpad(cast(D_MONTH as varchar(2)), 2, '0'));
      RESULT = replace(RESULT, 'M', D_MONTH);
      RESULT = replace(RESULT, 'DD', lpad(cast(D_DAY as varchar(2)), 2, '0'));
      RESULT = replace(RESULT, 'D', D_DAY);
    end
  end
  suspend;
end

Перевод чисел в 16-ричную систему счисления на Firebird SQL

Постановка задачи

Дано целое число. Требуется получить его представление в шестнадцатеричной системе счисления.

Решение

Описание: на вход эта процедура получает число и минимальную длину, до которой этот получившийся результат нужно дополнить нулями слева.

Версия СУБД: поддерживается в СУБД Firebird 2.1 и выше.

Входные параметры: 
  • NUM integer — число
  • MIN_LENGTH smallint — минимальная длина числа.
Выходной параметр:
  • RESULT varchar(8) — 16-ричное представление числа.
Примеры:
(select RESULT from INT2HEX(189)) = 'BD';
(select RESULT from INT2HEX(5949, 8) = '0000173D';
Комментарии: на выходе предполагается строка длиной 8, потому что в Firebird 2 и 3 тип integer является 32-битным, то есть записывается не более чем восемью 16-ричными цифрами от 0 до FFFFFFFF. Если заменить тип integer на bigint, то длина параметра RESULT должна увеличиться ровно вдвое (а больше никаких изменений не требуется).

Текст процедуры (дважды щёлкните на нём мышью, чтобы выделить для копирования):
create or alter procedure INT2HEX (
  NUM integer,
  MIN_LENGTH smallint = 0)
returns (
  RESULT varchar(8))
as
begin
  if (NUM is null) then
    RESULT = null;
  else
  begin
    RESULT = '';
    while (NUM > 0) do
    begin
      RESULT = decode(mod(NUM, 16), 15, 'F', 14, 'E', 13, 'D', 12, 'C', 11, 'B', 10, 'A', mod(NUM, 16)) || RESULT;
      NUM = NUM / 16;
    end
    if (MIN_LENGTH > 0) then
      RESULT = lpad(RESULT, MIN_LENGTH, '0');
  end
  suspend;
end

Преобразование списка, переданного в виде строки элементов, в таблицу

Постановка задачи

Дан список элементов, переданный в виде строки. Нужно получить этот же список в виде таблицы.

Зачем это нужно: иногда возникают такие ситуации, когда нужно передать в запрос переменное или неизвестное заранее количество параметров. Например (это очень сильно упрощённый пример!): дана таблица, содержащая два поля, ID (код) и NAME (название). Нам нужно выбрать из неё строки с определёнными конкретными кодами, количество которых заранее неизвестно. Мы можем выразить это так:
select
  ID,
  NAME
from
  MY_TABLE
where
  ID = any(select
             LIST_ITEM
           from
             SPLIT(:ID_LIST))
Теперь можно передать в этот запрос строку, содержащую список кодов через запятую.

Решение

Описание: на вход эта процедура получает саму строку и разделитель элементов. Фактически она делает преобразование, обратное к действию агрегатной функции list.

Версия СУБД: поддерживается в СУБД Firebird 1.5 и выше.

Входные параметры: 
  • DELIMITED_LIST varchar(1000) — список в виде строки
  • MIN_LENGTH varchar(10) — разделитель элементов списка.
Выходные параметры:
  • LIST_NUM smallint — порядковый номер элемента (если 0, то список пустой),
  • LIST_ITEM varchar(1000) — очередной элемент списка.
Примеры: см. выше, в постановке задачи.

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

Текст процедуры (дважды щёлкните на нём мышью, чтобы выделить для копирования):
create or alter procedure SPLIT (
  DELIMITED_LIST varchar(1000),
  DELIMITER varchar(10) = ',')
returns (
  LIST_NUM smallint,
  LIST_ITEM varchar(1000))
as
declare variable I smallint;
begin
  LIST_NUM = 0;
  if (DELIMITED_LIST is null or DELIMITED_LIST || '.' = '.') then
  /* сравнивать с пустой строкой напрямую в SQL нельзя, т.к. игнорируются хвостовые пробелы,
     см. спецификацию ANSI/ISO SQL-92, раздел 8.2 */
  begin
    LIST_ITEM = DELIMITED_LIST;
    suspend;
  end
  else
    while (DELIMITED_LIST || '.' <> '.') do
    begin
      if (DELIMITER is null or DELIMITER || '.' = '.') then
      begin
        LIST_ITEM = substring(DELIMITED_LIST from 1 for 1);
        DELIMITED_LIST = substring(DELIMITED_LIST from 2);
      end
      else
      begin
        I = position(DELIMITER in DELIMITED_LIST);
        if (I = 0) then
        begin
          LIST_ITEM = DELIMITED_LIST;
          DELIMITED_LIST = '';
        end
        else
        begin
          LIST_ITEM = substring(DELIMITED_LIST from 1 for I - 1);
          DELIMITED_LIST = substring(DELIMITED_LIST from I + char_length(DELIMITER));
        end
      end
      LIST_NUM = LIST_NUM + 1;
      suspend;
    end
end

Склонение размерности единиц измерения при числительных

Постановка задачи

Дано целое число и название размерности («рубль», «килограмм» и т. п.). Требуется выбрать правильный падеж и число для размерности, чтобы они соответствовали переданному значению: «1 рубль», «3 рубля», «15 рублей».

Решение № 1 (Firebird 2.1 и старше)

Описание: на вход эта процедура получает число и название единицы измерения в разных падежах и числах, а возвращает выбранный в зависимости от значения вариант.

Отмечу, что в действительности процедура здесь и не нужна, потому что состоит она из единственной строки. Если она нужна всего один-два раза, то можно просто взять эту строку и скопировать в свои запросы.

Версия СУБД: поддерживается в СУБД Firebird 2.1 и выше.

Входные параметры: 
  • NUM integer — число
  • TITLE0 varchar(100) — именительный падеж размерности, единственное число (например, «рубль», «день»)
  • TITLE1 varchar(100) — родительный падеж размерности, единственное число (например, «рубля», «дня»)
  • TITLE2 varchar(100) — родительный падеж размерности, множественное число (например, «рублей», «дней»)
Выходной параметр:
  • RESULT varchar(100) — название размерности в нужном падеже и числе.
Примеры:
(select RESULT from DECL_OF_NUM(213, 'день', 'дня', 'дней')) = 'дней';
(select RESULT from DECL_OF_NUM(42, 'рубль', 'рубля', 'рублей') = 'рубля';
Комментарии: как и в случае с переводом даты в строку (см. выше) эта процедура может показаться примитивной, но она оказывается полезной при форматировании строк.

Текст процедуры (дважды щёлкните на нём мышью, чтобы выделить для копирования):
create or alter procedure DECL_OF_NUM (
  NUM integer,
  TITLE0 varchar(100),
  TITLE1 varchar(100),
  TITLE2 varchar(100))
returns (
  RESULT varchar(100))
as
begin
  RESULT = iif(mod(NUM, 100) > 4 and mod(NUM, 100) < 20 or
    mod(NUM, 10) >= 5, TITLE2, decode(mod(NUM, 10), 0, TITLE2, 1,
    TITLE0, 2, TITLE1, 3, TITLE1, 4, TITLE1));
  suspend;
end

Решение № 2 (Firebird 1.0)

Описание: на вход эта процедура получает число и название единицы измерения в разных падежах и числах, а возвращает выбранный в зависимости от значения вариант.

Версия СУБД: поддерживается в СУБД Firebird 1.0 и выше.

Входные параметры: 
  • NUM integer — число
  • TITLE0 varchar(100) — именительный падеж размерности, единственное число (например, «рубль», «день»)
  • TITLE1 varchar(100) — родительный падеж размерности, единственное число (например, «рубля», «дня»)
  • TITLE2 varchar(100) — родительный падеж размерности, множественное число (например, «рублей», «дней»)
Выходной параметр:
  • RESULT varchar(100) — название размерности в нужном падеже и числе.
Примеры:
(select RESULT from DECL_OF_NUM(213, 'день', 'дня', 'дней')) = 'дней';
(select RESULT from DECL_OF_NUM(42, 'рубль', 'рубля', 'рублей') = 'рубля';
Комментарии: Это специальная версия процедуры для старых БД, в которых не поддерживаются нужные для предыдущего решения функции mod и decode. Вместо них тут формируется «виртуальная таблица», из которой выбирается нужное значение.

Текст процедуры (дважды щёлкните на нём мышью, чтобы выделить для копирования):
create or alter procedure DECL_OF_NUM (
  NUM integer,
  TITLE0 varchar(100),
  TITLE1 varchar(100),
  TITLE2 varchar(100))
returns (
  RESULT varchar(100))
as
begin
  if (NUM - NUM / 100 * 100 > 4 and NUM - NUM / 100 * 100 < 20 or NUM - NUM / 10 * 10 >= 5) then
    RESULT = TITLE2;
  else
    select
      NAME
    from
      (select
         0 as ID,
         :TITLE2 as NAME
       from
         RDB$DATABASE
       union all
       select
         1 as ID,
         :TITLE0 as NAME
       from
         RDB$DATABASE
       union all
       select
         2 as ID,
         :TITLE1 as NAME
       from
         RDB$DATABASE
       union all
       select
         3 as ID,
         :TITLE1 as NAME
       from
         RDB$DATABASE
       union all
       select
         4 as ID,
         :TITLE1 as NAME
       from
         RDB$DATABASE)
    where
      ID = :NUM - :NUM / 10 * 10
    into
      :RESULT;
  suspend;
end

Поиск латинских букв в русских словах

Постановка задачи

Имеется столбец в таблице, который должен содержать только русские буквы (например, ФИО). Но программа для ввода данных не умеет фильтровать русские и латинские буквы, из-за чего иногда русские буквы оказываются заменены похожими латинскими (русская «о» или «а» визуально неотличима от латинской). Нужно, во-первых, найти все такие строки, во-вторых, визуально выделить в них подлежащую замене букву, в-третьих, заменить её на русскую.

Предупреждения: Во-первых, многое зависит от шрифта. Во-вторых, в примерах ниже проверяются буквы, которые визуально совпадают в обычном прямом начертании большинства традиционных шрифтов. В курсивном начертании некоторые пары букв будут другими, например, для шрифтов Arial и Times New Roman добавляются строчная латинская «u» (u) и русская «и» (и) или строчная латинская «m» (m) и русская строчная «т» (т). В прямом начертании эти буквы в большинстве шрифтов совсем не похожи. Не похожи они и в курсиве шрифта Ubuntu, который используется в десктопной версии этого блога.

Решение № 1 (запрос, «функциональный» стиль)

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

Версия СУБД: поддерживается в СУБД Firebird 2.1 и выше.

Входные параметры:  нет.

Возвращаемые значения зависят от структуры таблицы. Я для примера использовал простую таблицу с двумя полями: ID (первичный ключ) и NAME (наименование, в котором мы ищем латинские буквы).
  • ID integer — первичный ключ таблицы
  • BAD_NAME varchar(...) (размерность зависит от размерности поля NAME) — поле NAME, в котором латинские буквы выделены квадратными скобками, чтобы сразу их увидеть
  • GOOD_NAME varchar(...) (размерность зависит от размерности поля NAME) — поле NAME, в котором латинские буквы заменены на похожие русские.
Комментарии: достоинством этого решения является то, что оно заметно короче и чуть быстрее работает, чем второе. Недостатки: во-первых, трудно воспринимается человеком, во-вторых, если понадобится добавить символ, то это не очень удобно делать.

Текст запроса (дважды щёлкните на нём мышью, чтобы выделить для копирования):
select
  ID,
  replace(replace(replace(replace(replace(replace(replace(replace(replace(replace(replace(replace(replace(replace(replace(replace(replace(replace(NAME, 'T', '[T]'), 'K', '[K]'), 'H', '[H]'), 'M', '[M]'), 'B', '[B]'), 'C', '[C]'), 'X', '[X]'), 'A', '[A]'), 'P', '[P]'), 'O', '[O]'), 'E', '[E]'), 'c', '[c]'), 'y', '[y]'), 'p', '[p]'), 'x', '[x]'), 'e', '[e]'), 'a', '[a]'), 'o', '[o]') as BAD_NAME,
  replace(replace(replace(replace(replace(replace(replace(replace(replace(replace(replace(replace(replace(replace(replace(replace(replace(replace(NAME, 'T', 'Т'), 'K', 'К'), 'H', 'Н'), 'M', 'М'), 'B', 'В'), 'C', 'С'), 'X', 'Х'), 'A', 'А'), 'P', 'Р'), 'O', 'О'), 'E', 'Е'), 'c', 'с'), 'y', 'у'), 'p', 'р'), 'x', 'х'), 'e', 'е'), 'a', 'а'), 'o', 'о') as GOOD_NAME
from
  MY_TABLE
where
  NAME similar to '%[eyopaxcTKHMBEOPAXC]+%'
Вариант: если нам непременно нужно «выловить» спрятанные среди кириллицы латинские буквы (то есть текст не содержит слов, полностью набранных латинскими буквами), то регулярное выражение усложняется:
select
  ID,
  replace(replace(replace(replace(replace(replace(replace(replace(replace(replace(replace(replace(replace(replace(replace(replace(replace(replace(NAME, 'T', '[T]'), 'K', '[K]'), 'H', '[H]'), 'M', '[M]'), 'B', '[B]'), 'C', '[C]'), 'X', '[X]'), 'A', '[A]'), 'P', '[P]'), 'O', '[O]'), 'E', '[E]'), 'c', '[c]'), 'y', '[y]'), 'p', '[p]'), 'x', '[x]'), 'e', '[e]'), 'a', '[a]'), 'o', '[o]') as BAD_NAME,
  replace(replace(replace(replace(replace(replace(replace(replace(replace(replace(replace(replace(replace(replace(replace(replace(replace(replace(NAME, 'T', 'Т'), 'K', 'К'), 'H', 'Н'), 'M', 'М'), 'B', 'В'), 'C', 'С'), 'X', 'Х'), 'A', 'А'), 'P', 'Р'), 'O', 'О'), 'E', 'Е'), 'c', 'с'), 'y', 'у'), 'p', 'р'), 'x', 'х'), 'e', 'е'), 'a', 'а'), 'o', 'о') as GOOD_NAME
from
  MY_TABLE
where
  NAME similar to '(%([[:WHITESPACE:]]|[^[:ALPHA:]]))?[eyopaxcTKHMBEOPAXC]+(([[:WHITESPACE:]]|[^[:ALPHA:]])%)?'

Решение № 2 (блок, «процедурный» стиль)

Описание: блок, в который также нужно подставить имена таблицы и поля.

Версия СУБД: поддерживается в СУБД Firebird 2.1 и выше.

Входные параметры:  нет.

Возвращаемые значения зависят от структуры таблицы. Я для примера использовал простую таблицу с двумя полями: ID (первичный ключ) и NAME (наименование, в котором мы ищем латинские буквы).
  • ID integer — первичный ключ таблицы
  • BAD_NAME varchar(...) (размерность зависит от размерности поля NAME) — поле NAME, в котором латинские буквы выделены квадратными скобками, чтобы сразу их увидеть
  • GOOD_NAME varchar(...) (размерность зависит от размерности поля NAME) — поле NAME, в котором латинские буквы заменены на похожие русские.
Комментарии: достоинством этого решения является то, что оно легко читается и здесь легко добавить или убрать любую пару похожих символов (они компактно собраны в одном месте). Недостатки: размер стал заметно больше, медленнее работает на больших таблицах.

Текст запроса (дважды щёлкните на нём мышью, чтобы выделить для копирования):
execute block
returns (
  ID type of column MY_TABLE.ID,
  BAD_NAME type of column MY_TABLE.NAME,
  GOOD_NAME type of column MY_TABLE.NAME)
as
declare variable LETTERS varchar(26);
declare variable REPLACEMENTS varchar(26);
declare variable LETTER_COUNT smallint;
declare variable I smallint;
declare variable C char(1);
begin
  LETTERS = 'eyopaxcEOPAHKXCBM';
  REPLACEMENTS = 'еуорахсЕОРАНКХСВМ';
  LETTER_COUNT = char_length(LETTERS);
  for select
        ID,
        NAME
      from
        MY_TABLE
      where
        NAME similar to '%[' || :LETTERS || ']+%'
/* '(%([[:WHITESPACE:]]|[^[:ALPHA:]]))?[' || :LETTERS || ']+(([[:WHITESPACE:]]|[^[:ALPHA:]])%)?'
для поиска «спрятанных» среди кириллицы букв */
      into
        :ID,
        :BAD_NAME
  do
  begin
    GOOD_NAME = BAD_NAME;
    I = 1;
    while (I <= LETTER_COUNT) do
    begin
      C = substring(LETTERS from I for 1);
      BAD_NAME = replace(BAD_NAME, C, '[' || C || ']');
      GOOD_NAME = replace(GOOD_NAME, C, substring(REPLACEMENTS from I for 1));
      I = I + 1;
    end
    suspend;
  end
end

Комментариев нет:

Отправить комментарий

К началу