jueves, 18 de febrero de 2016

Trabajar con Tipos de Datos y Funciones Integradas

Cuando definimos columnas en tablas, parámetros en procedimientos y funciones, y variables en lotes T-SQL, necesita elegirles un tipo de datos. El tipo de dato restringe los datos que son soportados, además de encapsular el comportamiento que opera sobre los datos, exponiéndolos a través de operadores y de otros medios. Debido a que los tipos de datos son un componente fundamental de sus datos, todo es construido por encima, sus opciones de tipos de datos tendrán implicaciones dramáticas para su aplicación en muchas capas diferentes. Por lo tanto, esta es un área que no debe tomarse a la ligera, sino que debe tratarse con mucho cuidado y atención. Su conocimiento de los tipos es fundamental tanto para la definición de datos, como para la manipulación de datos.
T-SQL soporta muchas funciones integradas que puede utilizar para manipular los datos. Porque las funciones operan sobre valores de entrada y retornan valores de salida, un entendimiento de las funciones integradas va de la mano con una comprensión de los tipos de datos.
Note que este post no pretende cubrir exhaustivamente todos los tipos y todas las funciones que T-SQL soporta. En lugar de ello, este post explica los factores que necesita considerar cuando elige un tipo de datos, y los aspectos clave del trabajo con funciones, por lo general en el contexto de ciertos tipos de datos, como los datos date y time o datos character.
 

Escoger el Tipo de Dato Apropiado

Escoger los tipos de datos apropiados para sus atributos es probablemente una de las decisiones más importantes que tomará con respecto a sus datos. SQL Server soporta muchos tipos de datos de diferentes categorías: numéricos exactos (INT, NUMERIC), cadena de caracteres (CHAR, VARCHAR), cadenas de caracteres Unicode (NCHAR, NVARCHAR), numéricos aproximados (FLOAT, REAL), cadenas binarias (BINARY, VARBINARY), fecha y tiempo (DATE, TIME, DATETIME2, SMALLDATETIME, DATETIMEOFFSET, DATETIME), y otros. Hay muchas opciones, por lo que podría parecer una tarea difícil, pero siempre y cuando siga ciertos principios, puede ser inteligente acerca de sus elecciones, lo que resulta en una base de datos robusta, coherente, y eficiente.
Una de las grandes fortalezas del modelo relacional es la importancia que le da a la aplicación de la integridad de datos como parte del modelo mismo, en múltiples niveles. Un aspecto importante al escoger el tipo apropiado para sus datos es recordar que un tipo es una restricción. Esto significa que tiene un cierto dominio de valores soportados y no permitirá valores fuera de ese dominio. Por ejemplo, el tipo DATE permite sólo fechas válidas. Un intento de introducir algo que no es una fecha, como 'abc' o '20120230', es rechazado. Si tiene un atributo que se supone representa una fecha, como por ejemplo la fecha de nacimiento, y se utiliza un tipo como INT o CHAR, no nos beneficiaremos de la validación integrada de fechas. Un tipo INT no impedirá un valor como 99999999 y un tipo CHAR no impedirá un valor como '20120230'.
Así como un tipo es una restricción, el NOT NULL es una restricción también. Si un atributo no se supone que permita NULLs, es importante hacer cumplir una restricción NOT NULL como parte de su definición. De lo contrario, los NULLs encontraran su camino en su atributo.
También, quiere asegurarse de que no confundirá el formato de un valor con su tipo. A veces, la gente utiliza las cadenas de caracteres para almacenar fechas porque piensan en almacenar una fecha en un formato determinado. El formato de un valor se supone que es responsabilidad de la aplicación cuando los datos son presentados. El tipo es una propiedad del valor almacenado en la base de datos, y el formato de almacenamiento interno no debería ser su asunto. Este aspecto tiene que ver con un principio importante en el modelo relacional llamado independencia de datos físicos.
Un tipo de datos encapsula el comportamiento. Si utiliza un tipo inadecuado, se olvida de todo el comportamiento encapsulado en el tipo, en la forma de operadores y funciones que lo soportan. Como un ejemplo sencillo, para los tipos que representan números, el operador más (+) representa adición, pero para las cadenas de caracteres, el mismo operador representa concatenación. Si elige un tipo inapropiado para su valor, a veces tiene que convertir el tipo (explícita o implícitamente), y a veces hacer malabares en el valor, con el fin de tratarlo como lo que se supone que es.
Otro principio importante en la elección del tipo apropiado para sus datos es el tamaño. A menudo uno de los principales aspectos que afectan el rendimiento de la consulta es la cantidad de E/S involucrada. Una consulta que lee menos, simplemente tiende a correr más rápido. Cuanto más grande es el tipo que utiliza, mayor es el almacenamiento que utiliza. Las tablas con muchos millones de filas, si no billones, son comunes hoy en día. Cuando inicia multiplicando el tamaño de un tipo por el número de filas en la tabla, los números pueden llegar a ser rápidamente significativos. A modo de ejemplo, suponga que tiene un atributo que representa resultados de pruebas, que son enteros en el rango de 0 al 100. El uso de un tipo de datos INT para este propósito es una exageración. Usaría 4 bytes por valor, mientras que una TINYINT usaría sólo 1 byte, y por lo tanto es el tipo más apropiado para este caso. Del mismo modo, para los datos que supuestamente representan fechas, la gente tiene una tendencia a utilizar DATETIME, que utiliza 8 bytes de almacenamiento. Si se supone que el valor representa una fecha sin un tiempo, debe usar DATE, que utiliza sólo 3 bytes de almacenamiento. Si se supone que el valor representa tanto fecha y tiempo, debería considerar DATETIME2 o SMALLDATETIME. El primero requiere almacenamiento entre 6 a 8 bytes (dependiendo de la precisión), y como valor añadido, ofrece una amplia gama de fechas y mejoras, de precisión controlable. El último sólo utiliza 4 bytes de almacenamiento, por lo tanto, si soporta un rango de fechas y precisión que cubra sus necesidades, debe utilizarlo. En resumen, debe utilizar el tipo más pequeño que sirva a sus necesidades. Aunque, por supuesto, esto no se aplica en ejecuciones cortas, pero si en ejecuciones largas. Por ejemplo, el uso de un tipo INT para una llave en una tabla que de un momento a otro crecerá a un grado de billones de filas, es una mala idea. Debería utilizar BIGINT. Pero el uso de INT para un atributo que representa resultados de pruebas o DATETIME para los valores de fecha y hora que requieren una precisión al minuto, son ambas malas opciones, más aún cuando piensa en ejecuciones largas.
Tenga mucho cuidado con los tipos imprecisos FLOAT y REAL. Las dos primeras oraciones en la documentación que describe estos tipos, debería darle un buen sentido de su naturaleza: "tipo de datos numéricos aproximados, para usarlo con datos numéricos de punto flotante. Los datos de punto flotante son aproximados; por lo tanto, no todos los valores en el rango del tipo de datos pueden ser representados con exactitud". El beneficio en estos tipos es que pueden representar números muy grandes y muy pequeños, más allá de cualquier otro tipo numérico que pueda representar y soportar el SQL Server. Así, por ejemplo, si necesita representar números muy grandes o muy pequeños, para fines científicos y no necesita una completa precisión, puede encontrar estos tipos de utilidad. También son bastante económicos (4 bytes para REAL y 8 bytes para FLOAT). Pero no los utilice para cosas que se supone que deban ser precisas.

El Problema Float

Recordamos un caso donde un cliente utilizaba FLOAT para representar los números de código de barras de productos, y estaba entonces sorprendido por no conseguir el producto correcto al escanear los códigos de barras de los productos. También, recientemente, se obtuvo una consulta acerca de la conversión de un valor FLOAT a NUMERIC, resultando en un valor diferente del que se ingresó. Aquí está el caso.
DECLARE @f AS FLOAT = '29545428.022495';
SELECT CAST(@f AS NUMERIC(28, 14)) AS Valor;
¿Puede adivinar el resultado de este código? Aquí está.
Valor
---------------------------------------
29545428.02249500200000
Como se ha mencionado, algunos valores no pueden ser representados con precisión.
En resumen, asegúrese de usar los tipos numéricos exactos cuando necesite representar valores precisos, y reservar el uso de tipos numéricos aproximados solo en los casos en que tenga la certeza de que sea aceptable para la aplicación.
Otro aspecto importante en la elección de un tipo, tiene que ver con la elección de los tipos fijos (CHAR, NCHAR, BINARY) versus los dinámicos (VARCHAR, NVARCHAR, VARBINARY). Los tipos fijos utilizan el almacenamiento para el tamaño indicado; por ejemplo, CHAR (30) utiliza el almacenamiento para 30 caracteres, ya sea que en realidad especifique 30 caracteres o menos. Esto significa que al actualizar no requerirá que la fila se expanda físicamente, y por lo tanto el desplazamiento de datos no es requerido. Así que para los atributos que son actualizados frecuentemente, donde el rendimiento de la actualización es una prioridad, debe considerar los tipos fijos. Note que cuando la compresión es utilizada, específicamente la compresión de filas, SQL Server almacena los tipos fijos como si fueran variables, pero con menos sobrecarga.
Los tipos variables utilizan el almacenamiento para el que ingresa, además de un par de bytes para información offset (o 4 bits con la compresión de fila). Así que para amplias variaciones de tamaños de cadena, si utiliza los tipos variables puede ahorrar mucho de almacenamiento. Como ya se mencionó, a menos almacenamiento utilizado, menos tiene una consulta para leer, y más rápido se puede realizar la consulta. Así los tipos de longitud variable son usualmente preferibles en tales casos, cuando el rendimiento de lectura es una prioridad.
Con las cadenas de caracteres, también existe la cuestión de utilizar los tipos de caracteres regulares (CHAR, VARCHAR) versus los tipos Unicode (NCHAR, NVARCHAR). Los primeros usan 1 byte de almacenamiento por carácter y soporta sólo un idioma (basado en las propiedades de intercalación) además del inglés. Los últimos usan 2 bytes de almacenamiento por carácter (a menos que este comprimido) y soportan múltiples idiomas. Si un par sustituto es necesario, un carácter requerirá 4 bytes de almacenamiento. Así que si los datos están en varios idiomas y necesita representar solo un solo idioma, además del inglés, en sus datos, puede beneficiarse del uso de tipos de caracteres regulares, con los requisitos de almacenamiento más bajos. Cuando los datos son internacionales, o su aplicación de forma nativa trabaja con datos Unicode, debe utilizar tipos de datos Unicode para que no pierda la información. Los requerimientos de almacenamiento más grandes de Datos Unicode son mitigados a partir de SQL Server 2008 R2 con la compresión Unicode.
Cuando utiliza tipos que pueden tener una longitud asociada, tales como CHAR y VARCHAR, T-SQL soporta omitir la longitud y entonces utilizar una longitud por defecto. Sin embargo, en diferentes contextos, los valores por defecto pueden ser diferentes. Es considerada una buena práctica siempre ser explícito acerca de la longitud, como en CHAR(1) o VARCHAR(30).
Cuando se definen atributos que representan la misma cosa a través de diferentes tablas, especialmente las que serán usadas después como columnas join (como la llave primaria en una tabla y la llave foránea en otra); es muy importante ser consistente con los tipos. De lo contrario, cuando compare un atributo con otro, SQL Server tiene que aplicar la conversión implícita de un tipo de atributo al otro, y esto podría tener implicaciones de rendimiento negativos, como evitar el uso eficiente de los índices.
También querrá asegurarse de que cuando se indica un literal de un tipo, utiliza la forma correcta. Por ejemplo, los literales de cadenas de carácter regulares son delimitados con comillas simples, como en 'abc', mientras que los literales de cadenas de caracteres Unicode son delimitadas con una N mayúscula y luego con comillas simples, como en N'abc'. Cuando una expresión involucra elementos de diferentes tipos, SQL Server necesita aplicar la conversión implícita cuando sea posible, y esto puede resultar en una penalidad del rendimiento. Note que en algunos casos la interpretación de un literal no puede ser lo que piensa intuitivamente. Con el fin de forzar un literal a ser de un cierto tipo, puede que necesite aplicar una conversión explícita con funciones como CAST, CONVERT, PARSE, o TRY_CAST, TRY_CONVERT y TRY_PARSE. A modo de ejemplo, el literal 1 es considerado un INT por SQL Server en cualquier contexto. Si necesita que el literal 1 sea considerado, por ejemplo, como un BIT, es necesario convertir el tipo del literal explícitamente, como en CAST(1 AS BIT). Del mismo modo, el literal 4000000000 es considerado NUMERIC y no BIGINT. Si necesita que el literal sea como en el último, utilice CAST(4000000000 AS BIGINT). La diferencia entre las funciones sin TRY y sus contrapartes con TRY es que aquellos sin TRY fallan si el valor no es convertible, mientras que aquellos con TRY retornan un NULL en tales casos. Por ejemplo, el código siguiente produce un error.
SELECT CAST('abc' AS INT);
Por el contrario, el código siguiente retorna un NULL.
SELECT TRY_CAST('abc' AS INT);
En cuanto a la diferencia entre CAST, CONVERT y PARSE; con CAST, se indica la expresión y el tipo de destino; con CONVERT, hay un tercer argumento que representa el estilo para la conversión, que es soportada por algunas conversiones, como entre cadenas de carácter y valores de fecha y tiempo. Por ejemplo, CONVERT(DATE, '1/2/2012', 101) convierte la cadena de caracteres literal a DATE usando el estilo 101 representando el estándar de Estados Unidos. Con PARSE, puede indicar la cultura utilizando cualquier cultura soportada por Microsoft .NET Framework. Por ejemplo, PARSE('1/2/2012' AS DATE USING ‘en-US’) analiza la entrada literal como DATE utilizando una cultura de inglés de Estados Unidos.
Cuando utiliza expresiones que implican operandos de diferentes tipos, SQL Server normalmente convierte el que tiene la precedencia menor de tipo de datos a la que tiene la mayor. Considerar la expresión 1+’1’ como ejemplo. Un operando es INT y el otro es VARCHAR. La INT precede a VARCHAR; por lo tanto, SQL Server implícitamente convierte el valor VARCHAR '1' al valor INT 1, y por lo tanto el resultado de la expresión es 2 y no la cadena '11'. Por supuesto, siempre puede controlarlo utilizando una conversión explícita.
Si todos los operandos de la expresión son del mismo tipo, este también será el tipo del resultado, y es posible que no quiera que sea el caso. Por ejemplo, el resultado de la expresión 5/2 en T-SQL es el valor INT de 2 y no el valor NUMERIC de 2.5, debido a que ambos operandos son números enteros, por lo tanto el resultado es un número entero. Si estaba trabajando con dos columnas enteras, como col1 / col2, y quería que la división sea NUMERIC, podría necesitar convertir las columnas explícitamente, como en CAST(col1 AS NUMERIC(12, 2)) / CAST(col2 AS NUMERIC(12, 2)).

Escogiendo un Tipo de Datos para Llaves

Cuando define llaves inteligentes en sus tablas, llámese llaves basadas en atributos ya existentes derivadas de la aplicación, no hay preguntas acerca de los tipos debido a que ya las escogió para sus atributos. Pero cuando necesita crear llaves sustitutas, que han sido agregadas con el único fin de ser utilizadas como llaves, necesita determinar un tipo apropiado para el atributo, además de un mecanismo para generar los valores de llave. La realidad es que escuchará muchas opiniones diferentes sobre cuál es la mejor solución, algunas basadas ​​en la teoría, y algunas respaldadas por evidencia empírica. Pero los diferentes sistemas y las diferentes cargas de trabajo podrían terminar con diferentes soluciones óptimas. Es más, en algunos sistemas, el rendimiento de escritura puede ser la prioridad, mientras que en otros, puede ser el rendimiento de lectura. Una solución puede hacer las inserciones más rápidas, pero las lecturas más lentas, y otra solución podría funcionar al revés. Al final del día, para tomar decisiones inteligentes, es importante aprender de la teoría, aprender acerca de las experiencias de otros, pero eventualmente asegurarse de ejecutar benchmarks en el sistema de destino.
Note que esta post se refiere a elementos como objetos sequence, la propiedad de columna identity, y los índices.
Las opciones típicas que las personas usan para generar llaves sustitutas son:
·         La Propiedad de Columna Identity. Una propiedad que automáticamente genera las llaves en un atributo de tipo numérico con una escala de 0; es decir, cualquier tipo entero (TINYINT, SMALLINT, INT, BIGINT) o NUMERIC/DECIMAL con escala de 0.
·         El Objeto Sequence. Un objeto independiente en la base de datos de la cual puede obtener nuevos valores de secuencia. Como identity, soporta cualquier tipo numérico con una escala de 0. A diferencia de identity, no está atada a una columna en particular; en su lugar, como se ha mencionado, se trata de un objeto independiente en la base de datos. También puede solicitar un nuevo valor de un objeto sequence antes de usarlo.
·     GUIDs No Secuenciales. Puede generar identificadores únicos globales no secuenciales para ser almacenados en un atributo de un tipo UNIQUEIDENTIFIER. Puede utilizar la función NEWID de T-SQL para generar un nuevo GUID, posiblemente invocándola con una expresión predeterminada adjunta a la columna. También puede generar uno desde cualquier lugar, por ejemplo, el cliente, utilizando una interface de programación de aplicaciones (API) que genera un nuevo GUID. Las GUIDs están garantizadas que serán únicas a través del espacio y del tiempo.
·         GUIDs Secuenciales. Puede generar GUIDs secuenciales en la máquina utilizando la función NEWSEQUENTIALID de T-SQL.
·         Soluciones Personalizadas. Si no desea utilizar las herramientas integradas que proporciona SQL Server para generar llaves, necesita desarrollar su propia solución personalizada. El tipo de datos para la llave entonces depende de su solución.
Una cosa a considerar con respecto a la elección del generador de llave sustituta y el tipo de datos implicado, es el tamaño del tipo de datos. Cuanto más grande sea el tipo, mayor almacenamiento es requerido, y por lo tanto hace más lenta la lectura. Una solución usando un tipo de datos INT requiere 4 bytes por valor, BIGINT requiere 8 bytes, UNIQUEIDENTIFIER requiere 16 bytes, y así sucesivamente. Los requerimientos de almacenamiento para su llave sustituta pueden tener un efecto en cascada si su índice clustered es definido sobre las mismas columnas llave (el valor predeterminado para una restricción de llave primaria). Las columnas llave de índice clustered son utilizadas por todos los índices nonclustered internamente, como los medios para localizar filas en la tabla. Así que si se define un índice clustered en una columna x, e índices nonclustered, uno en la columna a, uno en b, y uno en la c; sus índices nonclustered son internamente creados en la columna (a, x), (b, x), y (c, x), respectivamente. En otras palabras, el efecto es multiplicativo.
En cuanto al uso de llaves secuenciales (como ocurre con identity, sequence y NEWSEQUENTIALID) versus los no secuenciales (como con NEWID o un generador de llave aleatoria personalizada), tiene varios aspectos a considerar.
Empezando con llaves secuenciales, todas las filas van al extremo derecho del índice. Cuando una página está completa, SQL Server asigna una nueva página y la llena. Esto resulta en una menor fragmentación en el índice, el cual es beneficioso para el rendimiento de lectura. Además, las inserciones pueden ser más rápidas cuando una sola sesión está cargando los datos, y los datos residen en una sola unidad o un pequeño número de unidades. Sin embargo, con los subsistemas de almacenamiento de gama alta que tienen muchos discos, la situación puede ser diferente. Cuando carga los datos de varias sesiones, terminará con una contención latch de página (latches son objetos utilizados para sincronizar accesos a las páginas de la base de datos) sobre las páginas más a la derecha de la lista enlazada a nivel de hoja del índice. Este cuello de botella impide usar todo el rendimiento del subsistema de almacenamiento.
Note que si decide usar las llaves secuenciales, y está usando las numéricas, puede siempre comenzar con el valor más bajo en el tipo para utilizar el rango completo. Por ejemplo, en lugar de empezar con 1 en un tipo INT, puede comenzar con -147483648.
Considerar llaves no secuenciales, como las aleatorias generadas con NEWID o con una solución personalizada. Cuando trata de forzar una fila en una página ya llena, SQL Server realiza una división de página clásica, que asigna una nueva página y mueve la mitad de las filas de la página original a la nueva. Dividir una página tiene un costo, además de que da lugar a la fragmentación del índice. La fragmentación del índice puede tener un impacto negativo en el rendimiento de las lecturas. Sin embargo, en términos de rendimiento de inserción, si el subsistema de almacenamiento contiene muchos discos y va a cargar los datos de varias sesiones, el orden aleatorio en realidad puede ser mejor que el secuencial a pesar de las divisiones. Eso es porque no hay un punto caliente en el extremo derecho del índice, y se utiliza el almacenamiento del subsistema disponible de mejor rendimiento.
Note que la división y la fragmentación del índice pueden ser mitigadas por reconstruir el índice periódicamente como parte de las actividades de mantenimiento habituales, suponiendo que tiene una ventana disponible para esto.
Si por las razones antes mencionadas decida confiar en llaves generadas de forma aleatoria, aun necesitará decidir entre GUIDs y una solución de generador de llave aleatoria personalizada. Como ya se ha mencionado, los GUIDs son almacenados en un tipo UNIQUEIDENTIFIER que es de 16 bytes de tamaño; eso es grande. Pero uno de los principales beneficios de los GUIDs es el hecho de que pueden ser generados en cualquier lugar y no generan conflictos a través del tiempo y del espacio. Puede generar GUIDs no sólo en SQL Server utilizando la función NEWID, sino en cualquier lugar, usando los APIs. De lo contrario, podría quedarse con una solución personalizada que genera llaves aleatorias más pequeñas. La solución puede incluso ser una combinación de una herramienta integrada y de algunos ajustes encima.
Para concluir acerca de las llaves y tipos de llaves, recuerde que hay múltiples opciones. La más pequeña es generalmente la mejor, pero luego está la cuestión del hardware que utiliza y dónde están sus prioridades de rendimiento. Asimismo, recuerde que si bien es muy importante hacer conjeturas, también es importante un benchmark de las soluciones en el entorno de destino.

Funciones Date y Time

T-SQL soporta un número de funciones date y time que le permiten manipular sus datos de fecha y de hora. El soporte para las funciones date y time sigue mejorando, con las dos últimas de versiones de SQL Server que agregan un número de nuevas funciones.
Esta sección cubre algunas de las funciones importantes soportadas por T-SQL y proporciona algunos ejemplos.

Fecha y Hora actual

Una categoría importante de las funciones es la categoría que retorna la fecha y la hora actual. Las funciones en esta categoría son GETDATE, CURRENT_TIMESTAMP, GETUTCDATE, SYSDATETIME, SYSUTCDATETIME y SYSDATETIMEOFFSET.
GETDATE es específica de T-SQL, retornando la fecha y la hora actual en la instancia de SQL Server al que está conectado, como un tipo de datos DATETIME. CURRENT_TIMESTAMP es lo mismo, sólo que es estándar, y por lo tanto la recomendada para usar. SYSDATETIME y SYSDATETIMEOFFSET son similares, sólo que retornan los valores como los tipos más precisos DATETIME2 y DATETIMEOFFSET (incluyendo offset), respectivamente. Note que no hay funciones integradas para retornar la fecha actual o la hora actual; para obtener dicha información, simplemente convierta la función SYSDATETIME a DATE o TIME, respectivamente. Por ejemplo, para obtener la fecha actual, use CAST(SYSDATETIME() AS DATE). La función GETUTCDATE retorna la fecha y la hora actual en términos UTC como un tipo DATETIME, y SYSUTCDATE hace lo mismo, sólo retornando el resultado como el tipo DATETIME2 más preciso.

Partes de Fecha y de Hora

Esta sección cubre las funciones de fecha y de hora que o bien extraen una parte de un valor de fecha y de hora (como DATEPART) o construyen un valor de fecha y de hora de las partes (como DATEFROMPARTS).
Usando la función DATEPART, puede extraer de un valor de fecha y de hora de entrada  una parte deseada, tal como un año, los minutos, o nanosegundos, y retornar las partes extraídas como un entero. Por ejemplo, la expresión DATEPART(month, '20120212') retorna 2. T-SQL proporciona las funciones YEAR, MONTH y DAY como abreviaciones a DATEPART, y que no requiere especificarla. La función DATENAME es similar a DATEPART, sólo retorna el nombre de la parte como una cadena de caracteres, en contraposición a un valor entero. Note que la función es dependiente del idioma. Es decir, que si el idioma efectivo en su sesión es us_english, la expresión DATENAME(month, '20120212') retorna 'February', pero para el italiano, devuelve 'febbraio'.
T-SQL proporciona un conjunto de funciones que construyen un valor de fecha y de hora deseado de sus partes numéricas. Tiene una función para cada uno de los seis tipos de fecha y de hora disponibles: DATEFROMPARTS, DATETIME2FROMPARTS, DATETIMEFROMPARTS, DATETIMEOFFSETFROMPARTS, TIMEFROMPARTS y SMALLDATETIMEFROMPARTS. Por ejemplo, para construir un valor DATE de sus partes, podría usar una expresión así como DATEFROMPARTS(2012, 02, 12).
Por último, la función EOMONTH calcula la respectiva fecha de fin de mes para el valor de fecha y de hora de entrada. Por ejemplo, suponga que hoy es 12 de febrero del 2012. La expresión EOMONTH(SYSDATETIME()) debería entonces retornar la fecha '2012-02-29'. Esta función soporta una segunda entrada opcional indicando cuantos meses agregar al resultado.

Add y Diff

T-SQL soporta las funciones de adición y de diferencia de fecha y de hora llamado DATEADD y DATEDIFF.
DATEADD es una función muy comúnmente usada. Con la que puede agregar un número solicitado de unidades de una parte especificada a un valor de fecha y de hora especificado. Por ejemplo, la expresión DATEADD(year, 1, '20120212 ') agrega un año a la fecha de entrada 12 de febrero del 2012.
DATEDIFF es otra función comúnmente usada; retorna la diferencia en términos de una parte solicitada entre dos valores de fecha y de hora. Por ejemplo, la expresión DATEDIFF(day, '20110212', '20120212') calcula la diferencia en días entre el 12 de febrero del 2011 y el 12 de febrero del 2012, retornando el valor 365. Note que esta función mira sólo las partes solicitadas y por arriba en la jerarquía de fecha y de hora, no por debajo. Por ejemplo, la expresión DATEDIFF(year, '20111231', '20120101') mira sólo la parte del año, y por lo tanto retorna 1. No mira las partes mes y día de los valores.

Offset

T-SQL soporta dos funciones relativas a valores de fecha y de hora con un offset: SWITCHOFFSET y TODATETIMEOFFSET.
Con la función SWITCHOFFSET, puede retornar un valor DATETIMEOFFSET de entrada en un término offset solicitado. Por ejemplo, considere la expresión SWITCHOFFSET(SYSDATETIMEOFFSET(), ‘-08:00’). Independientemente del offset de la instancia a la que está conectado, solicita presentar el valor de fecha y de hora actual en términos del offset ‘-08:00’. Si el offset del sistema es, por ejemplo, '-05:00', la función compensará ésta por restar tres horas del valor de entrada.
La función TODATETIMEOFFSET es utilizada para un propósito diferente. Se utiliza para construir un valor DATETIMEOFFSET de dos entradas: el primero es un valor de fecha y de hora que no considera offset, y el segundo es el offset. Puede utilizar esta función cuando migra de datos que no consideran offset, donde se mantiene el valor de fecha y de hora local, en un atributo, y el offset en otro, para datos que consideran offset. Digamos que tiene la fecha y la hora local, en un atributo llamado dt, y el offset en un atributo llamado theoffset. Agrega un atributo llamado dto de un tipo DATETIMEOFFSET a la tabla. Entonces, actualiza el nuevo atributo a la expresión TODATETIMEOFFSET(dt, theoffset), y luego elimina los atributos originales dt y theoffset de la tabla.
El código siguiente demuestra cómo utilizar ambas funciones.
SELECT
  SWITCHOFFSET('20130212 14:00:00.0000000 -08:00', '-05:00') AS [SWITCHOFFSET],
  TODATETIMEOFFSET('20130212 14:00:00.0000000', '-08:00') AS [TODATETIMEOFFSET];
Aquí está el resultado de este código.
SWITCHOFFSET                  TODATETIMEOFFSET
---------------------------------- ----------------------------------
2013-02-12 17:00:00.0000000 -05:00 2013-02-12 14:00:00.0000000 -08:00

Funciones Carácter

T-SQL no fue realmente diseñada para soportar funciones de manipulación de cadenas de caracteres muy sofisticadas, por lo que no encontrará un conjunto muy grande de tales funciones. Esta sección describe las funciones de cadenas de caracteres que T-SQL soporta, organizadas en categorías.

Concatenación

La concatenación de cadenas de caracteres es una necesidad muy común. T-SQL soporta dos formas de concatenación de cadenas, una con el operador de suma (+), y otro con la función CONCAT.
Aquí se muestra un ejemplo de concatenación de cadenas en una consulta utilizando el operador +.
SELECT empid, country, region, city,
country + N',' + region + N',' + city AS localizacion
FROM HR.Employees;
Aquí está el resultado de esta consulta.
empid  country  region  city      localizacion
------ -------- ------- --------- ----------------
1      USA       WA      Seattle   USA,WA,Seattle
2      USA       WA      Tacoma    USA,WA,Tacoma
3      USA       WA      Kirkland  USA,WA,Kirkland
4      USA       WA      Redmond   USA,WA,Redmond
5      UK        NULL    London    NULL
6      UK        NULL    London    NULL
7      UK        NULL    London    NULL
8      USA       WA      Seattle   USA,WA,Seattle
9      UK        NULL    London    NULL
Observe que cuando cualquiera de las entradas es NULL, el operador + retorna un NULL. Es un comportamiento estándar que puede ser cambiado por desactivar una opción de sesión llamada CONCAT_NULL_YIELDS_NULL, aunque no es recomendable confiar en un comportamiento no estándar. Si quiere sustituir un NULL con una cadena vacía, hay un número de formas para hacer esto programáticamente. Una opción es usar COALESCE(<expresión>,' '). Por ejemplo, en estos datos, solo la región puede ser NULL, así que puede utilizar la siguiente consulta para reemplazar una coma más la región con una cadena vacía cuando la región es NULL.
SELECT empid, country, region, city,
country + COALESCE( N',' + region, N'') + N',' + city AS localizacion
FROM HR.Employees;
Otra opción es utilizar la función CONCAT que, a diferencia del operador +, sustituye una entrada NULL con una cadena vacía. Así es como se ve la consulta.
SELECT empid, country, region, city,
CONCAT(country, N',' + region, N',' + city) AS localizacion
FROM HR.Employees;
Aquí está la salida de esta consulta.
empid  country  region  city      localizacion
------ -------- ------- --------- ----------------
1       USA      WA      Seattle   USA,WA,Seattle
2       USA      WA      Tacoma    USA,WA,Tacoma
3       USA      WA      Kirkland  USA,WA,Kirkland
4       USA      WA      Redmond   USA,WA,Redmond
5       UK       NULL    London    UK,London
6       UK       NULL    London    UK,London
7       UK       NULL    London    UK,London
8       USA      WA      Seattle   USA,WA,Seattle
9       UK       NULL    London    UK,London
Observe que esta vez, cuando la región fue NULL, fue sustituido por una cadena vacía.

Extracción y Posición de Subcadenas

Esta sección cubre las funciones que puede utilizar para extraer una subcadena de una cadena, e identificar la posición de una subcadena dentro de una cadena.
Con la función SUBSTRING, puede extraer una subcadena de una cadena dada como el primer argumento, partiendo de la posición dada como el segundo argumento, y una longitud dada como el tercer argumento. Por ejemplo, la expresión SUBSTRING('abcde', 1, 3) retorna 'abc'. Si el tercer argumento es mayor que el que podría obtener al final de la cadena, la función no falla; en su lugar, simplemente extrae la subcadena hasta el final de la cadena.
Las funciones LEFT Y RIGHT extraen un número de caracteres solicitados desde la izquierda, y del final derecho de la cadena de entrada, respectivamente. Por ejemplo, LEFT('abcde', 3) retorna 'abc' y RIGHT('abcde', 3) retorna 'cde'.
La función CHARINDEX retorna la posición de la primera ocurrencia de la cadena proporcionada como el primer argumento en la cadena proporcionada como el segundo argumento. Por ejemplo, la expresión CHARINDEX(' ', 'Pedro Rondon') busca la primera ocurrencia de un espacio en la segunda de entrada, retornando 6 en este ejemplo. Note que puede proporcionar un tercer argumento indicando a la función dónde empezar la búsqueda.
Puede combinar, o anidar, funciones en la misma expresión. Por ejemplo, suponga que consulta una tabla con un atributo llamado nombrecompleto formateado como '<first> <last>', y necesita escribir una expresión que extrae la primera parte del nombre. Puede utilizar la siguiente expresión.
LEFT(nombrecompleto, CHARINDEX(' ', nombrecompleto) - 1)
T-SQL también soporta una función llamada PATINDEX que, como en CHARINDEX, puede utilizarla para localizar la primera posición de una cadena dentro de otra cadena. Pero mientras que con CHARINDEX está buscando una cadena constante, con PATINDEX está buscando un patrón. El patrón es formado muy similar al patrón LIKE con el que probablemente estará familiarizado, en el que utiliza comodines como % para cualquier cadena, _ para un solo carácter, y entre corchetes ([]), representando un carácter individual de una lista determinada o rango. Como un ejemplo, la expresión PATINDEX('%[0-9]%', 'abcd123efgh') mira la primera ocurrencia de un dígito (un carácter en el rango de 0-9) en la segunda entrada, retornando la posición 5 en este caso.

Longitud de Cadena

T-SQL proporciona dos funciones que puede utilizar para medir la longitud de un valor de entrada, LEN y DATALENGTH.
La función LEN retorna la longitud de una cadena de entrada en términos del número de caracteres. Note que retorna el número de caracteres, no bytes, si la entrada es una cadena de caracteres regular o de caracteres Unicode. Por ejemplo, la expresión LEN(N'xyz') retorna 3. Si hay espacios finales, LEN los elimina.
La función DATALENGTH retorna la longitud de la entrada en términos del número de bytes. Esto significa, por ejemplo, que si la entrada es una cadena de caracteres Unicode, contará 2 bytes por carácter. Por ejemplo, la expresión DATALENGTH(N'xyz') retorna 6. Note también que, a diferencia de LEN, la función DATALENGTH no quita los espacios finales.

Alteración de Cadenas

T-SQL soporta un número de funciones que puede utilizar para aplicar cambios a una cadena de entrada. Estos son REPLACE, REPLICATE y STUFF.
Con la función REPLACE, puede reemplazar en una cadena de entrada proporcionada como el primer argumento, todas las ocurrencias de la cadena proporcionada como el segundo argumento, con la cadena proporcionada como el tercer argumento. Por ejemplo, la expresión REPLACE('.1.2.3.', '.', '/') sustituye todas las ocurrencias de un punto (.) con una barra (/), retornando la cadena '/1/2/3/'.
La función REPLICATE permite replicar una cadena de entrada un número solicitado de veces. Por ejemplo, la expresión REPLICATE ('0', 10) replica la cadena '0' diez veces, retornando '0000000000'.
La función STUFF opera sobre una cadena de entrada proporcionada como el primer argumento; entonces, a partir de la posición del carácter indicado como segundo argumento, elimina el número de caracteres indicado por el tercer argumento. Luego inserta en esa posición la cadena especificada como el cuarto argumento. Por ejemplo, la expresión STUFF(',x,y,z', 1, 1, '') elimina el primer carácter de la cadena de entrada, retornando la cadena 'x,y,z'.

Formato de Cadenas

Esta sección cubre las funciones que puede utilizar para aplicar las opciones de formato a una cadena de entrada. Estas son las funciones UPPER, LOWER, LTRIM, RTRIM y FORMAT.
Las cuatro primeras funciones son fáciles de entender (mayúsculas de la entrada, minúsculas de la entrada, entrada después de eliminar espacios iniciales, y entrada después de eliminar espacios finales). Note que no hay alguna función TRIM que elimine tanto los espacios iniciales y los finales; para lograr esto, necesita anidar una llamada a una función dentro de otra, como en RTRIM(LTRIM(<input>)).
Con la función FORMAT, puede formatear un valor de entrada basado en una cadena de formato, y opcionalmente especificar la cultura como una tercera entrada, cuando sea relevante. Puede utilizar cualquier cadena de formato soportado por el .NET Framework. Como un ejemplo, la expresión FORMAT(1759, '000000000') formatea el número de entrada como una cadena de caracteres con un tamaño fijo de 10 caracteres con ceros a la izquierda, retornando '0000001759'.

Expresión CASE y Funciones Relacionadas

T-SQL soporta una expresión llamada CASE y un número de funciones relacionadas que puede utilizar para aplicar lógica condicional para determinar el valor retornado. Mucha gente se refiere incorrectamente a CASE como un sentencia. Una sentencia realiza algún tipo de acción o controla el flujo del código, y eso no es lo que CASE hace; CASE retorna un valor, y por lo tanto es una expresión.
La expresión CASE tiene dos formas, la forma simple y la forma buscada. He aquí un ejemplo de la forma CASE simple, emitida sobre la base de datos de muestra TSQL2012.
SELECT productid, productname, unitprice, discontinued,
 CASE discontinued
  WHEN 0 THEN 'No'
  WHEN 1 THEN 'Si'
  ELSE 'Desconocido'
 END AS discontinued_desc
FROM Production.Products;
La forma simple compara una expresión de entrada (en este caso el atributo discontinued) a múltiples expresiones when de escalares posibles (en este caso 0 y 1)​​, y retornan la expresión de resultado (en este caso, 'No' y 'Si', respectivamente) asociado con la primera coincidencia. Si no hay coincidencias y una cláusula ELSE es especificada, la expresión else (en este caso, 'Desconocido') es retornada. Si no hay cláusula ELSE, el valor predeterminado es ELSE NULL. Aquí se muestra una forma abreviada de la salida de esta consulta.
productid  productname    unitprice  discontinued discontinued_desc
---------- -------------- ---------- ------------ -----------------
1           Product HHYDP  18.00 0            No
2           Product RECZE  19.00 0            No
3           Product IMEHJ  10.00 0            No
4           Product KSBRM  22.00 0            No
5           Product EPEIM  21.35 1            Si
...
La forma buscada de la expresión CASE es más flexible. En lugar de comparar una expresión de entrada a múltiples expresiones posibles, utiliza predicados en las cláusulas WHEN, y el primer predicado que se evalúa a Verdadero determina cual expresión when es retornada. Si ninguno es Verdadero, la expresión CASE retorna la expresión else. La siguiente consulta es un ejemplo.
SELECT productid, productname, unitprice,
 CASE
  WHEN unitprice < 20.00 THEN 'Bajo'
  WHEN unitprice < 40.00 THEN 'Medio'
  WHEN unitprice >= 40.00 THEN 'Alto'
  ELSE 'Desconocido'
 END AS pricerange
FROM Production.Products;
En este ejemplo, la expresión CASE retorna una descripción del rango del precio unitario del producto. Cuando el precio unitario es inferior a $20.00, retorna ‘Bajo’, cuando es $20.00 o más y por debajo de $40.00, retorna ‘Medio’, y cuando es $40.00 o más, retorna 'Alto'. Hay una cláusula ELSE por seguridad; si la entrada es NULL, la expresión else retornada es "Desconocido". Note que el segundo predicado when no necesita comprobar si el valor es $20.00 o más, explícitamente. Eso es porque el predicado when es evaluado en orden y el primer predicado when no se evalua a Verdadero. He aquí una forma abreviada de la salida de esta consulta.
productid  productname    unitprice   pricerange
---------- -------------- ---------- ----------
1           Product HHYDP  18.00 Bajo
2           Product RECZE  19.00 Bajo
3           Product IMEHJ  10.00 Bajo
4           Product KSBRM  22.00 Medio
5           Product EPEIM  21.35 Medio
...
T-SQL soporta un número de funciones que pueden ser consideradas como abreviaciones de la expresión CASE. Estas son las funciones estándar COALESCE y NULLIF y las no estándar ISNULL, IIF y CHOOSE.
La función COALESCE acepta una lista de expresiones como entrada y retorna la primera que no es NULL, o NULL si todas son NULLs. Por ejemplo, la expresión COALESCE(NULL, 'x', 'y') retorna 'x'. Más generalmente, la expresión:
COALESCE(<exp1>, <exp2>, , <expn>)
Es similar a la siguiente.
CASE
  WHEN <exp1> IS NOT NULL THEN <exp1>
  WHEN <exp2> IS NOT NULL THEN <exp2>
 
  WHEN <expn> IS NOT NULL THEN <expn>
  ELSE NULL
END
Un uso típico de COALESCE es sustituir un NULL con otra cosa. Por ejemplo, la expresión COALESCE (region,'') retorna region si no es NULL y retorna una cadena vacía si es NULL.
T-SQL soporta una función no estándar llamada ISNULL que es similar a la estándar COALESCE, pero es un poco más limitada en el sentido de que sólo soporta dos entradas. Como COALESCE, retorna la primera entrada que es no NULL. Así, en lugar de COALESCE(region,''), podría usar ISNULL(región,''). Generalmente, se recomienda atenerse a las características estándar a menos que exista alguna ventaja en flexibilidad o en rendimiento en la característica no estándar que sea de mayor prioridad. ISNULL es en realidad más limitada que COALESCE, por lo general, se recomienda adherirse a COALESCE.
Hay un par de diferencias sutiles entre COALESCE e ISNULL que puede ser interesante. Una diferencia está, en cual entrada determina el tipo de salida. Considere el siguiente código.
DECLARE
 @x AS VARCHAR(3) = NULL,
 @y AS VARCHAR(10) = '1234567890';
SELECT COALESCE(@x, @y) AS [COALESCE], ISNULL(@x, @y) AS [ISNULL];
Aquí está el resultado de este código.
COALESCE   ISNULL
---------- ------
1234567890 123
Observe que el tipo de la expresión COALESCE es determinada por el elemento retornado, mientras que el tipo de la expresión ISNULL es determinada por la primera entrada.
La otra diferencia entre COALESCE e ISNULL es cuando se utiliza SELECT INTO. Supongamos que la lista SELECT de una sentencia SELECT INTO contiene las expresiones COALESCE(col1, 0) AS newcol1 versus ISNULL(col1, 0) AS newcol1. Si el atributo de origen col1 es definido como NOT NULL, ambas expresiones producirán un atributo en la tabla de resultado definido como NOT NULL. Sin embargo, si el atributo de origen col1 es definido para permitir NULLs, COALESCE creará un atributo de resultado permitiendo NULLs, mientras que ISNULL creará uno que no permite NULLs.
Tema Clave
COALESCE e ISNULL pueden afectar al rendimiento cuando está combinando conjuntos; por ejemplo, con joins o cuando está filtrando datos. Considere un ejemplo donde tiene dos tablas T1 y T2 y necesita unirlas basadas en una coincidencia entre T1.col1 y T2.col1. Los atributos permiten NULLs. Normalmente, una comparación entre dos NULLs nos da desconocido, y esto causa que la fila sea descartada. Quiere tratar dos NULLs como iguales. Lo que se hace en tal caso es usar COALESCE o ISNULL para sustituir un NULL con un valor que sabe que no puede aparecer en los datos. Por ejemplo, si los atributos son enteros, y sabe que tiene sólo enteros positivos en sus datos (puede incluso tener restricciones que garanticen esto), puede tratar de utilizar el predicado COALESCE(T1.col1,-1) = COALESCE(T2.col1,-1), o ISNULL(T1.col1,-1) = ISNULL(T2.col1, -1). El problema con esta forma es que, debido a que aplica manipulación a los atributos que está comparando, SQL Server no confiará en el ordenamiento del índice. Esto puede resultar en no utilizar los índices disponibles eficientemente. En lugar de ello, se recomienda utilizar la forma más larga: T1.col1 = T2.col1 OR (T1.col1 IS NULL AND T2.col1 IS NULL), el cual SQL Server entiende como una comparación que considera NULLs como iguales. Con esta forma, SQL Server puede utilizar eficientemente los índices.
T-SQL también soporta la función estándar NULLIF. Esta función acepta dos expresiones de entrada, retorna NULL si son iguales, y retorna la primera entrada si no lo son. Por ejemplo, considere la expresión NULLIF(col1, col2). Si col1 es igual a col2, la función retorna un NULL; de lo contrario, retorna el valor col1.
En cuanto a IIF y CHOOSE, éstas son funciones T-SQL no estándar que fueron añadidas para simplificar las migraciones de plataformas de Microsoft Access. Debido a que estas funciones no son estándar y hay alternativas estándar sencillas con expresiones CASE, no es recomendable utilizarlas. Sin embargo, cuando está migrando de Access a SQL Server, estas funciones pueden ayudar a agilizar la migración, y luego poco a poco se puede reconstruir el código para utilizar las funciones estándar disponibles. Con la función IIF, puede retornar un valor si un predicado de entrada es verdadero y otro valor en caso contrario. La función tiene la siguiente forma.
IIF(<predicate>, <true_result>, <false_or_unknown_result>)
Esta expresión es equivalente a la siguiente.
CASE WHEN <predicate> THEN <true_result> ELSE <false_or_unknown_result> END
Por ejemplo, la expresión IIF(orderyear = 2012, qty, 0) retorna el valor en el atributo qty cuando el atributo orderyear es igual a 2012, y cero en caso contrario.
La función CHOOSE le permite proporcionar una posición y una lista de expresiones y retorna la expresión en la posición indicada. La función tiene la siguiente forma.
CHOOSE(<pos>, <exp1>, <exp2>, , <expn>)
Por ejemplo, la expresión CHOOSE(2, 'x', 'y', 'z') retorna 'y'. De nuevo, es fácil reemplazar una expresión CHOOSE con una expresión CASE lógicamente equivalente; pero el punto en soportar CHOOSE, así como de IIF, es simplificar las migraciones de Access a SQL Server como un solución temporal.

Ejercicio 1: Aplicar Concatenación de Cadenas y Usar una Función Date y Time

En este ejercicio, practica la concatenación de cadenas y el uso de una función fecha y hora.
1.    Abrir el SSMS y conéctese a la base de datos de muestra TSQL2012.
2.    Escribir una consulta sobre la tabla HR.Employees que retorna el empid, el nombre completo del empleado (concatenando los atributos firstname, espacio, y lastname), y el año de nacimiento (aplicar una función al atributo birthdate). He aquí una posible consulta que logra esta tarea.
SELECT empid,
  firstname + N' ' + lastname AS nombrecompleto,
  YEAR(birthdate) AS annonacimiento
FROM HR.Employees;

Ejercicio 2: Usar Funciones Date y Time Adicionales

En este ejercicio, practica el uso de funciones de fecha y de hora adicionales.
Escribe una expresión que calcula la fecha del último día del mes en curso. También escribe una expresión que calcula el último día del año en curso. Por supuesto, hay un número de maneras de lograr tales tareas. He aquí una manera de calcular el fin del mes en curso.
SELECT EOMONTH(SYSDATETIME()) AS fin_de_mes_actual;
Y aquí está una manera de calcular el fin del año en curso.
SELECT DATEFROMPARTS(YEAR(SYSDATETIME()), 12, 31) AS ultimo_dia_de_anno_actual;
Usando la función YEAR, extrae el año actual. Luego, proporciona el año en curso junto con el mes 12 y el día 31 a la función DATEFROMPARTS para construir el último día del año en curso.

Ejercicio 3: Usar Funciones de Cadena y de Conversión

En este ejercicio, practica el uso de las funciones de cadena y de conversión.
1.    Escriba una consulta sobre la tabla Production.Products que retorna el productid numérico existente, además el productid formateado como una cadena de tamaño fijo con 10 dígitos con ceros a la izquierda. Por ejemplo, para productid de 42, necesita retornar la cadena '0000000042'. Una manera de enfrentar esta necesidad es usando el siguiente código.
SELECT productid,
  RIGHT(REPLICATE('0', 10) + CAST(productid AS VARCHAR(10)), 10) AS str_productid
FROM Production.Products;
2.    Usando la función REPLICATE, genera una cadena hecha de 10 ceros. Entonces, concatena el formato carácter del productid. Luego extrae los 10 caracteres más a la derecha de la cadena de resultado.
¿Puede pensar en una manera más sencilla de lograr la misma tarea utilizando las nuevas funciones que fueron introducidas en SQL Server 2012? Una manera mucho más simple para lograr lo mismo es utilizando la función FORMAT, como en la siguiente consulta.
SELECT productid,
  FORMAT(productid, 'd10') AS str_productid

FROM Production.Products;

1 comentario:

  1. Este post trata sobre tipo de datos y como elegirlos; y sus funciones integradas. Espero les sea de utilidad.

    ResponderBorrar