За годы работы у меня скопилось множество различных готовых решений для часто возникающих задач. Про замысловатые вещи (генератор паролей и 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)
— отформатированная дата.
1 2 3 | ( 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' ; |
Комментарии: достоинством этого решения является его простота и эффективность, а недостатком — опять-таки простота: поддерживается только один жёстко заданный формат, а для любого другого нужно писать отдельную процедуру.
Текст процедуры (дважды щёлкните на нём мышью, чтобы выделить для копирования):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | 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)
— отформатированная дата.
1 2 3 4 | ( 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' -- (зависит от текущей даты) |
Комментарии: достоинством этого решения является его универсальность, а недостатком — сложность и низкая скорость выполнения.
Текст процедуры (дважды щёлкните на нём мышью, чтобы выделить для копирования):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 | 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-ричное представление числа.
Примеры:
1 2 | ( 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
должна увеличиться ровно вдвое (а больше никаких изменений не требуется).Текст процедуры (дважды щёлкните на нём мышью, чтобы выделить для копирования):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | 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 (название). Нам нужно выбрать из неё строки с определёнными конкретными кодами, количество которых заранее неизвестно. Мы можем выразить это так:
1 2 3 4 5 6 7 8 9 10 | 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)
— очередной элемент списка.
Комментарии: важно отметить, что эта процедура не производит полноценный парсинг строки и не умеет распознавать экранированные разделители, как это делают многие языки программирования. Хотя её не так уж сложно доработать для этого.
Текст процедуры (дважды щёлкните на нём мышью, чтобы выделить для копирования):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 | 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)
— название размерности в нужном падеже и числе.
Примеры:
1 2 | ( select RESULT from DECL_OF_NUM(213, 'день' , 'дня' , 'дней' )) = 'дней' ; ( select RESULT from DECL_OF_NUM(42, 'рубль' , 'рубля' , 'рублей' ) = 'рубля' ; |
Комментарии: как и в случае с переводом даты в строку (см. выше) эта процедура может показаться примитивной, но она оказывается полезной при форматировании строк.
Текст процедуры (дважды щёлкните на нём мышью, чтобы выделить для копирования):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | 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)
— название размерности в нужном падеже и числе.
Примеры:
1 2 | ( select RESULT from DECL_OF_NUM(213, 'день' , 'дня' , 'дней' )) = 'дней' ; ( select RESULT from DECL_OF_NUM(42, 'рубль' , 'рубля' , 'рублей' ) = 'рубля' ; |
Комментарии: Это специальная версия процедуры для старых БД, в которых не поддерживаются нужные для предыдущего решения функции mod и decode. Вместо них тут формируется «виртуальная таблица», из которой выбирается нужное значение.
Текст процедуры (дважды щёлкните на нём мышью, чтобы выделить для копирования):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 | 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, который используется в десктопной версии этого блога.
Предупреждения: Во-первых, многое зависит от шрифта. Во-вторых, в примерах ниже проверяются буквы, которые визуально совпадают в обычном прямом начертании большинства традиционных шрифтов. В курсивном начертании некоторые пары букв будут другими, например, для шрифтов 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, в котором латинские буквы заменены на похожие русские.
Комментарии: достоинством этого решения является то, что оно заметно короче и чуть быстрее работает, чем второе. Недостатки: во-первых, трудно воспринимается человеком, во-вторых, если понадобится добавить символ, то это не очень удобно делать.
Текст запроса (дважды щёлкните на нём мышью, чтобы выделить для копирования):
1 2 3 4 5 6 7 8 | 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]+%' |
Вариант: если нам непременно нужно «выловить» спрятанные среди кириллицы латинские буквы (то есть текст не содержит слов, полностью набранных латинскими буквами), то регулярное выражение усложняется:
1 2 3 4 5 6 7 8 | 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, в котором латинские буквы заменены на похожие русские.
Комментарии: достоинством этого решения является то, что оно легко читается и здесь легко добавить или убрать любую пару похожих символов (они компактно собраны в одном месте). Недостатки: размер стал заметно больше, медленнее работает на больших таблицах.
Текст запроса (дважды щёлкните на нём мышью, чтобы выделить для копирования):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 | 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 |
Комментариев нет:
Отправить комментарий
Пожалуйста, не используйте в сообщениях ненормативную лексику и нарушающие закон темы