jueves, 23 de mayo de 2013

Expresiones regulares en oracle


Introducción al uso de las Expresiones Regulares en una Base de Datos Oracle

Por Fernando García oracle ace
Publicado en marzo 2012
Prólogo
Históricamente Oracle había ofrecido prestaciones muy básicas para el uso de expresiones regulares y la manipulación de cadenas de caracteres. Aquellos desarrolladores que necesitaban hacer uso de expresiones regulares se veían prácticamente obligados a extraer los datos de la base para manipular las cadenas de caracteres en capas intermedias de la aplicación. A partir de la versión 10g, con la introducción de las llamadas funciones REGEXP (REGular EXPressions), Oracle nos ofrece una batería de sofisticadas y potentes herramientas nativas que permiten al desarrollador centralizar el procesamiento intensivo de cadenas de caracteres dentro de la base de datos Oracle y con los lenguajes SQL y PL/SQL.
Tabla de Contenidos
¿Qué son las Expresiones Regulares?
Antes de introducirnos bien en el mundo de las expresiones regulares es necesario que le perdamos un poco el miedo. Lo más común es que un ser humano huya desesperadamente cuando se encuentra por primera vez con una expresión regular como la siguiente:
^Ab*[0-8]\*(ha|ve)n$ (a\*) el asterisco seria un caracter

Sin embargo, un buen manejo de las expresiones regulares es algo tan poderoso que vale la pena que nos tomemos un tiempo para aprender a utilizarlas. Además, saber manejar expresiones regulares nos servirá no solamente en el mundo de las bases de datos Oracle; las podremos usar también en otros lenguajes de programación como Perl, Java, .Net, Php y Unix Shell Scripting, entre otros.
La idea de este documento es hacer una introducción al tema de manera práctica, comenzando con ejemplos sencillos para luego ir aumentando la complejidad.
Comenzamos entonces definiendo a una expresión regular como una cadena de caracteres que definen un patrón de búsqueda. En una expresión regular encontramos literales y metacaracteres. Los literales se leen al pie de la letra. Los metacaracteres son caracteres que tienen un significado especial.
Tomemos por ejemplo la siguiente expresión regular:
[n|p]ata

Los metacaracteres son los corchetes y el símbolo pipe (|). El resto son literales.
Los corchetes agrupan a varios caracteres en un solo caracter.
El símbolo pipe indica un símbolo u otro; es decir la 'n' o la 'p'.
Luego, la expresión regular:
[n|p]ata

Coincide con las cadenas:
nata
pata

Seguramente el lector se preguntará cómo determinar qué elementos de una expresión regular son literales y qué elementos son metacaracteres. Más adelante encontrará la respuesta. Sigamos.
¿Para qué sirven las Expresiones Regulares?
En una base de datos Oracle existen diversos escenarios en que la implementación de expresiones regulares constituye una herramienta de gran utilidad:
  • Búsqueda de texto. Las expresiones regulares nos permiten hacer búsquedas complejas de cadenas de caracteres dentro de las columnas de nuestras tablas.
  • Formateo de datos. Podemos modificar los datos proyectados en las sentencias SELECT, invirtiendo palabras, agregando o quitando caracteres, etc.
  • Definición de constraints. A fin de forzar el contenido de una columna para que se ajuste a un formato determinado: casilla de correo, número telefónico, etc.
  • Manipulación de datos. Por ejemplo, regularizando datos en procesos de migración desde aplicaciones legacy y aplicando reglas de búsqueda y reemplazo (Search & Replace).
Entendiendo las Expresiones Regulares
Como se había dicho anteriormente, en una expresión regular encontramos literales y metacaracteres. Al analizar una expresión regular, lo primero que tenemos que hacer es determinar qué elementos son literales y qué elementos son metacaracteres (también llamados operadores). Existe una larga lista de metacaracteres soportados por Oracle. No es la intención estudiarlos a todos en este documento. A continuación iremos viendo algunos.
El metacaracter punto (.)
El metacaracter punto coincide con cualquier carácter. Por ejemplo, en la expresión regular
ca.a

Para que una cadena de caracteres coincida con dicho patrón:
- Debe estar el literal 'c'.
- Le debe seguir el literal 'a'
- Le debe seguir un y solamente un carácter cualquiera.
- Le debe seguir el literal 'a'
En la expresión regular encontramos 3 literales y el metacaracter punto. Analicemos las siguientes cadenas y veamos cuales coinciden con el patrón buscado y cuáles no.
Cadena¿Coincide?Análisis
casaSiCoinciden los literales y en la posición del operador punto aparece sólo un carácter
canaSiCoinciden los literales y en la posición del operador punto aparece sólo un carácter
caraSiCoinciden los literales y en la posición del operador punto aparece sólo un carácter
cantaNoEn la posición del punto aparece más de un carácter
palaNoEl literal 'p' en la primer posición no respeta el literal ´c´ definido en la expresión regular

El metacaracter suma (+)
El metacaracter suma coincide con una o más ocurrencias de la subexpresión que lo precede. Por ejemplo, analicemos la expresión regular
cas+a

Para que una cadena de caracteres coincida con dicho patrón:
- Debe estar el carácter 'c'
- Le debe seguir el carácter 'a'
- Le debe seguir una o mas ocurrencias del carácter 's'
- Le debe seguir el carácter 'a'
Vemos nuevamente tres literales y el metacaracter Suma. Busquemos el patrón en las siguientes cadenas y veamos las coincidencias.
Cadena¿Coincide?Análisis
cassa
cassssa
cassssssssa
SiEl símbolo más (+) es precedido por la subexpresión "s". Por lo tanto todas las cadenas que tienen una o más "s" en a partir de la tercer posición y respetan el resto de los literales, coinciden con el patrón representado en la expresión regular.
CaaNoEl patrón definido requiere al menos una ‘s’ en la tercer posición
LasaNoNo respeta todos los literales de la expresión regular
TassaNoNo respeta todos los literales de la expresión regular

El metacaracter asterisco (*)
Coincide con cero o más ocurrencias de la subexpresión que le precede al asterisco. Analicemos la expresión:
cas*a

Para que una cadena de caracteres coincida con dicho patrón:
- Debe estar el carácter 'c' - Le debe seguir el carácter 'a' - Le debe seguir cero o mas ocurrencias del carácter 's'. Es decir que el carácter 's' puede o no estar. - Le debe seguir el carácter 'a'
Analicemos qué pasa con las mismas cadenas de caracteres del ejemplo anterior:
Cadena¿Coincide?Análisis
Casa
cassssa
cassssssssa
SiEl símbolo asterisco (*) es precedido por la subexpresión "s". Por lo tanto todas las cadenas que tienen cero o más "s" a partir de la tercera posición y respetan el resto de los literales, coinciden con el patrón representado en la expresión regular.
caaSiEl patrón acepta la no existencia del carácter 's' en la tercera posición.
lasaNoNo respeta todos los literales de la expresión regular
tassaNoNo respeta todos los literales de la expresión regular

El metacaracter [char...]
Coincide con una sola ocurrencia de alguno de los caracteres de la lista entre corchetes. En la lista, todos los caracteres son tomados como literales excepto algunos caracteres que son interpretados como metacaracteres. Aquí nos limitaremos a mencionar el metacaracter '-' para representar rangos de caracteres. Analicemos la siguiente expresión regular:
ca[snt]a

Para que una cadena de caracteres coincida con dicho patrón:
- Debe estar el carácter 'c'
- Le debe seguir el carácter 'a'
- Le debe seguir uno y solamente uno de los siguientes caracteres: 's', 'n' o 't'.
- Le debe seguir el carácter 'a'
Cadena¿Coincide?Análisis
casaSiCoinciden los literales y en la posición del operador aparece sólo un carácter y es de los de la lista
canaSiCoinciden los literales y en la posición del operador aparece sólo un carácter y es de los de la lista
cataSiCoinciden los literales y en la posición del operador aparece sólo un carácter y es de los de la lista
caraNoEn la posición del operador aparece un solo carácter pero no está en la lista
cantaNoSi bien en la posición del operador aparecen literales de la lista; son más de uno
pasaNoNo respeta el primer literal de la lista

Como se dijo anteriormente, en la lista se puede utilizar el metacaracter rango '-'. Haciendo uso de este metacaracter la siguiente lista de caracteres:
[123456789]

Podemos expresarla como
[1-9]

Y la siguiente lista de caracteres
[abcdef]

Podemos expresarla como
[a-f]

Analicemos ahora la siguiente expresión regular:
a[3-7][f-i]9

Para que una cadena coincida con el patrón:
- Debe tener el literal 'a'
- Le debe seguir un numero de 3 a 7. Es decir: 3, 4, 5, 6 ó 7.
- Le debe seguir una letra de la f a la i. Es decir 'f', 'g', 'h' o 'i'
- Le debe seguir el numero 9
Cadena¿Coincide?Análisis
a3h9SiCoinciden los literales y en la posición de los operadores aparecen literales incluidos en los rangos especificados en el patrón de la expresión regular.
a33f9NoEn la posición del primer operador aparece más de un caracter
a3z9NoLa 'z' no está incluida en el rango del segundo operador

El metacaracter [^char...]
El sombrerito (^) que precede a la lista indica que los literales que le siguen no deben estar en la cadena de caracteres para que se produzca una coincidencia. Veamos,
ca[^snt]a

Para que una cadena de caracteres coincida con dicho patrón:
- Debe estar el carácter 'c'
- Le debe seguir el carácter 'a'
- Le debe seguir uno y solamente un carácter que no sea ni 's' ni 'n' ni 't'.
- Le debe seguir el carácter 'a'
Cadena¿Coincide?Análisis
casaNoCoinciden los literales y en la posición del operador aparece un carácter de los negados en la lista
canaNoCoinciden los literales y en la posición del operador aparece un carácter de los negados en la lista
cataNoCoinciden los literales y en la posición del operador aparece un carácter de los negados en la lista
caraSiCoinciden los literales y en la poscion del operador aparece un carácter que no está en la lista de literales negados.
cantaNoEn la posición del operador aparece más de un valor
paraNoNo respeta el primer literal de la lista

El metacaracter subexpresión (expr)
Considera a toda la expresión entre paréntesis como una unidad. La expresión puede ser una simple cadena de literales o una expresión compleja conteniendo otros metacaracteres. Analicemos la siguiente expresión regular
chau(hola)*chau

Ahora el asterisco precede a la expresión
hola

El asterisco indica que la expresión
hola

Puede aparecer cero ó más veces
Cadena¿Coincide?Análisis
chauchauSiCoinciden los literales y la expresión 'hola' aparece cero veces
chauholachauSiCoinciden los literales y la expresión 'hola' aparece una vez, es decir cero o más veces.
chauholaholachauSiCoinciden los literales y la expresión 'hola' aparece dos veces, es decir cero o más veces.
holachauNoNo aparecen el literal chau' que precede al ‘hola’
chauNoNo aparece el literal 'chau' que precede al ‘hola’

El metacaracter de anclaje de principio de línea (^)
Coincide con el principio de línea y el operador está representado con el caracter sombrerito (^). En la expresión regular
^hola

Los literales que componen la palabra 'hola' deben estar al inicio de la línea para que se produzca la coincidencia con el patrón expresado.
Cadena¿Coincide?Análisis
holaSiLos literales que componen la palabra 'hola' están al inicio de la línea
chauholaNoLa línea no comienza con los literales que conforman la palabra 'hola'
holachauSiLos literales que componen la palabra 'hola' están al inicio de la línea
holaholaSiLos literales que componen la palabra 'hola' están al inicio de la línea

El metacaracter de anclaje de fin de línea ($)
Coincide con el final de línea y el operador está representado con el carácter pesos ($). En la expresión regular
hola$

Los literales que componen la palabra 'hola' deben estar al final de la línea para que se produzca la coincidencia con el patrón expresado.
Cadena¿Coincide?Análisis
HolaSiLos literales que componen la palabra 'hola' están al final de la línea
chauholaSiLa línea finaliza con los literales que conforman la palabra 'hola'
holachauNoLos literales que componen la palabra 'hola' no están al final de la línea
holaholaSiLos literales que componen la palabra 'hola' están al final de la línea

El metacaracter de escape (\)
Precediendo a un metacaracter con el símbolo de escape, el metacaracter será interpretado como un literal. El doble carácter de escape (\\) permite considerar al carácter de escape como literal.
En la expresión regular
hola\*

Cadena¿Coincide?Análisis
holaNoFalta el literal '*' que sigue literal 'a'
Hola*SiCoinciden todos los literales
hol*NoFaltan los literales 'a' y ‘*’

Construyendo Expresiones Regulares complejas
Hasta aquí hemos visto algunos de los metacaracteres que se usan con más frecuencia. Combinando varios de estos metacaracteres en una sola expresión regular, podemos hacer construcciones más complejas y muy poderosas. Analicemos la siguiente expresión regular en la que encontramos varios metacaracteres
^hola[0-9]*chau[^a]+$

Hagamos un desglose de la expresión regular:
^holaAl principio de la línea debe estar la palabra 'hola'
[0-9]*Luego de la palabra 'hola' deben aparecer cero o mas dígitos del 0 al 9
chauLuego debe aparecer el literal 'chau'
[^a]+$La línea debe continuar hasta el final con uno o más caracteres distintos de 'a'

Busquemos el patrón en algunas cadenas de caracteres
Cadena¿Coincide?Análisis
hola123chaubSiComienza con el texto 'hola'. Le siguen varios dígitos entre 0 y 9. Le sigue el literal 'chau' y finaliza con al menos una letra distinta de 'a'.
holachaucSiComienza con el texto 'hola'. No aparecen dígitos del 0 al 9 (el asterisco indica que esto es aceptado para que haya coincidencia). Luego aparece el literal 'chau' y se finaliza con una letra distinta de 'a'
hola0chauaNoFinaliza con una ‘a’

Uso de las Expresiones Regulares en Oracle
Hasta aquí hemos visto un poco de teoría y algunos ejemplos prácticos para conocer y aprender un poco acerca de las expresiones regulares. Pero, ¿cómo podemos hacer uso de este conocimiento adquirido en una base de datos Oracle?
A partir de la versión 10g Oracle nos ofrece un grupo de nuevas funciones y condiciones para poder manejar expresiones regulares en el lenguaje SQL:
REGEXP_LIKECondición que se puede utilizar en la cláusula WHERE de una sentencia SQL SELECT y que permite retornar aquellas filas que coinciden con el patrón especificado en una expresión regular.
REGEXP_COUNTFunción que permite contar el número de veces que un patrón aparece en una cadena de caracteres.
REGEXP_INSTRFunción que permite determinar la posición de inicio de un patrón específico en una cadena de caracteres.
REGEXP_REPLACEFunción que permite hacer búsqueda y reemplazo en una cadena de caracteres utilizando expresiones regulares para la búsqueda.
REGEXP_SUBSTRFunción para extraer de una cadena una subcadena de caracteres que coincidan con un patrón especificado en una expresión regular.

Veamos algunos ejemplos de implementación de estas condiciones y funciones combinadas con el uso de expresiones regulares.
La condición REGEXP_LIKE es similar a la condición LIKE pero, a diferencia del LIKE, utiliza patrones basados en expresiones regulares para la búsqueda de coincidencias en las cadenas de caracteres analizadas.
En el siguiente ejemplo, vemos una tabla de empleados cuyos nombres fueron migrados de un sistema legacy. En muchos casos el nombre ha quedado separado del apellido por varios espacios en blanco:
SQL> select nombre from empleados;
 
NOMBRE
----------------------------------------
Fernando         Garcia
Marcelo  Burgos
       Marcelo         Ochoa
Gerardo     Tezza
Clarisa Maman Orfali
Rodolfo  Pajuelo Quispe
 
6 rows selected.

El único caso de un solo espacio entre el nombre y el apellido es el de Clarisa Maman Orfali. Veamos cómo podemos construir un query que detecte aquellas filas en que haya dos o más espacios separando nombres de apellidos.
SQL> select nombre
  2  from empleados
  3  where regexp_like(nombre, '[ ][ ][ ]*');
 
NOMBRE
----------------------------------------
Fernando         Garcia
Marcelo  Burgos
       Marcelo         Ochoa
Gerardo     Tezza
Rodolfo  Pajuelo Quispe

Corrijamos esta situación dejando un solo blanco de separación en donde hay dos o más espacios en blanco.
SQL> update empleados
  2  set nombre = regexp_replace(nombre, '[ ][ ][ ]*', ' ')
  3  where regexp_count(nombre, '[ ][ ][ ]*') > 0;
 
5 rows updated.

Veamos cómo quedo la tabla luego de la corrección
SQL> select nombre from empleados;
 
NOMBRE
----------------------------------------
Fernando Garcia
Marcelo Burgos
 Marcelo Ochoa
Gerardo Tezza
Clarisa Maman Orfali
Rodolfo Pajuelo Quispe
 
6 rows selected.

Bastante bien. Salvo el caso de Marcelo Ochoa, en donde nos quedó un espacio en blanco al principio de la línea. Eliminemos entonces los espacios en blanco que están al principio de la línea.
SQL> update empleados
  2  set nombre = regexp_replace(nombre, '^ ')
  3  where regexp_count(nombre, '^ ') > 0
  4  /
 
1 row updated.
 
SQL> select nombre from empleados;
 
NOMBRE
----------------------------------------
Fernando Garcia
Marcelo Burgos
Marcelo Ochoa
Gerardo Tezza
Clarisa Maman Orfali
Rodolfo Pajuelo Quispe
 
6 rows selected.

En este otro ejemplo vemos una tabla de impuestos en la que almacenamos un código de identificación tributaria numérico y que debe respetar el siguiente formato:
99-99999999-99 
 
SQL> select cuit from impuestos;
 
CUIT
-------------
20-20198123-9
10-43112345-8
1-22123987-9
8883838382
aa-75212456-x

Busquemos aquellos códigos que no están respetando el formato requerido:
select cuit
from impuestos
where not regexp_like (cuit, '[0-9][0-9]-[0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9]-[0-9]')
 
CUIT
-------------
1-22123987-9
8883838382
aa-75212456-x

A fin de evitar que la tabla siga admitiendo códigos que no respetan el formato, podemos crear una constraint que fuerce el ingreso de códigos que respeten el formato.
alter table impuestos
add constraint c_cuit
check (regexp_like(cuit,'[0-9][0-9]-[0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9]-[0-9]'))
enable novalidate
/
Table altered.

Veamos qué ocurre al ingresar nuevas filas a la table de impuestos
SQL> insert into impuestos (cuit) values ('20-17979866-2');
 
1 row created.
 
SQL> insert into impuestos (cuit) values ('1x-17979866-2');
insert into impuestos (cuit) values ('1x-17979866-2')
*
ERROR at line 1:
ORA-02290: check constraint (SYS.C_CUIT) violated
 
 
SQL> insert into impuestos values ('12888122812');
insert into impuestos values ('12888122812')
*
ERROR at line 1:
ORA-02290: check constraint (SYS.C_CUIT) violated

Profundizando el conocimiento y uso de las expresiones regulares en Oracle
Hemos visto varios ejemplos para demostrar el potencial y utilidad que nos ofrecen las expresiones regulares en las bases de datos Oracle. Existen muchos más metacaracteres que no hemos mencionado aquí. Las funciones tampoco las hemos visto en profundidad; hay más argumentos para hacer búsquedas que ignoren mayúsculas y minúsculas, etc. Para seguir profundizando el tema, aquí dejamos algunos links de interés:
Using Regular Expressions in Database Applications
Oracle® Database Advanced Application Developer's Guide
11g Release 2 (11.2)
Part Number E25518-04
http://docs.oracle.com/cd/E11882_01/appdev.112/e25518/adfns_regexp.htm#ADFNS1003
Introducing Oracle Regular
Expressions
An Oracle White Paper
Author: Peter Linsley
http://www.oracle.com/technetwork/database/focus-areas/application-development/twp-regular-expressions-133133.pdf
Regular-Expressions.info
The Premier website about Regular Expressions
http://www.regular-expressions.info/

No hay comentarios:

Publicar un comentario