viernes, 28 de marzo de 2008

Más control sobre los LOOPS en 11g

En PL/SQL podemos salir de un LOOP con la cláusula EXIT. Típicamente la usamos para la condición de salida de un loop:
 BEGIN
LOOP
...
EXIT WHEN condición ;
END LOOP;
-- el control sigue aquí
...
END;
Ahora la versión 11g agrega un nivel intermedio: CONTINUE. Con esta sentencia, podemos cancelar la iteración del loop actual y pasar a la siguiente, sin abortar el loop. Puede usarse simplemente CONTINUE o CONTINUE WHEN. En este último caso podremos evitar el uso de la sentencia IF para controlar la condición de salida de la iteración.
 BEGIN
LOOP
...
CONTINUE WHEN condicion; --vuelve al comienzo del loop

-- si condicion=true, esta parte no se ejecuta
...
END LOOP;
END;
Los programadores conservadores se preguntarán para que necesitamos esta sentencia, despues de todo siempre nos hemos arreglado con la cláusula IF-THEN-ELSE de modo de avanzar a la siguiente iteración. Es cierto, no lo necesitamos para hacer cosas que antes no podíamos, pero sí para simplificar el código y facilitar la lectura.

Un ejemplo con CONTINUE

Hay un caso típico donde CONTINUE puede beneficiarnos en la claridad de código para el seguimiento de la lógica: cuando tenemos que hacer múltiples validaciones antes de procesar un registro.

Imaginemos nuestro loop actual (muy simplificado), en donde para cada registro hacemos una serie de validaciones antes de procesarlo. Si una validación falla, debemos pasar al siguiente registro. Para resolver esta lógica, debemos crear una estructura anidada de IFs, ya que debo evitar que se ejecute el resto del loop si una de las validaciones falla.
LOOP
EXIT WHEN condicion;
i:=i+1;

a := valida_1(T(i));
IF (a) THEN
b := valida_2(a);
IF (b) THEN
....
IF (c) THEN

.....
procesoRegistro(T(i));
.....
END IF;
ELSE
....
END IF;
END IF;

END LOOP;
Notemos como los múltiples IF anidados complican el seguimiento del flujo. Muchas veces nos hemos visto complicados al tener que agregar una nueva validación, ya que hay que agregar un nuevo IF al loop, y debo mantener consistentes los correspondientes END IF que deshacen el nivel de anidación.

Ahora veamos como puede verse mejorado con CONTINUE-WHEN:
LOOP
EXIT WHEN condicion;
i:=i+1;

a := valida_1(T(i));
CONTINUE WHEN NOT a;

b := valida_2(a);
CONTINUE WHEN NOT b;

....

procesoRegistro(T(i));

END LOOP;
La mejoría es evidente, conseguimos disminuir la anidación (a 1) y evitar el uso repetitivo de IF/END-IF, reduciendo la posibilidad de cometer errores de lógica al momento de modificar.

CONTINUE es un recurso que evidentemente solo puede utilizarse en Oracle 11g, sin embargo, siendo conscientes que nuestro código no va a compilar en versiones anteriores, es una práctica que contribuye a la legibilidad del código, con todos los beneficios que eso trae aparejado.

Ver también sobre 11g
11g y sus índices invisibles

miércoles, 26 de marzo de 2008

NATURAL JOIN antinatural

El NATURAL JOIN en Oracle tiene un bug. Las versiones 9i y 10g retornan extraños productos cartesianos cuando ejecutamos joins naturales con varias tablas a la vez.

El join natural
Un natural join toma las columnas de igual nombre entre dos tablas y las utiliza para realizar un join. ¿Cuál es el beneficio? No hay que nombrar las columnas en el join.
Si bien puede sonar fantástico el ahorrarnos de escribir las columnas en el JOIN, en Oracle el natural join produce resultados inesperados que veremos más adelante.

Un caso de prueba
Tengo tablas que describen clientes, órdenes, y libros. Deseo realizar una consulta joineandolas a todas.

SQL> desc customers
Name Type Nullable Default Comments
--------- ------------ -------- ------- --------
CUSTOMER# NUMBER(4)
LASTNAME VARCHAR2(10) Y
FIRSTNAME VARCHAR2(10) Y
ADDRESS VARCHAR2(20) Y



SQL> desc orders
Name Type Nullable Default Comments
--------- --------- -------- ------- --------
ORDER# NUMBER(4)
CUSTOMER# NUMBER(4) Y
ORDERDATE DATE Y



SQL> desc orderitems
Name Type Nullable Default Comments
-------- ------------ -------- ------- --------
ORDER# NUMBER(4)
ISBN VARCHAR2(10)
QUANTITY NUMBER(3) Y



SQL> desc books
Name Type Nullable Default Comments
------- ------------ -------- ------- --------
ISBN VARCHAR2(10)
TITLE VARCHAR2(30) Y
PUBDATE DATE Y
COST NUMBER(5,2) Y

A considerar:

  • Todas las tablas tienen primary key, foreign keys, índices y estadísticas.
  • El JOIN que deseo hacer puede verse mirando las cuatro tablas desde arriba hacia abajo.
  • Las columnas con igual color determinan las llaves del JOIN.
  • La consulta que quiero realizar es --> Cuáles son los libros que ordenó JAKE LUCAS?


Resultado con INNER JOIN

SQL> SELECT isbn, title
2 FROM ((customers INNER JOIN orders USING (customer#))
3 INNER JOIN orderitems USING (order#))
4 INNER JOIN books USING (isbn)
5 WHERE firstname='JAKE' AND lastname='LUCAS';


ISBN TITLE
---------- ------------------------------
2491748320 PAINLESS CHILD-REARING
9247381001 HOW TO MANAGE THE MANAGER
2491748320 PAINLESS CHILD-REARING


3 rows selected.

Es el resultado esperado. El cliente JAKE LUCAS ordenó los 3 libros que aparecen. ¿Qué sucede si ejecutamos la misma consulta pero con NATURAL JOIN?

Resultado con NATURAL JOIN

SQL> SELECT isbn, title
2 FROM customers NATURAL JOIN orders
3 NATURAL JOIN orderitems
4 NATURAL JOIN books
5 WHERE firstname='JAKE' AND lastname='LUCAS';


ISBN TITLE
---------- ------------------------------
1059831198 BODYBUILD IN 10 MINUTES A DAY
0401140733 REVENGE OF MICKEY
0401140733 REVENGE OF MICKEY
0401140733 REVENGE OF MICKEY
0401140733 REVENGE OF MICKEY
8843172113 DATABASE IMPLEMENTATION
8843172113 DATABASE IMPLEMENTATION
8843172113 DATABASE IMPLEMENTATION
3437212490 COOKING WITH MUSHROOMS
3437212490 COOKING WITH MUSHROOMS
3437212490 COOKING WITH MUSHROOMS
3957136468 HOLY GRAIL OF ORACLE
1915762492 HANDCRANKED COMPUTERS
...


64 rows selected.

Retorna 64 filas, algo inquietante. Se estará realizando un producto cartesiano? Veamos el plan de ejecución:

SQL> set autotrace traceonly explain


SQL> SELECT isbn, title
2 FROM customers NATURAL JOIN orders
3 NATURAL JOIN orderitems
4 NATURAL JOIN books
5 WHERE firstname='JAKE' AND lastname='LUCAS';



Execution Plan
----------------------------------------------------------
0 SELECT STATEMENT Optimizer=ALL_ROWS (Cost=9 Card=3 Bytes=204)
1 0 NESTED LOOPS (Cost=9 Card=3 Bytes=204)
2 1 MERGE JOIN (CARTESIAN) (Cost=9 Card=1 Bytes=57)
3 2 MERGE JOIN (Cost=6 Card=1 Bytes=22)
4 3 TABLE ACCESS (BY INDEX ROWID) OF 'CUSTOMERS' (TABLE) (Cost=2 Card=1 Bytes=18)
5 4 INDEX (FULL SCAN) OF 'SYS_C00138991' (INDEX (UNIQUE)) (Cost=1 Card=20)
6 3 SORT (JOIN) (Cost=4 Card=21 Bytes=84)
7 6 TABLE ACCESS (FULL) OF 'ORDERS' (TABLE) (Cost=3 Card=21 Bytes=84)
8 2 BUFFER (SORT) (Cost=6 Card=14 Bytes=490)
9 8 TABLE ACCESS (FULL) OF 'BOOKS' (TABLE) (Cost=3 Card=14 Bytes=490)
10 1 INDEX (RANGE SCAN) OF 'IX_BOOKS' (INDEX) (Cost=0 Card=2 Bytes=22)
Efectivamente, en la línea 2 del plan aparece un MERGE JOIN (CARTESIAN) lo cual nos indica que un producto cartesiano se llevó a cabo. Esto no debería suceder, ya que las columnas para vincular las tablas con NATURAL JOIN existen y fueron verificadas en la primera consulta, usando la cláusula USING del INNER JOIN.

Resultado con NATURAL JOIN variando las columnas del SELECT
Si bien lo anterior no era normal, es aún más desconcertante lo que obtenemos si variamos las columnas del select: ¡La cantidad de registros también varía!

SQL> SELECT #order
2 FROM customers NATURAL JOIN orders
3 NATURAL JOIN orderitems
4 NATURAL JOIN books
5 WHERE firstname='JAKE' AND lastname='LUCAS';



42 rows selected.


SQL> SELECT isbn
2 FROM customers NATURAL JOIN orders
3 NATURAL JOIN orderitems
4 NATURAL JOIN books
5 WHERE firstname='JAKE' AND lastname='LUCAS';



64 rows selected.


SQL> SELECT #customer
2 FROM customers NATURAL JOIN orders
3 NATURAL JOIN orderitems
4 NATURAL JOIN books
5 WHERE firstname='JAKE' AND lastname='LUCAS';



896 rows selected.


SQL> SELECT *
2 FROM customers NATURAL JOIN orders
3 NATURAL JOIN orderitems
4 NATURAL JOIN books
5 WHERE firstname='JAKE' AND lastname='LUCAS';


3 rows selected.

Unicamente cuando seleccionamos todas las columnas (*) obtenemos el resultado correcto. ¡Esto debería desalentar a cualquiera a usar NATURAL JOIN!

Bug reconocido
La verdad es, que este bug fue reconocido por Oracle en Metalink, el soporte oficial. Lo que se informa es, que este bug se cree que será eliminado a partir de la versión 11.2. Pero eso deja de ser un problema si eliminamos hoy el uso de natural join, definitivamente.

Nunca usar NATURAL JOIN
El NATURAL JOIN es una sentencia que está en Oracle desde la versión 9i en un esfuerzo por cumplir con el estándar ANSI SQL. Dentro del propio mundo del SQL, el NATURAL JOIN es uno accesorio inútil como pocos, del cual podemos prescindir totalmente.

Más allá de que no funcione correctamente en este RDBMS, imaginemos que si lo hace y que en Oracle 11g R2 tenemos este bug solucionado: el NATURAL JOIN funciona de maravillas.
Ahora, que sucede con mi procedimiento si el día de mañana agregan una columna de nombre quantity a mi tabla books?
Como orderitems ya tiene una columna quantity, se van a retornar resultados inesperados, ya que la tablas se van a combinar por isbn y quantity (lo cual no tiene sentido). Un cambio leve en las estructuras modifica el comportamiento de mi JOIN, y en este caso no se trata de un bug sino que sería bastante lógico que lo hiciera.

Usar NATURAL JOIN también nos quita claridad en nuestro código: cada vez que lo encontramos en una consulta compleja perdemos tiempo buscando en las estructuras cuáles son las columnas que coinciden.

Lo mejor en todos los casos es usar INNER JOIN o la notación original de Oracle por medio de comparadores en el where. Con ellas el programador explicita cuáles son las columnas que deben entrar en juego, sin dejar nada librado al azar.

En conclusión, usar NATURAL JOIN es totalmente desaconsejado en SQL, ya que nuestras consultas dependen del nombrado de columnas en las tablas relacionadas. Un cambio insignificante como agregar una columna a una tabla, podría hacer dejar de funcionar mi aplicación.

lunes, 24 de marzo de 2008

Compilación condicional en 9i

La compilación condicional permite indicarle al compilador los fragmentos de código que debe compilar en nuestro código PL/SQL, según las condiciones que el programador indique utilizando directivas especiales.
Ahora, si bien la compilación condicional fue presentada con Oracle 10g R2, es también posible contar con esta característica en 9i y 10g R1, gracias a la aplicación de patches sobre la base de datos.

En Oracle 10g y superiores
En Oracle 10g ya viene incorporada a partir de la versión 10.1.0.4 y activada por defecto, por lo tanto no debemos hacer nada para comenzar a utilizarla.

En Oracle 9i
Para contar con ella en 9i debemos primero tener instalado el patch 9.2.0.6 o superiores. Luego debemos setear el parámetro indocumentado _plsql_conditional_compilation=TRUE. Este parámetro es del sistema y no es dinámico, por lo que el DBA deberá incluirlo en el archivo de parámetros y reiniciar la instancia.

En Oracle 8i e inferiores
No es soportada.

Una aplicación de la compilación condicional bastante práctica y recomendable es la de hacer que nuestro código PL/SQL compile en cualquier versión, utilizando los features propios de cada release cuando es posible, todo en el mismo código. Esta buena práctica tiene dos objetivos: el primero es hacer que nuestro paquete sea reutilizable (por ejemplo un paquete de funciones de uso general), y el segundo que nuestro código no genere un impacto al momento de migrar de versión.

Un ejemplo de compilación condicional
En este ejemplo, vamos a realizar un procedimiento para hacer un spool de determinadas cuentas que cumplen con un patrón en su código.
La compilación condicional va a entrar en juego con la siguiente lógica:
-Si la base es 9i, ejecuta SQL con invocaciones a SUBSTR para verificar el patrón
-Si la base es 10g, ejecuta SQL con expresiones regulares ya que es más performante

Primero compilamos la especificación de nuestro paquete, que no tiene nada de especial:

CREATE OR REPLACE PACKAGE pack_cuentas
IS
PROCEDURE list_cuentas (p_max IN VARCHAR2);
END pack_cuentas;
/
Luego compilamos el body, donde está la diferencia:
CREATE OR REPLACE PACKAGE BODY pack_cuentas
IS
PROCEDURE spool_cuentas (p_max IN VARCHAR2)
IS
BEGIN
$IF DBMS_DB_VERSION.VER_LE_9
$THEN

FOR cue IN (SELECT cod_cuenta FROM cuentas
WHERE activo='Y' AND SUBSTR(cod_cuenta,1,2)='00'
AND SUBSTR(cod_cuenta,3,1) IN ('0','1','2','3')
AND SUBSTR(cod_cuenta,LENGTH(cod_cuenta),-1)='A')
LOOP
dbms_output.put_line(cue.cod_cuenta);
END LOOP;
$ELSE
DECLARE
v_pattern VARCHAR2(20) := '00[0-'||p_max||']-[0-9]+A';
BEGIN

FOR cue IN (SELECT cod_cuenta FROM cuentas
WHERE activo='Y' AND REGEXP_LIKE (cod_cuenta,v_pattern))
LOOP
dbms_output.put_line(cue.cod_cuenta);
END LOOP;

END;
$END
END spool_cuentas;
END pack_cuentas;
/

Las directivas especiales $IF $THEN $END hacen el truco. Observar que este mismo código se compila en Oracle 9i, donde la función REGEXP_LIKE retornaría un error de compilación, sin embargo el fragmento de código que no corresponde a la versión ni siquiera es analizado por el compilador.
Para poder diferenciar entre las versiones 9i, 9iR1, 9iR2, 10g, 10gR1 y 10gR2 contamos con el paquete DBMS_DB_VERSION, el cual incluye constantes para cada versión, como la que usé en el ejemplo. En el ejemplo usamos la constante ver_le_9 que implica 'menor o igual a 9i', pero también tenemos para indicar menor o igual a un release como por ejemplo ver_le_9_2.
CREATE OR REPLACE package dbms_db_version is
version constant pls_integer := 10; -- RDBMS version number
release constant pls_integer := 2; -- RDBMS release number
ver_le_9_1 constant boolean := FALSE;
ver_le_9_2 constant boolean := FALSE;
ver_le_9 constant boolean := FALSE;
ver_le_10_1 constant boolean := FALSE;
ver_le_10_2 constant boolean := TRUE;
ver_le_10 constant boolean := TRUE;
end dbms_db_version;
/
Ahora podremos arreglar nuestro viejo código para que sea más eficiente en las versiones siguientes y que no tengamos que hacer cambios al momento de migrar, en definitiva adelantarnos y minimizar el retrabajo en el futuro.

miércoles, 19 de marzo de 2008

Cómo verificar archivos y directorios en Shell

Estos son mis recursos favoritos cuando necesito comandos en Unix para verificar la existencia de carpetas, saber si existen archivos o si están vacíos. Acudo a ellos comúnmente en scripts de respaldo de bases de datos, antes de invocar a RMAN o EXP bajo Linux y después de que se realizan los mismos para verificar los resultados.
También recurro al tamaño en bytes de archivos o de todos los archivos de un directorio, especialmente para generar reportes o logs en nuestros scripts.

Verificar si una carpeta existe
  if [ -d archivo ]
then
# Codigo si existe
echo existe
else
# codigo si no existe
echo no existe
fi
Verificar si un archivo de salida no esta vacío
  if [ -s archivo ]
then
# Codigo si existe y no es vacio
...
else
# codigo si no existe o es vacio
...
fi
Verificar si un archivo es escribible
  if [ -w archivo ]
then
# Codigo si existe y es escribible
...
else
# codigo si no existe o no es escribible
...
fi
Verificar la integridad de un archivo comparando checksum
  check1=`md5sum archivo_origen | cut -d' ' -f1`

check2=`md5sum archivo_destino | cut -d' ' -f1`

if [ $check1 -eq $check2 ]
then
# Integridad del archivo correcta
...
else
# Error de checksum
...
fi
Obtener el tamaño de un archivo en bytes
  x=`du -m archivo | cut -f1`
Sumar la cantidad total de bytes en un directorio
  suma=0
for arch in *
do
if [ ! -d $arch ]
then
tamano=`du -b $arch | cut -f1`
let suma=$suma+$tamano
fi
done
echo $suma


Ver también
Comandos útiles para Unix Shell
Limpieza de archivos en Unix
Cómo obtener la fecha de ayer en Unix

lunes, 17 de marzo de 2008

Error ORA-942 while gathering statistics

Este error puede suceder cuando un usuario no DBA quiere setear AUTOTRACE en su sesión.

SQL> set autotrace traceonly statistics;
Error ORA-942 while gathering statistics
SP2-0611: Error enabling STATISTICS report


Si es un usuario con acceso limitado, entonces es de esperar que no tenga grants sobre las vistas que se necesitan para leer las estadísticas generadas por AUTOTRACE. Se requieren permisos explícitos de lectura sobre las vistas v$_session, v_$sesstat y v_$statname.

Para facilitar la asignación de estos grants, el administrador cuenta con un script para crear un rol especial y así poder extenderlo a los usuarios.

Script:
$ORACLE_HOME/sqlplus/admin/PLUSTRCE.SQL


Cómo hacer para que un usuario pueda usar AUTOTRACE

1. En el sistema operativo, posicionarse sobre el directorio con el script.
    lfer@linux>  cd $ORACLE_HOME/sqlplus/admin
2. Loguearse a SQL*plus con usuario 'AS SYSDBA'

3. Ejecutar en SQL*plus:
    SQL> @PLUSTRCE.SQL
4. Otorgar el rol PLUSTRACE a todos los usuarios que deseen usar AUTOTRACE:
    grant PLUSTRACE to USUARIO;
Ver también:
SQLPlus Users Guide and Reference 10g sobre AUTOTRACE

miércoles, 12 de marzo de 2008

Cómo detectar caracteres extraños o no imprimibles

Muchas veces pasa que en las cargas de datos aparecen caracteres de control como ^M o símbolos ilegibles, y no es deseable que estos queden almacenados entre los datos.
¿Cómo detectar si existen?
Es sencillo si contamos con expresiones regulares en SQL como en 10g, pero en 9i igualmente podemos crear una función que recorra cada uno de los caracteres y verifique si pertenece al juego de caracteres 'raros'.
Mostraré una sencilla implementación compatible con todas las versiones de Oracle, pero puede considerarse utilizar compilación condicional para utilizar expresiones regulares cuando sea posible.
El código:
CREATE OR REPLACE FUNCTION buscar_no_imprimible(v_cadena VARCHAR2) 
RETURN BOOLEAN IS
v_ret BOOLEAN := FALSE;
v_iter NUMBER := 1;
v_ascii_min NUMBER := 33;
v_ascii_max NUMBER := 126;
BEGIN
WHILE (v_iter <= LENGTH(v_cadena))
LOOP
IF ASCII(SUBSTR(v_cadena,v_iter,1))
NOT BETWEEN v_ascii_min AND v_ascii_max THEN
v_ret := TRUE;
EXIT;

END IF;
v_iter := v_iter + 1;
END LOOP;
RETURN(v_ret);
END buscar_no_imprimible;
Esta función booleana retorna TRUE si la cadena contiene un caracter no imprimible, FALSE en caso contrario. Una rápida mirada en una tabla ASCII nos dice que el rango de caracteres válidos se encuentra entre las representaciones decimales 33 y 126.

Si se desean considerar caracteres del ASCII extendido (por ejemplo vocales con acentos), bastará ajustar el procedimiento para incluir el conjunto de caracteres deseado.

Ver también:
Como eliminar acentos del español
Limpiando código DDL
Compilación condicional en 9i

lunes, 10 de marzo de 2008

11g y sus Indices Invisibles

Los índices invisibles, disponibles a partir de Oracle Database 11g, según la documentación oficial, son una interesante alternativa al borrado de índices o seteo como "unusable". Pero, son realmente una alternativa?

Según la información disponible, un índice invisible será ignorado por el optimizador al momento de evaluar los planes de ejecución, excepto que alteremos el valor de sesión (o sistema) OPTIMIZER_USE_INVISIBLE_INDEXES, pero en todo momento el estado del índice será válido. Esto nos brinda múltiples beneficios.

Un índice unusable es una forma sutil de dejar un índice "fuera de combate" temporalmente en TODAS las sesiones. Un índice invisible en cambio es selectivo: podemos verlo (y usarlo) en una sesión mientras en el resto hacer como si no existiera.

Un índice invisible puede volverse visible sin costo alguno, con una simple alteración del mismo: ALTER INDEX mi_indice VISIBLE. Un índice unusable en cambio deberá ser reconstruído, ya que las actualizaciones de datos sobre la tabla no fueron reflejadas mientras el índice estuvo en ese estado. Los índices invisibles SIEMPRE son mantenidos por Oracle con cada sentencia DML.

Con estas afirmaciones, podemos concluir que los índices invisibles llegan para sustituir el estado unusable?
-No! El propósito es completamente diferente. Los índices invisibles evitarán, por ejemplo, que tengamos que 'inutilizar' índices para poder realizar performance tunning, pero en ciertos casos particulares seguiremos necesitando del estado unusable.

Cuando tengamos que realizar una carga masiva de datos, tendremos que alterar el índice como 'unusable' para realizar la carga eficientemente, ya que la invisibilidad de índices de 11g tiene efecto sobre las consultas SELECT y no sobre las demás sentencias DML. El índice invisible continúa aportando overhead en cada modificación de datos.

La clave de este novedoso estado está en el testeo de consultas. Por ejemplo, podremos probar cómo se comporta un SELECT con determinado índice, sin que el ambiente en el cual lo estoy probando se entere. Creo un índice invisible, seteo el valor de OPTIMIZER_USE_INVISIBLE_INDEXES como true, y testeo mi aplicación sin efectos colaterales.

Otro beneficio, razonando en forma opuesta: ¿Cómo funcionaría mi aplicación si determinado índice no existiera?
Alteramos el índice como 'invisible', seteamos en nuestra sesión OPTIMIZER_USE_INVISIBLE_INDEXES = false, y probamos. Los demás usuarios mientras tanto, continuarán utilizándolo normalmente.

Los índices invisibles son un valor agregado a nuestra forma de trabajo, ya que nos proveen transparencia sobre el ambiente. Nos permiten realizar nuestras pruebas tranquilos de que nadie está siendo afectado.

Ver también:
Indices condicionales
Mis índices no funcionan!
Indices de función para mejorar un LIKE
Oracle Database Administrator's Guide 11g sobre índices invisibles

sábado, 8 de marzo de 2008

SOUNDEX en español

SOUNDEX, función que ha estado presente en Oracle desde tiempos remotos, es un clásico algoritmo fonético utilizado para indexar nombres propios, asociando palabras que suenen igual a pesar de ocasionales diferencias en su escritura.

El algoritmo SOUNDEX fue concebido teniendo en cuenta la fonética del idioma inglés, por lo que hace inadecuado su uso en el castellano. Realizando adaptaciones propias del idioma español, implementé una versión en PL/SQL llamada SOUNDESP, la cual respeta la esencia del algoritmo original.

Los pasos básicos son:

  1. Retener la primera letra de la cadena. Tener en cuenta las letras dobles como CH y LL.
  2. Remover todas las ocurrencias de las letras siguientes a partir de la segunda posición: a, e, i, o, u, h, w, y (cuando suena como vocal i )
  3. Asignar números a las siguientes letras (luego de la primera):
    • b, f, p, v = 1
    • c, g, j, k, q, s, x, z = 2
    • d, t = 3
    • l = 4
    • m, n = 5
    • r = 6
    • ll, y, ch = 7
  4. Si hay números consecutivos, dejar solamente uno en la serie.
  5. Retornar los cuatro primeros caracteres, si son menos de cuatro completar con ceros.

SOUNDESP es un proyecto abierto y es bienvenido cualquier comentario para mejorar su implementación.
Ultima versión: 1.8 (04-MAR-2010)


NOTA: Para descargar el código correctamente, haga click derecho y elija Guardar destino

Ver también:
Wikipedia: Soundex