Archive

Archive for 27 septiembre 2010

El problema con try-catch: stack unwinding y problemas de rendimiento

septiembre 27, 2010 3 comentarios

Todos conocemos las ventajas de usar bloques try-catch (o try-catch-finally, para los lenguajes que lo soportan). Pero en muchas ocasiones surgen preguntas sobre el rendimiento que este sistema puede tener sobre nuestra aplicación. Y estas dudas puede que nos hagan recular al momento de encerrar un bloque de código potencialmente peligroso en un bloque try-catch. En otras palabras, ¿los bloques try-catch nos alentan la aplicación? ¿Debo, por ende, colocar estos bloques en todos lados? ¿Qué hago cuando atrapo una excepción?

Hay que comenzar a hacer este análisis desde la base misma: las excepciones. En primera instancia, hay que entender que, a nivel conceptual, las excepciones son eso: casos excepcionales. Situaciones que no deberían presentarse en un escenario normal. Esto, por supuesto, implica que uno tiene que hacer las validaciones en código correspondientes para evitar que las excepciones ocurran. Sin embargo, en algún momento de la vida van a ocurrir: se acabó la memoria de tu máquina, o se cayó la red a la mitad de una transacción de base de datos, etc. Por lo tanto, una solución robusta debe considerarlas como posibilidad. Y en consecuencia, deben ser atrapadas y manejadas convenientemente, de tal suerte que poner bloques try-catch en todos lados donde pueda surgir una excepción parece ser el camino correcto. Pero esto tiene sus matices, como veremos a en un momento.

Esto, sin embargo, puede crear preocupación sobre el hecho de que poner muchos manejadores ocasione una pérdida de rendimiento en la aplicación. Esto, afortunadamente, no es de alarmar. El poner un try-catch no supone problema alguno de sobrecarga ni rendimiento. Se pueden anidar tantos bloques como se requieran, y la pérdida de  memoria y rendimiento sería mínimo, casi nulo. El problema es cuando ocurre una excepción: ahí sí se tiene problemas de rendimiento. Me explico.

En lenguajes estructurados, procedimentales, orientados a objetos y prácticamente cualquiera que no sea ‘espagueti’ se cuenta con funciones que mandan llamar otras funciones. Cuando una función es llamada por otra, el motor de ejecución/máquina virtual/sistema operativo/lo que sea (dependiendo del lenguaje y plataforma) tiene que ubicar memoria suficiente para la función misma (una dirección en memoria), sus parámetros, las variables locales y finalmente, una variable que guarde el valor deretorno (siempre que éste no sea void -o en el caso de VB, que no sea una subrutina en lugar de una función). Al terminar la función, bien porque se llega al final o bien porque alcanza la cláusula return, la memoria asignada se destruye, liberándola. Incluso, en el caso de C++, se llaman a los destructores de aquellas variables locales que hayan sido creadas. Luego entonces, cuando llamas funciones anidadas, tienes que bloques de memoria se van asignado sobre sí: a esto se le llama pila de memoria –el último bloque en ser asignado será el primero en desasignarse. Este diseño hace que el manejo de memoria sea eficiente: no hay que mantener una tabla de referencias de las ubicaciones de memoria ni nada. Simplemente, cuando llega el momento de desasignar, de va por el último bloque de memoria y listo. A todo dar.

El problema es que la definición de excepción, o mejor dicho, de lanzamiento de una excepción, le da al traste a esto. Consideremos estos escenarios (para C++ y C#, respectivamente).

// C++

void boo() {
  try {
     foo();
  } catch (std::exception& e) {
    std::cout << e.what() << endl;
  }
}
 
void foo() {
   goo();
}
 
void goo() {
   hoo();
}
 
void hoo() {
  throw std::logic_error("Hoo!")
}
// C#

void boo() {
  try {
     foo();
  } catch (Exception e) {
    Console.WriteLine(e.Message)
  }
}
 
void foo() {
   goo();
}
 
void goo() {
   hoo();
}
 
void hoo() {
  throw new Exception("Hoo!")
}

Esto es lo que ocurre con este pedazo de código.

  1. Cuando boo se manda llamar, se crea el bloque de memoria para boo.
  2. Boo manda llamar a foo y se crea el bloque de memoria para foo.
  3. Foo manda llamar a goo y se crea el bloque de memoria para goo.
  4. Goo manda llamar a hoo y se crea el bloque de memoria para hoo.
  5. Hoo lanza una excepción, regresa a goo el control en busca de una cláusula catch.
  6. Goo regresa a foo el control en busca de una cláusula catch.
  7. Foo regresa a boo el control en busca de una cláusula catch.
  8. Boo captura la excepción pero no regresa el control, y termina la ejecución de su método en consecuencia.

Como se puede apreciar, el meollo del asunto es que se tiene que revisar la pila de llamadas para ver quién es el afortunado que tiene un bloque catch, y así regresarle el control. Esto equivale a quitar la memoria mientras se busca por el catch, en forma secuencial. Además, como no se conoce qué catch capturará la excepción, no hay mucho espacio para la optimización. A todo este relajo se le conoce como “Stack Unwinding” (ver [1]). Ahora, imaginemos este escenario modificado:

// C++

void boo() {
  try {
    foo();
  } catch (std::exception& e) {
    std::cout << e.what() << endl;
  }
}
 
void foo() {
  try {
    goo();
  } catch (std::exception& e) {
    throw logic_error("Error en goo")
  }
}
 
void goo() {
  try {
    hoo();
  } catch (std::exception& e) {
    throw logic_error("Error en hoo")
  }
}
 
void hoo() {
  throw std::logic_error("A hoo le dio patatús")
}

// C#

void boo() {
  try {
    foo();
  } catch (Exception e) {
    Console.WriteLine(e.Message);
  }
}
 
void foo() {
  try {
    goo();
  } catch (Exception e) {
    throw new Exception("Error en goo", e)
  }
}
 
void goo() {
  try {
    hoo();
  } catch (Exception e) {
    throw new Exception("Error en hoo", e)
  }
}
 
void hoo() {
  throw new Exception("A hoo le dio patatús")
}

En estos ejemplos, podemos apreciar que ocurren tres stack unwindings: hoo para goo, goo para foo, y foo para boo). Se puede ver fácilmente que tener muchos try-catch anidados que lancen excepciones suponen un costo grande de rendimiento, causado por el stack unwinding. Pero esto es así sólo cuando se lanza la excepción y por ende, se provoca el stack unwinding.

Ahora bien, dado que se supone que las excepciones son casos excepcionales, es preferible tener esa pérdida de rendimiento que solo ocurrirá en raras ocasiones, a que el programa truene miserablemente como una patata demasiado cocida. Pero si uno abusa de los try-catch, puede uno terminar con el agua hasta al coronilla: problemas serios de rendimiento.

Para contrarrestar estos efectos sin perder la fortaleza de un try-catch, Microsoft ha recomendado una serie de medidas destinadas a mejorar la práctica del manejo de excepciones (sobre todo, enfocado a su plataforma .NET).

Adicionalmente a éstas, mi modesta experiencia me hace considerar un conjunto de reglas a seguir, sencillas todas, a través de la cual podemos mejorar y robustecer nuestra aplicación. Estas reglas las expongo a continuación.

1. Nunca utilices excepciones para controlar el flujo. Úsalas solo para indicar casos excepcionales que no deberían ocurrir de forma normal. Prefiere hacer validaciones (i.e. prueba si la variable es nula, si el índice del array está dentro de rango, si las precondiciones para un objeto predeterminado se cumplen, etc.) y controla el flujo utilizando if, else, while, return… incluso el goto es preferible.

2. Cuando escribas una propiedad o método, realiza validación de las precondiciones (i. e. que los parámetros no sean nulos, que el estado interno de la clase sea válido, que el objeto no haya sido marcado como "disposable", etc.) y en dado caso que no se cumplan, lanza una excepción adecuada (ArgumentNullException, InvalidOperationException y ObjectDisposedException, respectivamente en C#; o std::invalid_argument, std::logic_error o std::runtime_error respectivamente en C++). Si una de estas excepciones se lanza en algún momento de la vida, tienes un bug (ya que por eso son precondiciones) y tienes que arreglar tu código.

3. Atrapa las excepciones sólo donde lo necesites. Si una función tiene que reaccionar ante una excepción (como cerrar un archivo, abortar una transacción, cancelar una sesión, etc.) entonces usa el catch. Si solo necesitas asegurarte que ciertas tareas se cumplan, no pongas catch y pon nada más el finally (en el caso de C#, C++ no lo soporta). Si a tu función le da igual, en el sentido de que no puede arreglar el desperfecto, no pongas un catch y déjala burbujear hasta que haya una función a la que sí le importe.

4. Coloca manejadores de errores genéricos. Si al final no hubo función alguna que pudiera tratar el error, entonces si no lo controlas tu aplicación tronará miserablemente. Y no hay nada menos profesional que el mensajito de "una excepción no controlada ha sucedido, el programa abortará su ejecución". Si estás con aplicación Windows, pon el try catch en las rutinas de alto nivel (digamos, en los manejadores de los eventos, que son los que suelen iniciar las acciones, o en los manejadores de mensajes, si estás con C++). Si estás con aplicación ASP.NET, asegúrate de contar con una página de errores personalizada y redirigir los errores a ésta a través del web.config (o sobre-escribiendo el Page.OnError). Pon un mensaje de error amigable al usuario, y guarda en algún log la información de la excepción, incluido el stack trace: puede salvarte horas al momento de hacer una depuración.

5. Relanza excepciones solo cuando sea estrictamente necesario. Si un método necesita reaccionar ante una excepción, pero no puede manejarla (i. e. no puede restaurar el estado del sistema por sí misma) pon el catch correspondiente, y después de tu código, relanza la excepción:

// C++
void foo()
{
  try {
    goo();
  } catch (std::exception& e) {
    // hacer algo con excepción
    throw;
  }
}
// C#
void foo()
{
  try {
    goo();
  } catch (Exception e) {
    // hacer algo con excepción
    throw e;
  }
}

En estos casos, causarás otro stack unwinding, pero ni modo, no queda de otra. Pero si no lo necesitas, por favor no lo pongas, o te puedes ver metido en líos de rendimiento, por lo ya explicado.

6. Nunca de los nuncas atrapes una excepción y la dejes sin atender. Es decir, nunca hagas esto:

// C++
void foo() 
{
  try {
    goo(); 
  } catch (std::exception& e) { }
}
// C#
void foo() 
{
  try {
    goo(); 
  } catch (Exception e) { }
}

Cada vez que alguien escribe código como ese un gatito muere. En verdad. Es como si al barrer guardaras el polvo debajo de la alfombra. No te deshaces del problema, solo lo ocultas. Nada más que a veces lo que uno guarda sin darse cuenta puede que no sea polvito, sino el contenido de uno de los canales de aguas negras de Chalco, llenas de pestilencia, enfermedades, gusanos y podredumbre. En serio. Por lo menos guarda el error en el Event Viewer o en un log. Creo que incluso es preferible que truene la aplicación a que no te enteres que hay algo mal.

Bueno, pues espero no haber causado más confusión. Todo lo anterior lo podemos resumir como sigue.

Concuerdo con que muchos try-catch puedan ser señal de falta de validación, pero mientras no lancen excepciones a cada rato y mientras no causen stack unwindings, no deberías tener problemas. Aunque personalmente, pienso que es mejor dejarlas burbujear siempre que sea posible, dado que no hacerlo implica que o bien estás generando muchos stack unwinds (porque si no puedes manejar el error, lo estás relanzando) o bien atrapas la excepción pero no haces nada al respecto (con lo cuál nomás te haces güey metiendo polvo debajo de la alfombra), y ambas acciones son causantes de que recibas un duro y sonoro sape.

Bueno, hasta aquí llega mi perorata. Te sugiero que leas más sobre excepciones, es un tema interesante al que se le puede sacar provecho. Busca en MSDN y en CodeProject, hay muy buenas fuentes.

Sillón leiter.

Categorías:Apunte, C#, C++ Etiquetas: , ,

101 Ejemplos de LINQ en MSDN


Pues el día de hoy me topé… no no no, mejor dicho… el día de hoy JTorrecilla, en los foros de MSDN, respondió una pregunta sobre ejemplos en LINQ y puso un enlace muy bueno a MSDN con muchos ejemplos sobre LINQ. Me parece justo reproducirlo aquí, por si a alguien le sirve.

MSDN – 101 LINQ Samples

Das ist alles für den Moment.

Categorías:.NET Framework, C#, Noticias Etiquetas:

El misterio de las extensiones de los archivos

septiembre 22, 2010 3 comentarios

Hoy por la mañana un tío en los foros de MSDN hizo una pregunta relacionada con las extensiones de los archivos. En esencia, lo que se preguntaba era que cómo era posible que una aplicación entendiera archivos con extensión totalmente diferente a la original, y que si se podía explicar cómo se codificaban las extensiones de los archivos.

Bien, dado que es un concepto que a veces cuesta trabajo entender (creo que por la naturaleza misma del concepto de extensión) aprovecho para hacer un pequeño apunte al respecto.

Por supuesto, todos conocemos lo que es una extensión de un archivo. Es decir, los documentos de Microsoft Word tienen una extensión .doc, los de Excel .xls, los de texto son .txt y el formato universal para música es .mp3. Pero ¿qué quiere decir esto de extensión? La respuesta no es muy complicada: una extensión no es otra cosa que parte del nombre del archivo. O mejor dicho, forma parte del nombre y son usadas para clasificar a los archivos, de la misma forma en la que nuestros apellidos son nombres para identificar la familia a la que pertenecemos. Pero la extensión no afecta las características de un archivo, así como el nombre no afecta mi ser: yo soy un humano independientemente de si me apellido Gómez, Pérez o Teal’c.

¿Para qué utilizar extensiones entonces? Bueno, el sistema operativo puede hacer búsquedas, filtros, ordenamientos y muchas otras cosas con esta información adicional, de la misma forma en la que una persona en el registro público puede encontrar nuestra información de forma más fácil si le damos nuestro apellido. Pero la extensión de un archivo, repito, no afecta el contenido del mismo.

En esencia, todos los archivos se guardan en un solo formato: binario. Todos, absolutamente todos. Es más, todo lo que está en nuestra máquina se encuentra en binario. En realidad, cuando uno dice que guarda un archivo en formato de texto, de XML, de Word, de PDF, etc., lo que quiere decir es que el arreglo de los bytes está hecho de tal forma que se puede representar como texto, como un XML, o que puede ser interpretado por programas que entiendan el ordenamiento de bytes establecido por Word o Acrobat. Incluso los ejecutables o las librerías de enlace dinámico son binarios, aunque estos son interpretados de forma especial por el sistema operativo.

Luego entonces, la capacidad de un programa de "leer" un tipo de archivo se traduce a si el programa puede o no entender e interpretar el orden de bytes que contiene un determinado archivo. Tomemos como ejemplo al Notepad. La finalidad de este programa consiste en leer y escribir texto simple. La tabla de caracteres ASCII (o UTF, Unicode, etc.) establece una correspondencia entre un byte y un caracter. Por ejemplo, el byte con valor 65 corresponde a la letra latina mayúscula A, mientras que la letra latina minúscula j se corresponde con un byte con valor 106. Y así sucesivamente, para cada caracter. Por ende, el Notepad lo que hace es interpretar estos números y dibujar en pantalla los píxeles correspondientes a cada caracter.

En cambio, un archivo de Word no solamente guarda texto, sino que guarda mucha más información: fuentes, colores, posición de las páginas, encabezados, imágenes, errores ortográficos y un larguísimo etcétera. Si abres un archivo Word con el Notepad verás la interpretación en texto simple del contenido binario. En algunos casos, reconocerás palabras (que se corresponde con el texto que guarda Word, eventualmente), aunque la mayor parte de las veces serán símbolos raros, correspondientes al equivalente en la tabla ASCII de los números que cada byte representa.

En fin, el punto es que la extensión de archivo no hace nada, y de hecho estas son arbitrarias. Normalmente, como programadores escogemos extensiones estándares e intuitivas (como .xml para documentos XML, .cpp y .cs para archivos de código C++ y C#, o incluso .dbf para un archivo que contenga una base de datos), o bien los escogemos por su contenido (por ejemplo, a veces se usa la extensión .dat para indicar un archivo que guarda datos de algún tipo, o .config para indicar que el archivo en cuestión es uno que guarda datos de configuración), o incluso se escogen en base al nombre del programa (por ejemplo, un .psd indica un archivo de imagen de Photoshop, o un .ppt indica un archivo de Power Point). Pero nuevamente, estos son arbitrarios.

Como prueba, hagamos este ejercicio. Abre Microsoft Excel, crea una hoja y mete algunos datos. Luego, guarda el archivo y cierra el programa. Una vez hecho esto, ve y cambia la extensión al archivo, digamos, de prueba.xls a prueba.fer. Abre Excel nuevamente, dale al menú Abrir y navega al directorio donde radica el archivo. Notarás que éste no aparece: esto es así porque Excel filtra los archivos mostrados a aquellos que tengan extensión xls o xlsx (un ejemplo de cómo las extensiones pueden hacer la vida más fácil). En fin, selecciona la opción de “mostrar todos los archivos” y ahora sí, selecciona a prueba.fer. Cuando Excel te pregunte que si deseas abrir el archivo, dile que sí, y voilá: sin problemas mostrará el archivo en cuestión. Esto es así porque solo cambió el nombre del archivo, mas no su contenido. Igual pasa con las extensiones de cualquier otro archivo: al final lo que importa es la interpretación que se le dé al contenido.

Ahora bien, desde el punto de vista de C# y .NET, abrir un archivo cualquiera significa utilizar un objeto de tipo FileInfo y mediante este, obtener un objeto de tipo FileStream (ambos declarados en System.IO). Al FileStream, como a cualquier otro flujo, le podemos leer o escribir bytes (obviamente dependiendo de si abrimos el archivo en modo lectura o escritura). Ahora que podemos usar algunas clases como BinaryReader y BinaryWriter para que nos ayuden a escribir el binario, o bien StreamReader y StreamWriter para que nos ayuden a escribir texto (en este caso, ambas clases se encargan de interpretar el binario como texto). Análogamente tenemos a XmlTextReader y XmlTextWriter. Y así sucesivamente. Bueno, ya en otra ocasión hice una entrada sobre los flujos de datos, ahí hay más información al respecto. Pero el punto es el mismo: lo que se lee y escribe es binario, puros bytes, solo que contamos con algunas clases que nos facilitan la lectura y escritura.

Bueno, pero… ¿qué onda con el formato? Ah bueno. El formato es, digamos, un acuerdo en cómo interpretar los bytes. En el caso de los archivos de texto, la interpretación se hace en base a la tabla ASCII / UTF / Unicode que corresponda. En el caso de un XML, no es otra cosa que un archivo de texto plano que tiene ciertas reglas para crear y acomodar marcas. Por otra parte, un mapa de bits de 32 bits guarda un byte para el color rojo, uno para el verde y uno para el color azul, más otro para la transparencia, aparte de guardar un encabezado con información adicional. Algunos formatos son estándares, como el texto o el XML, algunos son propietarios pero públicos como el JPEG o el GIF, y algunos otros son propietarios y privados, como el PSD o el DOC. De cualquier forma, es necesario conocer el formato de antemano, para leer un archivo. Por ejemplo, en el caso de los mapas de bits, necesitamos leer los primeros 54 bytes del archivo para tener información sobre la profundidad del color, el número de filas, etc.

Por supuesto, tú también puedes crear tus propios formatos. A este proceso se le llama serialización y la mayoría de los lenguajes y plataformas soporta la serialización. En .NET, por ejemplo, basta agregar el atributo [Serializable] a la clase y solito crea el formato necesario para leer y escribir un objeto de ese tipo. Adicionalmente, puedes implementar la interfaz ISerializable y tú mismo controlar qué se serializa y qué no.

En fin, bueno, eso ha sido todo. Espero haber sido claro y no haberte dejado más liado que cuando llegaste. Mi meta es quitar ese como velo de misterio que hay tras las extensiones, y espero haberlo logrado: de esta forma uno puede entender mejor los archivos y como crearlos. Ahí me dices si sí cumplí mi objetivo.

Arrivederci bambini!

Categorías:Apunte Etiquetas: ,

Realizar búsquedas de palabras fonéticamente similares usando Soundex

septiembre 22, 2010 6 comentarios

Realizar búsquedas es una cosa complicada, y un buen motor de búsquedas requiere redes neuronales, minería de datos y seguramente inteligencia artificial. Dicho eso, uno puede realizar consultas más sencillas sobre una base de datos.

Una de ellas, por supuesto, consiste en utilizar las clásicas consultas SQL usando el operador de igualdad, el operador LIKE, el operador BETWEEN, y otros por el estilo. Por ejemplo, supongamos que tengo una tabla Empleado que contiene tres campos: Nombre, Apellidos y Dirección. Si quiero hacer una búsqueda sobre Nombre, normalmente haría algo así:

select * from Empleado where Nombre like '%Fernando%'

Realizar esta consulta me regresaría todos los empleados cuyo nombre contenga a “Fernando”, por ejemplo, “Fernando Arturo” y “Juan Fernando”. Pero esta consulta oculta muchos problemas. Por ejemplo, supongamos que en lugar de “Fernando”, quiero buscar “Andrés”:

select * from Empleado where Nombre like '%Andrés%'

Por supuesto, esta consulta me regresa todos los de nombre “Andrés”… pero no me regresa los de nombre “Andres”, es decir, sin acento. Peor aún, por ejemplo, si buscamos a alguna “Jimena”, ya que puede haber registros con Gimena y Ximena. Y cuando desarrollamos aplicaciones empresariales, esto es un problema, porque los usuarios suponen que esto no será un impedimento. Y obvio, no querrán modificar toda su base de datos para revisar cuestiones gramaticales.

Siendo este el contexto, me gustaría comentar sobre un algoritmo de búsqueda por sonidos que SQL Server soporta, que ayuda a solucionar lo anterior. Pero para ello, necesitamos una breve historia…

[me reclino en la mecedora mientras enciendo un cigarrillo y agito la copa de coñac]

» Hace mucho tiempo cuando no había bases de datos ni computadoras ni electrónica, en la era oscura, cuando Thor buscaba a Jörmungandr y se podía ver a Freyja, vestida de blanco, pasear por los bosques de Midgard… ejem… bueno… quiero decir, que antes de las computadoras, los bibliotecarios, gente del registro civil y en general cualquier persona que tuviera que ordenar datos y realizar búsquedas se las veían negras para localizar información específica. En principio, un ciudadano iba a buscar, digamos, el acta de nacimiento de su padre, John. Sin embargo, John puede escribirse como John, Jon, Jhon, etcétera, por lo que a veces estas diferencias causaban que el dato en cuestión no se encontrara. Imagínate en idiomas como el español: realizar búsquedas incluye buscar con acentos, sin acentos, con H o si H, con Y o doble L… y un larguísimo etcétera.

» En aquellos días oscuros parecía que no habría forma de obtener la información que uno buscaba… parecía que el Ragnarök estaba cerca… Entonces a alguna mente en los Estados Unidos a inicios de siglo XX se le ocurrió diseñar un algoritmo que basara la búsqueda en similitudes fonéticas en lugar de alfabéticas… y dio como resultado un algoritmo para codificar nombres con la misma pronunciación, llamado Soundex. Este algoritmo se basa en codificar letras de sonidos similares, ignorar otras y al final reducir una palabra a cuatro letras. Dos palabras con sonidos iguales (como John y Jon) regresarán una codificación igual, mientras que si son semejantes (digamos, Fernando y Hernando) regresarán una codificación parecida(usualmente, coincidirán tres de las cuatro letras) pero no iguales.

» Soundex probó ser útil y permitió a la oficina de censos de EE.UU. realizar análisis precisos de los censos hechos entre 1890 y 1920. Probó ser tan útil que sobrevivió a la era oscura y llego a la era del Yggdrasil: ha sido incorporada en los motores de bases de datos modernos. De tal suerte que puedes utilizar el algoritmo Soundex en SQL Server para realizar una búsqueda por sonidos.

[abro los ojos, dejo la colilla del cigarrillo y vacío de un trago el resto del coñac]

Bueno, el punto es que SQL Server tiene una función llamada SOUNDEX que toma una cadena de texto y la codifica usando el algoritmo Soundex. Por ejemplo:

select soundex('casa'), soundex('caza'), soundex('cahsa')
go

regresa el siguiente resultado:

C200 C200 C200

Como casa, caza y cahsa se pronuncian igual, pues se regresa el mismo código. De igual forma:

select soundex('Fernando'), soundex('Hernando')
go

regresa:

F655 H655

Como puedes ver, los códigos son muy similares, pero no exactos. Así, tres de las cuatro letras coinciden, por lo que la aproximación debe ser buena, pero no exacta. Entonces, podemos ver que cuando comparamos dos términos, lo que nos interesa es la diferencia que éstos regresan, en un rango del 1 al 4 (1 si prácticamente no se parecen, 4 si se parecen demasiado). Afortunadamente, SQL Server provee una función llamada DIFFERENCE, que internamente hace uso de SOUNDEX y regresa un número del 1 al 4: en esencia, qué tanto se parecen los términos buscados. En efecto:

select difference('casa', 'caza')

regresa, sin sorpresas, un 4, mientras que

select difference('fernando', 'hernando')

regresa un 3. Así, una forma de realizar búsquedas pudiera ser haciendo uso de DIFFERENCE. Por ejemplo:

select nombre, apellidos, direccion
from empleado
where difference(nomrbe, 'fernando') > 2

Esta consulta te regresaría todos los “fernandos” de la tabla, pero también todos los “hernandos” y los “fehrnandos”.

Nota, sin embargo, que SOUNDEX y DIFFERENCE tienen sus puntos débiles. Después de todo, incluso Thor murió envenenado tras matar a Jörmungandr. Este algoritmo está muy bien para buscar nombres y palabras, pero no para buscar frases. Un soundex entre “En algún lugar de un gran país” y “país” te va a regresar códigos diferentes. De hecho, select difference(‘En algún lugar de un gran país’, ‘país’) te regresa apenas un 2. Así, mi recomendación sería utilizar este tipo de búsquedas sobre campos que contengan un solo nombre: por ejemplo, nombres de personas, apellidos, ciudades, países, etc.

Ah, por cierto. No olvides que el tema del idioma es muy importante, ya que por el asunto de la pronunciación, soundex se comporta de forma diferente en inglés que en español (por ejemplo, en inglés Y y J se pronuncian igual, mientras que en español se pronuncian diferente; análogamente, en español LL y Y son iguales mientras que en inglés son diferentes). Así que asegúrate que el idioma de tu base de datos sea el correcto, si vas a utilizar este algoritmo.

En fin, dicho lo anterior… la idea de este post es darte una alternativa más que puedas utilizar en tu desarrollo. Espero te sirva de algo. Ahí me cuentas cómo te fue.

Categorías:Apunte, SQL, SQL Server Etiquetas:

Error al restaurar base de datos: la clave del Registro BackupDirectory no está configurada correctamente.


El día de hoy llegué a la oficina a las nueve de la mañana. Desvelado, obviamente, ya que ayer hubo una sesión de Stargate SG-1, temporada cinco, aunque solo vi un par de capítulos mientras configuraba mi nueva máquina. Bueno, el chiste es que llegué desvelado, tuve que pasar por mi café y mi Pepsi Kick, y ya acá revisé el plan de actividades que tengo para el proyecto nuevo en el que estoy metido: la actualización tecnología del sitio GeoSEP de la Secretaría de Educación Pública. Y uno de los pendientes que tengo es el restaurar la antigua base de datos (es decir, la versión anterior) en el ambiente de pruebas.

Entonces, tras consumir mi Pepsi Kick y menear un poco la cabeza (al ritmo de un pegajoso “tan-tarán-tara-rararán”), tomé la base de datos del servidor de producción e hice una copia de seguridad. Pasé el archivo a mi máquina, abrí mi SQL Server Manager Studio y me conecté a la instancia local. Cree una nueva base de datos, vacía del todo, e intenté restaurar la base de datos con la que saqué de producción, sobreescribiendo cualquier configuración. Para mi sorpresa, me topé con el siguiente mensaje de error:

[3047] La clave del Registro BackupDirectory no está configurada correctamente. Esta clave debe especificar la ruta raíz en la que se almacenan los archivos de copia de seguridad en el disco cuando no se proporcionan nombres de ruta. Esta ruta también se utiliza para buscar archivos de reinicio de punto de comprobación para RESTORE.

Esto me sorprendió un poco, por lo que comencé a investigar. Al principio, pensé que era una cuestión de permisos, así que intenté hacer nuevos respaldos. Empero, el mensaje de error seguía mencionando a una llave del registro, por lo que me di cuenta que era más una cuestión interna. En efecto, el mensaje dice que es una llave del registro de Windows y que su valor se utiliza para copiar archivos durante una copia de seguridad. Así que su valor debería ser un directorio donde existieran permisos de escritura.

Solo quedaba un problema: buscar dónde demonios se encuentra la mentada llave. En primer lugar, HKEY_LOCAL_MACHINE\Software\Microsoft\Microsoft SQL Server\ es donde se encuentran todas las llaves de configuración general de SQL Server. Comencé a buscar en todas las subcarpetas y en ningún lugar encontré la mentada llave BackupDirectory. Vamos, hice una búsqueda completa en todo el registro y nada. Y sí, finalmente, después de buscar y algunos errores, di con el directorio: HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Microsoft SQL Server\MSSQL.1\MSSQLServer. Lo que hice fue crear la llave de tipo alfanumérico (REG_SZ) y ponerle de valor inicial un directorio cualquiera con permisos de escritura. Y lixto, eso solucionó el problema.

A ciencia cierta, no sé por qué mi registro no tenía esa llave. He de decir que la máquina con la que trabajo fue utilizara por alguien anteriormente, y no fue formateada, por lo que está llena de cosas. Quién sabe, en una de esas algo se echó a perder.

Pero bueno, esperemos que si te pasa este error, tengas la fortuna de cruzarte con este post y te sea más leve de lo que me fue a mí.

Auf wiedersehen!