SQL Server 2008, tips an tricks. Miguel Egea Charla en video en el CodeCamp 2009

Posted by Miguel Egea | Posted in Relacional | Posted on 18-01-2010

3

SQL Server 2008 incorpora nuevas funcionalidades para dar soporte a las tecnologías que demanda la sociedad; en esta linea, veremos cómo SQL Server hace la vida más fácil gestionando localizaciones geográficas y puntos en el espacio; además veremos cómo SQL Server nos ayuda a gestionar más eficientemente videos, documentos, y archivos de gran tamaño.

Usando vistas para crear subconjuntos de datos

Posted by Miguel Egea | Posted in Relacional | Posted on 16-01-2010

0

La verdad es que he usado esta técinca bastante, lo que hago es crear vistas, en una base de datos que apunten a las tablas o vistas originales en la otra base de datos. De esta forma no se duplica el espacio, y además no se penaliza (apenas) el rendimiento, en principio la única operación adicional que hace el motor frente a que estén en la misma base de datos es comprobar que el usuario tenga permisos en la otra base de datos. En resumen, no es malo crear una vista en una base de datos apuntando a otra en el mismo server, las operaciones CRUD (Inserción, lectura, actualización y borrado) segurian funcionando todas como si de una tabla se tratase.

Sin embargo si quieres crear un gran número de vistas, la cosa puede ser tediosa. Si sé que por ejemplo voy a crear vistas para todas las tablas de un esquema, o para todas las que cumplen ciertos patrones, puedo utilizar otra técnica que exploto de vez en cuando, crear una consulta que me devuelva las consultas a ejecutar, para despues de esto copiar y pegar.

Si las operaciones a hacer son CRUD, no hay problema separandolas con ; todo funciona perfectamente, sin embargo si las operaciones a realizar son operaciones de definición de objetos (sentencias DDL), entonces hay que sepaararlas por GO para ejecutarlas en el entorno, o bien, hay que ejecutarlas una a una, esto, tampoco es algo que yo desee. ¿como hago para añadir los GO?. Las técnicas que uso habitualmente son dos una añadir al final de la sentencia el caracter 13 y el caracter 10 . Algo así

select 'select 1 ' +char(10) +CHAR(13) + 'go'

Sin embargo este código no funciona en el modo Grid, habitual de management estudio, si copiamos desde el grid el código y lo pegamos, nos aparecerá en una sola línea y no en dos, que es lo que buscamo,s así pues hemos de pasara a modo texto (pulsando CTRL+to bien en el menú Query->Results To->Text. La verdad es que queda algo “chapuza” esta alternativa.

¿alguna otra solución? Pues lo que tendríamos que hacer es además de construir la sentencia, añadir tantos “GO” como sentencias haya… eso es sencillo, si la primera linea es :

select  'CREATE VIEW ' + TABLE_SCHEMA+'.['+TABLE_NAME  +  '] AS select * from vdnav60.'+table_schema+'.['+table_name+'];'  from information_Schema.tables where table_name like '%patron%'

Lo que hemos de hacer es un select, pero esta vez solo con el literal “GO” del mismo origen, de esta forma tendremos el mismo número de “go”. Sería algo así

select  'GO'  from information_Schema.tables where table_name like '%patron%'

El problema aquí viene por dos partes, la primera, las dos sentencias no son una sola, que es lo que yo necesito, (sin embargo ambas solo devuelven una columna, que es un literal con lo que sirve como código). Esto podemos solucionarlo usando una cláusula UNION ALL (al ser la estrutura devuelta igual).

Pero esto aún encierra un problema adicional, que es que no vienen en el orden adecuado, para ordenar algo podemos usar la cláusula order by, pero no tenemos ningún campo que nos sirva para hacer ese entrelazado (o al menos no podemos suponer que lo tenemos), es por ello que yo uso este otro script.

select  'CREATE VIEW ' + TABLE_SCHEMA+'.['+TABLE_NAME  +  '] AS select * from vdnav60.'+table_schema+'.['+table_name+'];'  from information_Schema.tables where table_name like '%patron%'
union all
select row_number() over( order by table_name), 'go' from vdnav60.information_Schema.tables where table_name like '%patron%'

Las cláusulas Row_number() nos van a devolver el número de fila según los criterios que usemos (partition by y order by), y luego como las dos tablas debieran devolver lo mismo, el resultado es que ordenando por ese número de fila, obtendremos la consulta perfectamente compuesta , como puede verse en la imagen

Disfrutad el código

Descubrir bloqueos en SQL Server

Posted by Miguel Egea | Posted in Relacional | Posted on 08-01-2010

4

Los bloqueos son la principal causa de bajo rendimiento inexplicable, y generalmente son muy complicados de cazar, de detectar y de solucionar, hablaremos de bloqueos más adelante, pero ahora mismo vamos a poner un procedimiento almacenado que sirve para cazar a los procesos bloqueadores y pondremos una imagen en la que se ve cual es el resultado, en proximos capítulos pondremos como configurar el SQL Mail para que además nos lleguen por correo electrónico

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
-- ***************************************************************************
-- Copyright (C) PortalSQL 2006
--
-- Fichero: CazaBloqueadores.sql
-- Descr.: Busca bloqueos que se mantengan
-- durante el tiempo que le pasemos por parámetro, si hay bloqueos que se mantienen
-- muestra información relevante
-- autor: Miguel Egea Gómez -- Solid Quality Learning IberoAmericana
--
-- comentarios: Puede ser adecuado meter ejecutar este procedimiento desde un job y
-- usar DatabaseMail para reportar sus resultados al administrador de base
-- de datos.
-- He usado la vista de compatibilidad sys.sysprocesses, esto es porque he c
-- comprobado que algunas ocasiones la tabla sys.dm_exec_request no tiene registros
-- para los bloqueadores.
--
-- Parámetros de entrada:
-- @waittime --> Tiempo que debe esperarse para considerar que hay un bloqueo
-- Valores de retorno : 0--> Funcionó de forma correcta
-- 1--> En cualquir otro caso
--
-- <!--(más reciente más arriba) -->
-- yyyy/mm/dd by description
-- ========== ======= ==========================================================
-- 2006/06/04 MEG Creación.
--
-- ***************************************************************************
-- Ejemplo de llamada
--
/*
DECLARE @Valor varchar(10) ,@result int
SET @Valor='00:00:02'
EXEC @result =CazaBloqueadores @valor
select @result</code>
 
*/ 
 
CREATE PROC CazaBloqueadores @waittime varchar(10)
as
begin
begin try
declare @t table (bloqueado int, bloqueador int)
-- usaremos la variable de tipo tabla para esperar
insert into @t (bloqueado,bloqueador)
select session_id, blocking_session_id
from sys.dm_exec_requests der
where blocking_session_id!=0
-- Recogemos los objetos que están bloqueados..
waitfor delay @waittime
SELECT der.session_id [Sesion bloqueada],
det.text [Comando Bloqueado],
der.[blocking_Session_id] [id del bloqueador],
subq.TEXT [Comando Bloqueador],
des.[host_name] [Nombre del host],
des.[Program_name] [Aplicación],
db_name(der.database_id) [Base de Datos],
user_name(der.user_id) [usuario de bbdd],
des.login_name [login],
cast(des.context_info as varchar(100)) [Informacion Adicional],
des.cpu_time [Tiempo de CPU],
des.total_elapsed_time [Tiempo de espera],
des.logical_reads [lecturas],
des.writes [escrituras],
der.start_time [Comenzó],
der.status [estado],
case des.transaction_isolation_level
when 0 then 'Sin especificar'
when 1 then 'Read Uncommitted'
when 2 then 'Read Committed'
when 3 then 'Repeatable reads'
when 4 then 'Serializable'
when 5 then 'Snapshot'
else 'Inesperado'
end [Nivel de Aislamiento]
FROM sys.dm_exec_requests der
INNER JOIN sys.dm_exec_sessions des
ON der.session_id=des.session_id
CROSS APPLY sys.dm_exec_sql_text(sql_handle) det
left join (select SPID session_id, det2.[text]
from sys.sysprocesses der2
CROSS APPLY sys.dm_exec_sql_text(sql_handle) det2
) subq on der.[blocking_Session_id]=subq.session_id
WHERE der.blocking_session_id
in (select bloqueador from @t)
return 0
end try
begin catch
declare @error sysname
set @error=ERROR_MESSAGE()
raiserror (@error,16,1)
return 1
end catch
end 

Aquí puedes ver una imagen en la que se ejecuta un comando que genera un bloqueo, y un comando que se ve bloqueado por ese bloqueo, cuando se crea una tabla dentro de una transacción se genera un bloqueo, un select que consulte esa tabla espera a que se haga commit o rollback en la creación, para devolver los registros o un error. 

a continuación puedes ver más resultados devueltos por el procedimiento almacenado

Disfruta del código

Como reduzco mi log de transacciones

Posted by Miguel Egea | Posted in Relacional | Posted on 05-01-2010

5

Si te haces esta pregunta estás ante lo más común cuando comienzas a administrar SQL, la respuesta fácil sería darte dos comandos, y que luego funcionen o no. La verdad no me interesa eso en absoluto, creo que debes entender como funciona el log para saber porque crece, así entenderás porque aunque hagas backups todas las noches tu log sigue creciendo, y además, y mucho mejor sabrás como evitar que crezca si no tiene que hacerlo.

Para comenzar, que el log crezca depende directamente del modo de recuperación que tenga tu base de datos, solo en los modos FULL y BULK LOGGED crecerá sin medida (también puede crecer en el modo simple, es mucho más raro pero puede suceder, en 3 lineas veremos porqué).

El modo de recuperación de la bbdd puedes verlo en las propiedades (SQL Server Management Studio, te conectas al relacional, propiedades.. y en la pesstaña opciones aparece el modo de recuperación actual y en el desplegable el resto de modos. Yo tengo el cliente en inglés, así que disculpadme pero lo vereis en inglés

 

 El log de transacciones se encarga de registrar una tras otras de forma secuencial todas las modificaciones que se producen en la base de datos, esto es, la información que después ha de consolidarse en las tablas.  De esta forma el fichero MDF  (o los NDF’s ) no tienen porque contenter toda la información actualizada en un momento dado. Así pues, hagais lo que hagais NO BORREIS EL FICHERO DE LOG, que además de ser una barvaridad, os hará seguramente tener inconsistencias en la base de datos. Es más no se puede recuperar sin un comando totalmente indocumentado que usa soporte de microsoft.

Si es una estructura secuencial, y registra los cambios ¿debe crecer infinitamente?, la respuesta es depende, depende del modo de recuperación , si estamos en modo simple, en cuanto un proceso de baja prioridad que tiene SQL consolida la información del log en los datos pasa y salvaguarda los datos, esa información se puede sobre-escribir. ¿hay excepciones? SI, no se puede sobreescribir si hay una transacción antigua abierta, es decir si alguien hace, BEGIN TRAN, Update Tabla SET Campo=VAlor WHERE condicion, y se olvida de ejecutar el COMMIT TRAN o el ROLLBACK, sql no puede rehusar el trozo de log, porque no sabe que va a pasar con esa transacción y por tanto el fichero seguirá creciendo…  Si el modo de recuperación es sencillo se puede reaprovechar el log, como acabamos de ver, sin embargo, si algo falla, solo podremos recuperar el último backup completo (ningún backup del log puede garantizar que no se hallan reaprovechado trozos, y por tanto son inútiles, tanto que SQL no deja ni hacerlos :) ).

En el modo de recuperación completo y bulk logged solo se reaprovecharán estos trozos, si además de no quedar transacciones antiguas abiertas, se ha hecho copia de seguridad DEL LOG, es decir no BACKUP DATABASE sino BACKUP LOG.

Así pues ¿porque crece mi log de transacciones?,

Puede ser porque : Tienes una transacción muy antigua abierta (compruebalo con DBCC OPENTRAN en la BBDD que quieras hacer la operación), el comando devolverá algo así

Transaction information for database ‘AdventureWorks2008′.

Oldest active transaction:

SPID (server process ID): 53

UID (user ID) : -1

Name : user_transaction

LSN : (33:452:2)

Start time : Jan 5 2010 1:34:24:870AM

SID : 0×0105000000000005150000006ae2268ca3bb996b6e0e17c6e8030000

DBCC execution completed. If DBCC printed error messages, contact your system administrator.

 Si no es esto ni la hubo, puede ser porque no hayas hecho nunca copias de seguridad del log de transacciones, en cuyo caso no se reaprovecha el trocito del log.

Bueno, ya sabes porque crece, pero ya ha crecido y ahora ¿como lo reduzco?.

Bueno, el tamaño del log depende del uso de la BBDD así que no hay una regla fija, pero vamos entre el 5 y el 40% de la BBDD es “aceptable” más es muy grande, y menos es muy raro, lo normal entre el 10% y el 15%, aunque insisto, que esto no te obsesione, que no hay reglas fijas.

Si tu tamaño es demasiado grande, reducirlo puede ser una tarea sencilla, prueba los siguientes pasos :

1.- Cambia a modo de recuperación Simple

2.- Ve al menú shrink y reduce el fichero de la bbdd.

¿puede que no se reduzca ni aún así? Si, puede que no se reduzca, el log es una estructura circular y no quita espacio del principio del fichero, sino solo del final así pues lo suyo es que reorganize las páginas antes.

Con esto en principio debiera reducirse, ahora, para que no vuelva a crecer, recuerda, o bien modo de recuperación sencillo (tendrá el tamaño de la transacción abierta que más haya durado y en la que concurrentemente más cosas se hayan hecho) o bien modo completo pero con copias de seguridad frecuentes. (varias al dia es lo habitual).

Saludos

Usando CTE’s para construir la dimensión de tiempo 3/3

Posted by Miguel Egea | Posted in Analisys Services, Relacional | Posted on 27-12-2009

1

Empezaremos este tercer artículo de la serie simplemente creando una tabla que alojará nuestros datos de tiempo en distintos idiomas, una vez rellenada esta tabla, puede servirnos perfectamente de base para la creación de una dimensión tiempo, que será el siguiente artículo, en el que comenzaremos a crear el proyecto de Analisys Services para construir la dimensión tiempo.

La tabla contiene la estrucutra básica para contener todos los datos necesarios relacionados con el tiempo , y además tiene la versión en castellano, francés e ingles de estos datos

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
CREATE TABLE [Dimensiones].[Tiempo](
[FechaKey] [int] not NULL primary key ,
[Fecha] [datetime] NULL,
[Año] [int] NULL,
[Numero_Mes] [int] NULL,
[Mes_Castellano] [nvarchar](30) NULL,
[Mes_año_Castellano] [nvarchar](35) NULL,
[MesKey] [int] NULL,
[Semana] [int] NULL,
[SemanaKey] [int] NULL,
[Fecha_Castellano] [nvarchar](52) NULL
[TipoDia] [varchar](13) NOT NULL,
[DiaDelAño] [int] NULL,
[DiaDeLaSemana] [int] NULL,
[DiaDelMes] [int] NULL,
[Dia_castellano] [nvarchar](30) NULL,<code>[Mes_Inglés] [nvarchar](30) NULL,
[Mes_año_Inglés] [nvarchar](35) NULL,
[Fecha_Inglés] [nvarchar](52) NULL,
[Dia_Inglés] [nvarchar](30) NULL,
 
[Mes_Francés] [nvarchar](30) NULL,
[Mes_año_Francés] [nvarchar](35) NULL,
[Fecha_Francés] [nvarchar](52) NULL,
[Dia_Francés] [nvarchar](30) NULL
 
) ON [PRIMARY]
 
GO

Una vez que tenemos la tabla tenemos que rellenarla para ello usaremos el mismo código que la vista del artículo anterior. En este caso si podemos utilizar las cláusulas que hablamos en el primer artículo SET DATE FIRST y SET LANGUAGE, de forma que todo esté adecuadamente configurado cuando hagamos los inserts y los updates corresponeientes. Veamnos como rellenar la tabla con las fechas que deseamos.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
set language spanish;
set datefirst 1;
insert into dimensiones.tiempo
(FechaKey, Fecha, Año, Numero_Mes, Mes_Castellano, Mes_año_Castellano, MesKey, Semana, SemanaKey, Fecha_Castellano,
TipoDia, DiaDelAño, DiaDeLaSemana, DiaDelMes, Dia_castellano)
select
CAST(CONVERT(varchar(10),fecha + id ,112) as int) FechaKey,
fecha+id as Fecha,
YEAR(fecha+ID) as año,
MONTH(fecha+id) as Numero_Mes,
DATENAME(month,fecha+id) Mes_Castellano,
DATENAME(month,Fecha + id ) +' '+ cast(YEAR(Fecha + id ) as varchar(4)) Mes_año_Castellano,
year(Fecha + id )*100+month(Fecha + id ) MesKey,
DATEPART(DW,Fecha + id ) Semana,
year(Fecha + id )*100 + DATEPART(DW,Fecha + id ) SemanaKey,
cast(DAY(Fecha + id ) as varchar(10)) + ' de ' + DATENAME(MM,Fecha + id ) + ' de ' + cast(YEAR(Fecha + id ) as varchar(4)) Fecha_Castellano,
case when datepart(weekday,Fecha + id )&gt;5 then 'Fin de semana' else 'Semana' end TipoDia,
datepart(dayofyear,Fecha + id ) DiaDelAño,
DATEPART(DW,Fecha + id ) DiaDeLaSemana,
DATEPART(dd,Fecha + id ) DiaDelMes,
DATENAME(dw,Fecha + id ) Dia_castellano</code>
 
from (select CAST('19991231' as datetime) fecha ) tabla
cross join nums;

Con este primer insert tendremos rellena la mayor parte de la tabla y los descriptores en castellano, sin embargo, aún no tenemos rellenados los campos en inglés y francés que pretendemos tener completos. Veamos el código para actualizar la parte en inglés

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
set language english;
 
update Dimensiones.Tiempo 
set Fecha_Inglés=a.Fecha_Inglés,
   Dia_Inglés=a.Dia_ingles,
   Mes_año_Inglés=a.Mes_año_Inglés,
   Mes_Inglés=a.Mes_Inglés 
from 
 (  select 
  CAST(CONVERT(varchar(10),fecha  + id ,112) as int) FechaKey,
  DATENAME(month,fecha+id) Mes_Inglés,
  DATENAME(month,Fecha + id ) +' '+  cast(YEAR(Fecha + id ) as varchar(4)) Mes_año_Inglés,
  DATENAME(MM,Fecha + id ) ++ ' ' +cast(DAY(Fecha + id ) as varchar(10)) +  ', ' + cast(YEAR(Fecha + id ) as varchar(4)) Fecha_Inglés,
  DATENAME(dw,Fecha + id ) Dia_ingles
 
  from (select CAST('19991231' as datetime) fecha ) tabla 
      cross join nums ) a inner join Dimensiones.Tiempo on a.FechaKey=Dimensiones.Tiempo.FechaKey

En francés tendremos que hacer algo parecido, simplemente hay que elegir de nuevo el idioma.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
set language français;
update Dimensiones.Tiempo 
set Fecha_Francés=a.Fecha_Francés,
   Dia_Francés=a.Dia_ingles,
   Mes_año_Francés=a.Mes_año_Francés,
   Mes_Francés=a.Mes_Francés 
from 
 (  select 
  CAST(CONVERT(varchar(10),fecha  + id ,112) as int) FechaKey,
  DATENAME(month,fecha+id) Mes_Francés,
  DATENAME(month,Fecha + id ) +' '+  cast(YEAR(Fecha + id ) as varchar(4)) Mes_año_Francés,
  DATENAME(MM,Fecha + id ) ++ ' ' +cast(DAY(Fecha + id ) as varchar(10)) +  ', ' + cast(YEAR(Fecha + id ) as varchar(4)) Fecha_Francés,
  DATENAME(dw,Fecha + id ) Dia_ingles
 
  from (select CAST('19991231' as datetime) fecha ) tabla 
      cross join nums ) a inner join Dimensiones.Tiempo on a.FechaKey=Dimensiones.Tiempo.FechaKey

Espero que os sirva el código.

Usando CTE’s para crear tablas de tiempo 2/?

Posted by admin | Posted in Relacional | Posted on 26-12-2009

0

Siguiendo con nuestra serie, hoy vamos a ver otro punto de vista, aún seguiremos usando CTE’s aunque el objetivo final es bien diferente, el objetivo es simplemente rellenar una tabla con números que después usaremos desde una vista, de esta forma podríamos usar esta vista para crear una dimensión de tipo tiempo. Esto es al fin y al cabo el objetivo de esta serie de artículos. Para hacerlo vamos a necesitar una tabla con los números del 1 al 10.000 y ahí es donde entra a jugar nuestra CTE, es una bastante típica, solamente que esta vez la usaremos para insertar esos diez mil registros. Vamos a ver un poquito de código que sirve para esto:



create table nums(id int not null primary key);
with mycte(id) as  (
 select 1 id
  union all 
select id+1 from mycte where id<10000  )
insert into nums (id)
select id from mycte option(maxrecursion 0);

 

 

Una vez que tenemos este código listo y por tanto tenemos nuestra tabla nums que contiene los valores del 1 al 10.000 vamos a usarla en una vista, la idea es bien sencilla, lo que haremos será elegir una fecha de inicio y sumar el valor de id, eso nos sumará el número de dias que estamos buscando. De esta forma tendremos 10.000 fechas disponibles lo que son haciendo una simple división más de 27 años. La idea de la vista es hacer join de una select que solo devolverá la fecha de inicio con la tabla de nums, obviamente no tenemos cláusula por la que unir pero eso no es problema, podemos usar un CROSS JOIN que no necesita claúsula ON. Veamos el código que nos servirá de base para nuestra vista.


SELECT

�

FECHA+id as Fecha FROM ( SELECT CAST('20000101' AS DATETIME) FECHA) t CROSS JOIN nums 

Esta consulta devuelve lo que puedes ver en esta imagen:

Esta es la idea obviamente, pero necesitamos  construir una vista que nos devuelva un resultado similar al que obteniamos en el artículo 1 de esta serie, la connotación aquí es ligeramente distinta porque no podemos asegurar en que valor está establecido el set datefirst ni el valor del lenguaje, por lo que resulta poco predecible,  aún así aquí teneis el código y una imagen del resultado…


CREATE

 

VIEW DimTiempo as 
select Fecha, 
CAST(CONVERT(varchar(10),fecha,112) as int) FechaKey,
YEAR(Fecha) as Año,
MONTH(Fecha) as Numero_Mes,
DATENAME(month,fecha) Mes_Castellano,
DATENAME(month,fecha) +' '+ cast(YEAR(fecha) as varchar(4)) Mes_ao_Castellano,
year(fecha)*100+month(fecha) MesKey,
DATEPART(DW,fecha) Semana,
year(FEcha)*100 + DATEPART(DW,fecha) SemanaKey,
cast(DAY(fecha) as varchar(10)) + ' de ' + DATENAME(MM,fecha) + ' de ' + cast(YEAR(fecha) as varchar(4)) Fecha_Castellano,
case when datepart(weekday,fecha)>5 then 'Fin de semana' else 'Semana' end TipoDia,
datepart(dayofyear,fecha) DiaDelAño,
DATEPART(DW,fecha) DiaDeLaSemana,
DATEPART(dd,fecha) DiaDelMes,
DATENAME(dw,fecha) Dia_castellano 
from 
(select CAST('20000101' as datetime) fecha ) tabla cross join nums

Esta vista devuelve el resultado que podeis ver en la siguiente imagen.

En la siguiente parte de esta serie, materializaremos esta vista en una tabla y pondremos tres idiomas dentro de nuestra tabla de tiempo, también generaremos un procedimiento almacenado para que todo esto se vea relleno. Al final de la serie pondremos una serie de artículos colgaré todos los script para que sirvan de referencia.

Usando CTE’s para crear tablas de tiempo 1/?

Posted by admin | Posted in Relacional | Posted on 26-12-2009

0

En el artículo anterior usamos la funcionalidad introducida en SQL 2005 de las CTE’s , Vimos como crear un contador sin utilizar while, ni cursores, ahora usaremos la misma técnica para simular una dimensión de tipo tiempo para un datawarehouse, lo que vamos a usar básicamente es la CTE para crear un incremental de fecha y las funciones de fecha para construir todas las variaciones que vamos a necesitar. Las variaciones son bastante auto-descriptivas, simplemente recordar que las funciones datepart y date-name nos ayudan mucho.
También llamar la atención sobre la funcionalidad set datefirts , el número que ponemos después influye en el número que devuelve el datepart(wd,..), es decir, si queremos distinguir los sábados y los domingos debemos saber exactamente cual es el dia en el que comienza la semana, de ahí que nos aseguremos poniendo SET DATEFIRST 1, el SET LANGUAGE, es para que los nombres salgan en castellano.   Por lo demás el código es bastante sencillo.
En los próximos articulos seguiremos abundando en esta parte, también usaremos vistas, y por fin lo que haremos, será crear una tabla y rellenarla con varios idiomas. 
Y ahora el código que es lo que importa realmente.


  
set language spanish; 
set datefirst 1;
 
   
With MiPrimeraCTE (fecha)
 as 
( select cast(‘20000101′ as datetime) Fecha
  union all
  select fecha+1 from MiPrimeraCTE where fecha < ‘20101231′
 )
 select Fecha,
 CAST(CONVERT(varchar(10),fecha,112) as int) FechaKey,
YEAR(Fecha) as Año,
MONTH(Fecha) as Numero_Mes,
DATENAME(month,fecha) Mes_Castellano,
DATENAME(month,fecha) +‘ ‘+ cast(YEAR(fecha) as varchar(4)) Mes_ao_Castellano,
year(fecha)*100+month(fecha) MesKey,
DATEPART(DW,fecha) Semana,
year(FEcha)*100 + DATEPART(DW,fecha) SemanaKey,
cast(DAY(fecha) as varchar(10)) + ‘ de ‘ + DATENAME(MM,fecha) + ‘ de ‘ + cast(YEAR(fecha) as varchar(4)) Fecha_Castellano,
case when datepart(weekday,fecha)>5 then ‘Fin de semana’ else ‘Semana’ end TipoDia,
datepart(dayofyear,fecha) DiaDelAo,
DATEPART(DW,fecha) DiaDeLaSemana,
DATEPART(dd,fecha) DiaDelMes,
DATENAME(dw,fecha) Dia_castellano
FROM MiPrimeraCTE option(maxrecursion 0) 
 
 
Â
Saludos Cordiales, Miguel Egea

Usando funciones Recursivas CTE’s. para crear secuencias

Posted by admin | Posted in AntiCursores, Relacional | Posted on 24-12-2009

0

Para distintas funcionalidades (os pondré algún ejemplo en los siguientes dias que vienen del gran Itzik )  es muy interesante tener una tabla con números del 1 al n, se pueden usar para convertir parámetros separados por comas en valores, se pueden usar para simular joins y duplicar filas, bueno, tienen un gran número de utilidades y funcionalidades.

Lo que os voy a contar hoy es como hacerlo sin recurrir a los cursores

Lo típico es utilizar una sentencia del tipo while para rellenar esos valores. Veamos un ejemplo

declare @t table (id int)

declare @num int

 set @num=0

 while @num<=10000

  begin

       insert into @t values (@num)

      set @num=@num+1

end

select * from @t

Esto no deja de ser un cursor, y ya sabeis que soy de la opinión de que el único cursor bueno es el cursor muerto y me considero miembro activo de “la brigada anticursores”.   Antes de SQL 2005, no podía sin embargo matar este cursor excepto usando triquiñuelas que nada me gustaban y que probablemente eran peor que el propio cursor :) , sin embargo con las CTE’s son sencillos de eliminar. Las CTE’s permiten recursividad, es decir yo puedo basar el resultado de un query en el resultado del anterior, esta va a ser la idea, primero declararemos la cte con la cláusula with, luego pondremos la cláusula de inicio que será un siemple SELECT 0 as id, después usaremos la CTE para incrementar de 1 en uno y luego  pondremos la cláusula de salida de la recursividad ( en el where ), el resultado es el siguiente :

With MiPrimeraCTE (id )
as
(
select 0 as id
union all 
select id+1 from MiPrimeraCTE where Id < 10000 )
select * from MiPrimeraCTE option(maxrecursion 0)

 

La única curiosidad que nos queda por resolver es la última cláusula (option maxrecursion) eso es simplemente porque el limite de llamadas recursivas en SQL Server 2005 y 2008 está en 100, (en SQL 2000 estaba en 32), y hay que prevenirse de esa limitación para no obtener un error. Para ello usamos ese Query hint. En el proximo artículo lo usaremos con fechas que también funciona y bastante bien, y veremos un par de lugares en el que nos puede resultar útil.

Gestión de acumulados

Posted by admin | Posted in Relacional | Posted on 11-12-2009

0

10 de Septiembre de 2001

Introducción

    Este ejemplo pretende dar mi opinión sobre como tratar una gestión de acumulados de almacén desde Sql-Server y Visual-Basic. El ejemplo se basa en una pregunta al grupo de noticias de Sql-Server en Castellano y pretende resolver este punto en base a , de un lado tener acumulados de compras y ventas mensuales ágiles, y de otra parte tener estos mismos datos desde un punto de vista anual con la misma agilidad.
    Es de suponer que tambien se necesite tener un control del almacén desde un punto de vista global -¿Cuantos bolígrafos tengo?-, pero eso puede conserguirse bien desde estás mismas tablas, usando sentencias de agrupamiento por el criterio que se necesite, o bien incrementando la funcionalidad de los triggers que aquí se proponen para que acumulen también esa información. La decisión dependerá siempre del rendimiento que se espere del sistema…..
    Notad que no se ha tenido en cuenta multialmacén para el ejemplo y que se ha desnormalizado la relación Albaran-Lineas al objeto de tener más accesible la fecha ya que de otra forma complicaría en exceso el código y los detalles a tener en cuenta.     Yo creo que una desnormalización no es algo intrínsecamente negativo, existen casos como este que justifican la pérdida de espacio, por la agilidad y sencillez en el desarrollo. Tambien en este caso hay motivos de rendimiento evidentes que justifican esta desnormalización

La parte de Visual-Basic
    Una imagen vale más que mil palabras así que aquí tienes la pantallita que es lo que hace, ahora bien, si quieres descargarte todo el código haz click aquí

 

La parte de Sql-Server
    El código relativo al sql-server se divide a su vez en dos partes diferentes, la primera simplemente genera la estructura de tabla que soporta nuestro ejemplo, la segunda, contiene los dos triggers, realmente casi idénticos que aportarán esos acumulados mensuales y anuales. No he tenido en cuenta todos los colores que ofrece el sql-Query-analizer ya que es un pelín pesadito hacer esto en web….
El código de creación de tablas

if not object_id(‘Stocks’) is null
drop table stocks
go
if not object_id(‘StocksAño’) is null
drop table stocksaño
go
if not object_id(‘LineasAlbaranCompra’) is null
drop table lineasAlbaranCompra
go
if not object_id(‘LineasAlbaranVenta’) is null
drop table lineasAlbaranVenta
go

CREATE TABLE Stocks (año int not null, mes int not null,articulo int not null,
cantidadComprada float, ValorComprado float,
CantidadVendida float , ValorVendido float,
stock as isnull(CantidadComprada,0)-isnull(CantidadVendida,0))
go

create table StocksAño(año int not null, articulo int not null ,
cantidadComprada float, ValorComprado float,
CantidadVendida float , ValorVendido float,
stock as isnull(CantidadComprada,0)-isnull(CantidadVendida,0))
go

alter table Stocks add constraint pk_Compra primary key (año,mes,articulo)
go
alter table Stocksaño add constraint pk_compraaño primary key (año,articulo)
go

Create table LineasAlbaranCompra(id int not null, linea int not null,
Articulo int,Fecha datetime, Cantidad Float,
Precio Float )
go
alter table LineasAlbaranCompra add constraint pk_lac primary key (id,linea)

go
Create table LineasAlbaranVenta(id int not null, linea int not null,
Articulo int,Fecha datetime, Cantidad Float,
Precio Float )
go
alter table LineasAlbaranVenta add constraint pk_lav primary key (id,linea)
go

El código del trigger
    Observad que tiene suficientes comentarios para entender perfectamente lo que hace, por otra parte en el fichero zip teneis el código completo, aquí solo pondré uno de los dos triggers, ya que son practicamente igualitos.


create trigger trg_LineaAlbaranCompra on LineasAlbaranCompra
for insert,delete,update
as
begin


– ********************************************************************** –
– PRIMERO CON LAS INSERCIONES …………………………………. –
– ********************************************************************** –
– Primero intentamos actualizar las Stocks
– si no existe la clave primaria en el registro
– El update no hará nada, si si lo tiene actualizará los valores como yo le diga…

– en un update no se puede usar la clausula group by…..
– por lo que nos vemos obligados ha agruparlos antes…..
– Si estuviesemos en ss-2000 quizá podríamos usar variables
– en lugar de tablas temporales, pero las pruebas están hechas
– contra sql-server 7.0

select year(fecha) año ,month(fecha) mes, articulo,Sum(Cantidad) Cantidad ,sum(cantidad*Precio) Total
into #i
from inserted
group by year(inserted.fecha),month(inserted.fecha),articulo
– y ahora a actualizar…

– Primero a las Stocks mensuales ….
update Stocks Set CantidadComprada=isnull(CantidadComprada,0)+Cantidad,
ValorComprado = isNull(Valorcomprado,0)+Total
from #i Nuevas inner join Stocks
on Nuevas.Articulo=Stocks.articulo and
Nuevas.año = Stocks.año and
Nuevas.mes = Stocks.mes
– y después a las anuales…, el update es el mismo….
update StocksAño Set CantidadComprada=isnull(CantidadComprada,0)+Cantidad,
ValorComprado = isNull(Valorcomprado,0)+Total
from #i Nuevas inner join StocksAño
on Nuevas.Articulo=Stocksaño.articulo and
Nuevas.año = Stocksaño.año

– Algunas tendrán que ser insertadas ya que no existe el regitro
– de ese articulo en este año-mes, Luego haremos un insert into…
– Primero mensuales ….
insert into Stocks (año,mes,articulo,CantidadComprada,ValorComprado)
select nuevas.año,nuevas.mes,Nuevas.articulo,Cantidad,Total
from #i Nuevas left join Stocks
on Nuevas.Articulo=Stocks.articulo and
Nuevas.año = Stocks.año and
Nuevas.mes = Stocks.mes
where Stocks.articulo is null
– Después anuales ..
insert into StocksAño (año,articulo,CantidadComprada,ValorComprado)
select nuevas.año,Nuevas.articulo,Cantidad,Total
from #i Nuevas left join StocksAño
on Nuevas.Articulo=StocksAño.articulo and
Nuevas.año = StocksAño.año
where StocksAño.articulo is null


– lo que hago en esta insert es tener en cuenta que un not in que es lo
– que te pide el cuerpo (Insertar la que no estén ya), es un coste muy
– alto, luego se hace un join externo que devuelve nulos con mucho menor
– coste y se exige que el campo relacionado sea nulo, esto aunque es –
– básico conviene no olvidarlo….

– ********************************************************************** –
– AHORA CON LOS BORRADOS …………………………………. –
– ********************************************************************** –
– pasa lo mismo, el update no soporta group by por lo que lo tenemos que
– hacer un poquito antes….

select year(fecha) año ,month(fecha) mes, articulo,Sum(Cantidad) Cantidad ,sum(cantidad*Precio) Total
into #d
from deleted
group by year(fecha),month(fecha),articulo

– Primero mensuales …
update Stocks Set CantidadComprada=isnull(CantidadComprada,0)- Cantidad,
ValorComprado = isNull(Valorcomprado,0)- Total
from #d Viejos inner join Stocks
on Viejos.Articulo=Stocks.articulo and
Viejos.año = Stocks.año and
viejos.mes = Stocks.mes
– Luego Anuales …
update StocksAño Set CantidadComprada=isnull(CantidadComprada,0)- Cantidad,
ValorComprado = isNull(Valorcomprado,0)- Total
from #d Viejos inner join StocksAño
on Viejos.Articulo=StocksAño.articulo and
Viejos.año = StocksAño.año

– Supongo, aunque luego la experiencia te dice que no siempre es así que
– para borrar algo primero debe haberse insertado y si se ha insertado
– ya existe, luego no hace falta insertar el – lo que sea del campo Stocks
– Digo que la experiencia dice otra cosa ya que si un usuario toca a mano
– la tabla Stocks,….. cualquier cosa podría suceder y deberiamos tenerla
– contemplada…

end

go