Archivo de la etiqueta: SQL

Tratamiento de NIF españoles (parte 2)

Una expresión regular para tratar los NIF españoles eliminando todos los caracteres que no sean números o letras. Además, comprueba el tamaño del NIF para eliminar el código de país (identificador fiscal comunitario) si fuera necesario:

select result1 ORIGINAL_TAXID, 
    upper(
        regexp_replace( 
            regexp_replace(result1, '[^[:alnum:]]', ''), -- Remove non-digit and non-alphabetic characters 
            '^(ES)([[:alnum:]]{9})$', -- Identify spanish NIFs with country code 
            '\2')) CLEAN_AND_NATIONAL_TAXID -- Remove spanish country code 
from ( 
    WITH test AS
     (SELECT 'ESA45678901,B23456789,GB3456789,12345678L,B234567890123,ES-A45678901,B.23456789,GB3::456789,B234_567_890123' col1 FROM dual) 
    SELECT regexp_substr(col1, '[^,]+', 1, rownum) result1 
      FROM test 
    CONNECT BY LEVEL <= regexp_count(col1, ',') + 1);

El resultado es:

ORIGINAL_TAXID	CLEAN_AND_NATIONAL_TAXID
--------------  ------------------------
ESA45678901	A45678901
B23456789	B23456789
GB3456789	GB3456789
12345678L	12345678L
B234567890123	B234567890123
ES-A45678901	A45678901
B.23456789	B23456789
GB3::456789	GB3456789
B234_567_890123	B234567890123

En el artículo Tratamiento de NIF españoles se muestra una expresión regular para añadir el código de país.

Anuncios

Extraer valores de una lista contenida en una columna

En ciertas ocasiones nos vemos en la necesidad de almacenar una serie de valores distintos en una misma columna. Especialmente cuando estamos tratando con datos de configuración que pueden variar en el número y valores posibles con el tiempo. Explotar esta información desde SQL suele presentar muchos inconvenientes puesto que se trata de información sin normalizar y que requiere tratar con cadenas de texto de alta variabilidad.

Gracias a la implantación de expresiones regulares dentro de SQL podemos mejorar las alternativas a la hora de consultar este tipo de datos.

Tomemos, a modo de prueba, las siguientes cadenas de texto:

TYPE=String#LENGTH=18#DEFAULT=empty
LENGTH=1#TYPE=Number#DECIMALS=0#DEFAULT=0
LENGTH=10#DEFAULT=1.00#TYPE=Number#DECIMALS=2

Mediante el uso de las funciones “regexp_instr” y “regexp_substr” dentro de una consulta SELECT podemos extraer los distintos componentes de cada una de las cadenas y, por ejemplo, mostrarlos como columnas:

with test_tab as (
    select 1 cfg, 'TYPE=String#LENGTH=18#DEFAULT=empty' config from dual
    union all
    select 2 cfg, 'LENGTH=1#TYPE=Number#DECIMALS=0#DEFAULT=0' config from dual
    union all
    select 3 cfg, 'LENGTH=10#DEFAULT=1.00#TYPE=Number#DECIMALS=2' config from dual)
select cfg,
       decode((regexp_instr(config, '(^|#)TYPE=(.+)($|#)')),     0, '', (regexp_substr(config, '[^#]+', (regexp_instr(config, '(^|#)TYPE=(.+)($|#)')),     1))) cfg_TYPE,
       decode((regexp_instr(config, '(^|#)LENGTH=(.+)($|#)')),   0, '', (regexp_substr(config, '[^#]+', (regexp_instr(config, '(^|#)LENGTH=(.+)($|#)')),   1))) cfg_LENGTH,
       decode((regexp_instr(config, '(^|#)DECIMALS=(.+)($|#)')), 0, '', (regexp_substr(config, '[^#]+', (regexp_instr(config, '(^|#)DECIMALS=(.+)($|#)')), 1))) cfg_DECIMALS,
       decode((regexp_instr(config, '(^|#)DEFAULT=(.+)($|#)')),  0, '', (regexp_substr(config, '[^#]+', (regexp_instr(config, '(^|#)DEFAULT=(.+)($|#)')),  1))) cfg_DEFAULT,
       config
from test_tab;

Resultado:

CFG	CFG_TYPE	CFG_LENGTH	CFG_DECIMALS	CFG_DEFAULT	CONFIG
---	-----------	----------	-------------	-----------	----------------------------------------
1	TYPE=String	LENGTH=18	DEFAULT=empty			TYPE=String#LENGTH=18#DEFAULT=empty
2	TYPE=Number	LENGTH=1	DECIMALS=0	DEFAULT=0	LENGTH=1#TYPE=Number#DECIMALS=0#DEFAULT=0
3	TYPE=Number	LENGTH=10	DECIMALS=2	DEFAULT=1.00	LENGTH=10#DEFAULT=1.00#TYPE=Number#DECIMALS=2

Estas mismas funciones nos pueden servir para filtrar los datos en la cláusula WHERE o para modificar los datos de la cadena a través del operador SET en una operación UPDATE.

Por último, mediante la cláusula CONNECT BY, podemos listar todos los datos de una de las listas y mostrarlos como filas distintas en el resultado de la consulta:

with test_tab as (select 1 cfg, 'TYPE=String#LENGTH=18#DEFAULT=empty' config from dual)
select cfg, config, regexp_substr(config, '[^#]+', 1, level) key_value
from test_tab
connect by regexp_substr(config, '[^#]+', 1, level) is not null;

Resultado:

CFG	CONFIG					KEY_VALUE
---	-----------------------------------	-------------
1	TYPE=String#LENGTH=18#DEFAULT=empty	TYPE=String
1	TYPE=String#LENGTH=18#DEFAULT=empty	LENGTH=18
1	TYPE=String#LENGTH=18#DEFAULT=empty	DEFAULT=empty

Select sobre colecciones PL/SQL correlacionadas

Oracle permite acceder a los valores de colecciones PL/SQL desde sentencias SQL. Esta propiedad del comando SELECT se llama table_collection_expression y muestra el contenido de una colección como si de una tabla se tratara.

Para que el proceso funcione correctamente es necesario que la colección esté declarada en el esquema, no dentro de un paquete:

-- Create PL/SQL collection
create or replace type ty_number is table of number;

-- Select from one PL/SQL collection
select * from table(ty_number(10,20,30,50,40));

La sentencia daría como resultado:

COLUMN_VALUE
------------
10
20
30
50
40

Esta propiedad puede resultarnos muy útil cuando estamos programando en PL/SQL y, por ejemplo, necesitamos extraer datos de una consulta a través de un proceso bulk collection y compararlos con registros de otras tablas.

Pero, ¿qué ocurre si necesitamos cruzar datos de dos colecciones PL/SQL y datos de tablas? ¿Cómo vamos a correlacionar los elementos de varias colecciones entre sí? En estos casos podemos recurrir a una sentencia join en combinación con la cláusula connect by level para generar los índices que nos permitirán recorrer las colecciones:

-- Select from two correlated PL/SQL collections
select coll_index, coll_1_value, coll_2_value
from (select level coll_index from dual connect by level <= 5) gen_idx -- Generate index for iterate collections
     join (select rownum rn, column_value coll_1_value from table(ty_number(10,20,30,50,40))) coll_1 on gen_idx.coll_index = coll_1.rn -- Collection 1
     join (select rownum rn, column_value coll_2_value from table(ty_number(11,21,31,51,41))) coll_2 on gen_idx.coll_index = coll_2.rn; -- Collection 2

La ejecución de la sentencia produce el siguiente resultado:

COLL_INDEX	COLL_1_VALUE	COLL_2_VALUE
----------	------------	------------
1		10		11
2		20		21
3		30		31
4		50		51
5		40		41

Como puede apreciarse al fijarnos en las filas 4 y 5, los elementos de las colecciones mantienen el orden original y la correlación entre sus elementos.

Select random rows

Para seleccionar una o varias filas de una tabla de manera aleatoria existen distintos enfoques. En este ejemplo se intenta obtener una fila de la tabla EMP tomando una muestra del 25% de los registros totales sobre el que posteriormente se aplica una ordenación utilizando la función DBMS_RANDOM.random. De esta forma podemos aplicar una ordenación aleatoria sobre los registros de la tabla evitando el alto coste de ejecutar la función random en tablas que sean muy grandes.

-- Select 1 random row from EMP table
select *
from ( select *
    from emp sample(25) -- 25% total rows
    order by dbms_random.random)
where rownum = 1;

El resultado:

EMPNO	ENAME	JOB	MGR	HIREDATE	SAL	COMM	DEPTNO
7698	BLAKE	MANAGER	7839	01-MAY-81	2850	-	30

GitHub: Oracle Database Sample Schemas

A través de GitHub – oracle/db-sample-schemas Oracle pone a disposición de la comunidad de desarrolladores y diseñadores de bases de datos los scripts para generar los siguientes schemas:

  • HR: Human Resources
  • OE: Order Entry
  • PM: Product Media
  • IX: Information Exchange
  • SH: Sales History
  • BI: Business Intelligence

Tratamiento de NIF españoles

Una expresión regular para tratar los NIF españoles eliminando todos los caracteres que no sean números o letras. Además, comprueba el tamaño del NIF para incluir el código de país (identificador fiscal comunitario) si fuera necesario:

select result1 ORIGINAL_TAXID, 
    upper(
        regexp_replace( 
            regexp_replace(result1, '[^[:alnum:]]', ''), -- Remove non-digit and non-alphabetic characters 
            '^([[:alnum:]]{1}[[:digit:]]{1})([[:alnum:]]{7})$', -- Identify spanish NIFs without country code 
            'ES\1\2')) CLEAN_AND_COMPLETE_TAXID -- Add spanish country code 
from ( 
    WITH test AS 
     (SELECT 'ESA45678901,B23456789,GB3456789,12345678L,B234567890123,ES-A45678901,B.23456789,GB3::456789,B234_567_890123' col1 FROM dual) 
    SELECT regexp_substr(col1, '[^,]+', 1, rownum) result1 
      FROM test 
    CONNECT BY LEVEL <= regexp_count(col1, ',') + 1 
);

El resultado es:

ORIGINAL_TAXID	CLEAN_AND_COMPLETE_TAXID
--------------- ------------------------
ESA45678901	ESA45678901
B23456789	ESB23456789
GB3456789	GB3456789
12345678L	ES12345678L
B234567890123	B234567890123
ES-A45678901	ESA45678901
B.23456789	ESB23456789
GB3::456789	GB3456789
B234_567_890123	B234567890123

UUID en forma canónica

Cómo generar de un “Universally Unique Identifier” (UUID) desde una select y convertirlo a su forma canónica “8-4-4-4-12”:

select lower(
        regexp_replace(
           rawtohex(sys_guid())
           ,'([A-F0-9]{8})([A-F0-9]{4})([A-F0-9]{4})([A-F0-9]{4})([A-F0-9]{12})'
           ,'\1-\2-\3-\4-\5')) GUID_canonical_format
from dual;

Resultado:

GUID_CANONICAL_FORMAT
------------------------------------
34266296-4de0-26df-e053-0100007fd523