2015/06/04

ELECCION ENTRE TIPOS DE DATOS, AHORRANDO ESPACIO

Después de un receso, regreso con nuevos artículos y durante este tiempo, les puedo comentar que he observado diferentes BDs en las cuales usan versiones recientes de SQL SERVER y aun no conocen los diferentes y nuevos tipos de datos que ofrece, esto servirá no solo para ahorrar espacio en disco duro si no también para pasar o consumir menos ancho de banda, cache del equipo, entre muchas otras cosas, sin más les explicaré algunos nuevos tipos de datos que ofrecen las nuevas versiones.

INT o INTEGER
Como bien conocemos, guarda solo enteros positivos o negativos, pero sabías que existe más de un tipo de dato INT y que cada uno ocupa diferente espacio en disco duro? Bien, te dejo una siguiente tabla extraída de la página de MSDN


Tipo de datos
Intervalo
Almacenamiento
bigint
De -2^63 (-9.223.372.036.854.775.808) a 2^63-1 (9.223.372.036.854.775.807)
8 bytes
int
De -2^31 (-2.147.483.648) a 2^31-1 (2.147.483.647)
4 bytes
smallint
De -2^15 (-32.768) a 2^15-1 (32.767)
2 bytes
tinyint
De 0 a 255
1 byte
El tipo de dato TINYINT, solo almacena números positivos como podemos observar en la tabla y solo ocupa 1 byte de espacio, pero se preguntarán sobre algún caso en la vida real en la cual se pudiera utilizar dicho tipo de dato, pues bien, un ejemplo claro sería la edad, o no? Si comparamos un tipo de dato INT con el tipo TINYINT para alguien que tiene una edad de 50, no es lo mismo que en disco duro ocupe o que pasen por la red 4 bytes, a que sea solo 1 byte, pero que pasaría si tuviéramos una tabla con 1 millón de filas, estaríamos almacenando en disco duro y pasando por la red 4,000,000 de bytes con el tipo de dato INT, mientras que con TINYINT solo tendríamos 1,000,000 de bytes, veamos un ejemplo de esto, la primer tabla muestra los extremos de los valores permitidos en cada columna así como si almacenáramos un 1 en cada una de ellas.
DECLARE @tab TABLE(
 colBIGINT BIGINT
 , colINT INT
 , colSMALLINT SMALLINT
 , colTINYINT TINYINT
)
INSERT INTO @tab
VALUES( -9223372036854775808 , -2147483648, -32768, 0 )
, ( 1 , 1 , 1 , 1 )
, ( 9223372036854775807 , 2147483647 , 32767 , 255 )

SELECT * FROM @tab

SELECT DATALENGTH( colBIGINT ) AS lnColBIGINT
, DATALENGTH( colINT ) AS lnColINT
, DATALENGTH( colSMALLINT ) AS lnColSMALLINT
, DATALENGTH( colTINYINT ) AS lnColTINYINT
FROM @tab

DECIMAL, NUMERIC.
Por lo general estamos acostumbrados a usar el tipo de datos FLOAT, debemos de cambiar esa costumbre y usar DECIMAL o NUMERIC, porque su precisión es más exacta que FLOAT o REAL, observen el siguiente ejemplo:
DECLARE @tab TABLE ( 
 colDECIMAL DECIMAL(20,10)
 , colNUMERIC NUMERIC(20,10)
 , colFLOAT FLOAT(10)
 , colREAL REAL
)

INSERT INTO @tab VALUES( 1234567890.0123456789, 1234567890.0123456789 , 1234567890.0123456789 , 1234567890.0123456789 )

SELECT *
, CAST( colFLOAT AS DECIMAL(20,10) )
, CAST( colREAl AS DECIMAL(20,10) )
 FROM @tab
Y para el espacio en disco duro, tenemos más amplia variedad de opciones a elegir de la cantidad de dígitos que utilizaremos en DECIMAL y NUMERIC que en FLOAT y REAL, este ultimo no es posible escoger la precisión, es equivalente a un FLOAT(24).
DECIMAL, NUMERIC.
Precisión
Bytes de almacenamiento
1 - 9
5
10-19
9
20-28
13
29-38
17

FLOAT
Valor del parámetro n
Precisión
Tamaño de almacenamiento
1-24
7 dígitos
4 bytes
25-53
15 dígitos
8 bytes

REAL = FLOAT(24)

MONEY , SMALLMONEY
De la misma manera, es posible almacenar valores tipo moneda, pero debemos poner mucha atención a la longitud que utilizaremos, ya que tenemos dos opciones similares que pueden ocupar diferente espacio en disco duro.

Tipo de datos
Intervalo
Almacenamiento
money
De -922,337,203,685,477.5808 a 922,337,203,685,477.5807
8 bytes
smallmoney
De - 214.748,3648 a 214.748,3647
4 bytes
Veamos el siguiente ejemplo donde claramente podemos observar cada uno de los tipos, así como el espacio en disco duro utilizado.
DECLARE @tab TABLE(
 colMONEY MONEY
 , colSMALLMONEY SMALLMONEY
)

INSERT INTO @tab
VALUES( 123456789123456.7891 , 123456.7891 )
, ( 1.1 , 1.1 )
, ( -123456789123456.7891 , -123456.7891 )

SELECT * FROM @tab

SELECT DATALENGTH( colMONEY ) AS lnColMONEY
, DATALENGTH( colSMALLMONEY ) AS lnColSMALLMONEY
FROM @tab


TIPOS DE DATOS FECHA
En este rubro, tenemos varios tipos los cuales debemos conocer para saber cual debemos utilizar de acuerdo al rango que estemos manejando, veamos los tipos y el espacio ocupado en disco duro.

DATE
El rango es de 0001-01-01 a 9999-12-31 con un espacio en disco duro de 3 bytes, no importa la fecha que se utilice de ese rango:
DECLARE @tab TABLE( 
 colDATE DATE )

INSERT INTO @tab
VALUES( '00010101' ) 
, ( CAST( GETDATE() AS DATE ) ) 
, ( '99991231' )

SELECT *, DATALENGTH( colDATE ) AS lnColDate
 FROM @tab 

DATETIME
El rango es de 1753-01-01 00:00:00.000 a 9999-12-31 23:59:59.997 con un espacio en disco duro de 8 bytes, no importa la fecha que se utilice del rango:
DECLARE @tab TABLE( 
 colDATETIME DATETIME )

INSERT INTO @tab
VALUES( '17530101 00:00:00.000' ) 
, ( GETDATE() ) 
, ( '99991231 23:59:59.997' )

SELECT *, DATALENGTH( colDATETIME ) AS lnColDateTime
 FROM @tab


DATETIME2
El rango es un poco más amplio y además la precisión de los decimales es mayor, acepta un parámetro que define este ultimo dato que es de 0 a 7 digitos, el rango es de de 0001-01-01 00:00:00.0000000 a 9999-12-31 23:59:59.9999999 con un espacio en disco duro que varía de acuerdo a la precisión establecida, si no establecemos una precisión, el default es 7 por lo tanto ocupará 8 bytes:
Precision
Almacenamiento
0-2
6 bytes
3-4
7 bytes
5-7
8 bytes
Veamos un ejemplo de lo que es posible almacenar cada tipo de dato y los bytes que utiliza:
DECLARE @tab TABLE( 
 colDATETIME2SinPrecision DATETIME2
 ,colDATETIME20 DATETIME2(0)
 ,colDATETIME21 DATETIME2(1)
 ,colDATETIME22 DATETIME2(2) 
 ,colDATETIME23 DATETIME2(3)
 ,colDATETIME24 DATETIME2(4)
 ,colDATETIME25 DATETIME2(5)
 ,colDATETIME26 DATETIME2(6)
 ,colDATETIME27 DATETIME2(7)
)

INSERT INTO @tab
VALUES( '00010101 00:00:00.1234567'
 , '00010101 00:00:00.1234567'
 , '00010101 00:00:00.1234567'
 , '00010101 00:00:00.1234567'
 , '00010101 00:00:00.1234567'
 , '00010101 00:00:00.1234567'
 , '00010101 00:00:00.1234567'
 , '00010101 00:00:00.1234567'
 , '00010101 00:00:00.1234567'
 ) 
 , ( CAST( GETDATE() AS DATETIME2 )
 , CAST( GETDATE() AS DATETIME2(0) )
 , CAST( GETDATE() AS DATETIME2(1) )
 , CAST( GETDATE() AS DATETIME2(2) )
 , CAST( GETDATE() AS DATETIME2(3) )
 , CAST( GETDATE() AS DATETIME2(4) )
 , CAST( GETDATE() AS DATETIME2(5) )
 , CAST( GETDATE() AS DATETIME2(6) )
 , CAST( GETDATE() AS DATETIME2(7) )
 ) 
 , ( '99991231 23:59:59.9999999'
 , '99991231 23:59:59.9999999'
 , '99991231 23:59:59.9999999'
 , '99991231 23:59:59.9999999'
 , '99991231 23:59:59.9999999'
 , '99991231 23:59:59.9999999'
 , '99991231 23:59:59.9999999'
 , '99991231 23:59:59.9999999'
 , '99991231 23:59:59.9999999'
 ) 

SELECT * FROM @tab 

SELECT DATALENGTH( colDATETIME2SinPrecision ) AS lncolDATETIME2SinPrecision0
, DATALENGTH( colDATETIME20 ) AS lnColDATETIME20
, DATALENGTH( colDATETIME21 ) AS lnColDATETIME21
, DATALENGTH( colDATETIME22 ) AS lnColDATETIME22
, DATALENGTH( colDATETIME23 ) AS lnColDATETIME23
, DATALENGTH( colDATETIME24 ) AS lnColDATETIME24
, DATALENGTH( colDATETIME25 ) AS lnColDATETIME25
, DATALENGTH( colDATETIME26 ) AS lnColDATETIME26
, DATALENGTH( colDATETIME27 ) AS lnColDATETIME27
FROM @tab


DATETIMEOFFSET
Como aclaración, no explicaré el funcionamiento, solo la manera en que podemos ahorrar espacio: de la misma manera que DATETIME2, usando una menor precisión, solo que este tipo de datos ocupará un poco más de espacio porque también almacena la zona horaria proporcionada, veamos el ejemplo y podremos darnos cuenta de los bytes que almacena de acuerdo a la precisión, recuerden si no utilizan el parámetro de precisión el default será 7 y por lo tanto estarán ocupando el mayor espacio posible para el tipo de dato:
DECLARE @tab TABLE( 
 DATETIMEOFFSETSinPrecision DATETIMEOFFSET
 ,DATETIMEOFFSET0 DATETIMEOFFSET(0)
 ,DATETIMEOFFSET1 DATETIMEOFFSET(1)
 ,DATETIMEOFFSET2 DATETIMEOFFSET(2) 
 ,DATETIMEOFFSET3 DATETIMEOFFSET(3)
 ,DATETIMEOFFSET4 DATETIMEOFFSET(4)
 ,DATETIMEOFFSET5 DATETIMEOFFSET(5)
 ,DATETIMEOFFSET6 DATETIMEOFFSET(6)
 ,DATETIMEOFFSET7 DATETIMEOFFSET(7)
)


INSERT INTO @tab
VALUES( '00010101 00:00:00.1234567'
 , '00010101 00:00:00.1234567'
 , '00010101 00:00:00.1234567'
 , '00010101 00:00:00.1234567'
 , '00010101 00:00:00.1234567'
 , '00010101 00:00:00.1234567'
 , '00010101 00:00:00.1234567'
 , '00010101 00:00:00.1234567'
 , '00010101 00:00:00.1234567'
 ) 
 , ( CAST( GETDATE() AS DATETIMEOFFSET )
 , CAST( GETDATE() AS DATETIMEOFFSET(0) )
 , CAST( GETDATE() AS DATETIMEOFFSET(1) )
 , CAST( GETDATE() AS DATETIMEOFFSET(2) )
 , CAST( GETDATE() AS DATETIMEOFFSET(3) )
 , CAST( GETDATE() AS DATETIMEOFFSET(4) )
 , CAST( GETDATE() AS DATETIMEOFFSET(5) )
 , CAST( GETDATE() AS DATETIMEOFFSET(6) )
 , CAST( GETDATE() AS DATETIMEOFFSET(7) )
 ) 
 , (  '99991231 23:59:59.9999999'
 , '99991231 23:59:59.9999999'
 , '99991231 23:59:59.9999999'
 , '99991231 23:59:59.9999999'
 , '99991231 23:59:59.9999999'
 , '99991231 23:59:59.9999999'
 , '99991231 23:59:59.9999999'
 , '99991231 23:59:59.9999999'
 , '99991231 23:59:59.9999999'
 ) 

SELECT * FROM @tab 

SELECT DATALENGTH( DATETIMEOFFSETSinPrecision ) AS lnDATETIMEOFFSETSinPrecision
, DATALENGTH( DATETIMEOFFSET0 ) AS lnDATETIMEOFFSET0
, DATALENGTH( DATETIMEOFFSET1 ) AS lnDATETIMEOFFSET1
, DATALENGTH( DATETIMEOFFSET2 ) AS lnDATETIMEOFFSET2
, DATALENGTH( DATETIMEOFFSET3 ) AS lnDATETIMEOFFSET3
, DATALENGTH( DATETIMEOFFSET4 ) AS lnDATETIMEOFFSET4
, DATALENGTH( DATETIMEOFFSET5 ) AS lnDATETIMEOFFSET5
, DATALENGTH( DATETIMEOFFSET6 ) AS lnDATETIMEOFFSET6
, DATALENGTH( DATETIMEOFFSET7 ) AS lnDATETIMEOFFSET7
FROM @tab

TIME
De la misma manera, este tipo de dato usa un parámetro que es la precisión de hasta 7 decimales, si no utilizamos el parámetro, el default es 7 lo cual ocupa un espacio de 5 bytes, si no lo ocupan, en nuestro diseño de base de datos establezcamos la precisión a 0, veamos el ejemplo:
DECLARE @tab TABLE( 
 TIMESinPrecision TIME
 ,TIME0 TIME(0)
 ,TIME1 TIME(1)
 ,TIME2 TIME(2) 
 ,TIME3 TIME(3)
 ,TIME4 TIME(4)
 ,TIME5 TIME(5)
 ,TIME6 TIME(6)
 ,TIME7 TIME(7)
)


INSERT INTO @tab
VALUES( '00010101 00:00:00.1234567'
 , '00010101 00:00:00.1234567'
 , '00010101 00:00:00.1234567'
 , '00010101 00:00:00.1234567'
 , '00010101 00:00:00.1234567'
 , '00010101 00:00:00.1234567'
 , '00010101 00:00:00.1234567'
 , '00010101 00:00:00.1234567'
 , '00010101 00:00:00.1234567'
 ) 
 ,( CAST( GETDATE() AS TIME )
 , CAST( GETDATE() AS TIME(0) )
 , CAST( GETDATE() AS TIME(1) )
 , CAST( GETDATE() AS TIME(2) )
 , CAST( GETDATE() AS TIME(3) )
 , CAST( GETDATE() AS TIME(4) )
 , CAST( GETDATE() AS TIME(5) )
 , CAST( GETDATE() AS TIME(6) )
 , CAST( GETDATE() AS TIME(7) )
 ) 
 , ( '99991231 23:59:59.9999999'
 , '99991231 23:59:59.9999999'
 , '99991231 23:59:59.9999999'
 , '99991231 23:59:59.9999999'
 , '99991231 23:59:59.9999999'
 , '99991231 23:59:59.9999999'
 , '99991231 23:59:59.9999999'
 , '99991231 23:59:59.9999999'
 , '99991231 23:59:59.9999999'
 ) 

SELECT * FROM @tab 

SELECT DATALENGTH( [TIMESinPrecision] ) AS lnTIMESinPrecision
, DATALENGTH( TIME0 ) AS lnTIME0
, DATALENGTH( TIME1 ) AS lnTIME1
, DATALENGTH( TIME2 ) AS lnTIME2
, DATALENGTH( TIME3 ) AS lnTIME3
, DATALENGTH( TIME4 ) AS lnTIME4
, DATALENGTH( TIME5 ) AS lnTIME5
, DATALENGTH( TIME6 ) AS lnTIME6
, DATALENGTH( TIME7 ) AS lnTIME7
FROM @tab


A partir de las versiones más recientes de SQL SERVER ya tenemos muchos más tipos de datos que podemos utilizar para ahorrar espacio no solo en disco duro si no también la cantidad de información que se transmitirá por la red, la cantidad de información que procesará nuestro servidor etcétera, por lo que les recomiendo ampliamente que tengan especial cuidado al momento de elegir dichos tipos de dato, sobre todo nunca elijan un tipo de dato que tenga una precisión default, sabiendo que nunca utilizarán dicha precisión en la información que pueden establecer a 0 para ocupar la menor cantidad de bytes.

Espero les sirva en su próximo diseño de tablas.

SALUDOS.
COMPARTE ESTA INFORMACION SI TE PARECIO INTERESANTE

0 comentarios:

Publicar un comentario