Categorías
Donaciones

Todo el contenido es gratuito y en beneficio de la comunidad. Puedes reconocer el esfuerzo con una donación si lo deseas.

Inserte aquí su publicidad

Archivo de la categoría ‘FAQs’

No me prodigo mucho en Reporting Services, así que cada vez que me toca pasar por allí aprendo una cuantas cosas. En mi último viaje, me pidieron algo trivial, que se enviara un listado en formato Excel por mail.

Hasta ahí, pues muy sencillo. La complicación vino cuando me pidieron que en el mail se adecuaran de forma dinámica algunas cosas, como incluir el rango de fechas del informe en el asunto y en el cuerpo del mensaje y dotar al fichero de un nombre que incluyera esas características.

Después de ver hasta dónde se podía llegar con una Data-driven subscription, comprobé que se pueden hacer cosas, pero no demasiadas, y desde luego no las que yo necesitaba. Así que hice lo que todos, porque quizá no sepa mucho de SSRS, pero sí sé buscar en Google. Además, esta necesidad es bastante simple a mi entender, con lo que contaba con que no era el primer caso en la historia del mundo mundial en el que a alguien se le ocurría que el fichero a adjuntar a un mail tuviera un nombre que no fuera el del informe.

Y me sorprendió ver que, si bien el problema era común, no había una solución simple. Me costó encontrarlo, y la alternativa no es muy ortodoxa que digamos (ni soportada) así que me pareció interesante compartirlo. Hay varias entradas similares en la web, yo partí de la siguiente: http://www.sqlservercentral.com/scripts/Reporting+Services+(SSRS)/70387/.

Esa es la pista. Hay que crear una suscripción normal y corriente. Luego encontrar el job que la lanza y modificarlo. Antes de lanzar la suscripción, se personaliza (ojo, conservando una referencia de lo que se ha cambiado):

declare @reportID uniqueidentifier, @subscriptionID uniqueidentifier, @path nvarchar(255), @body nvarchar(max), @asunto nvarchar(max)

SELECT
  @reportID  = rs.reportID
, @path = '/Listados/Listado_Semana_' + convert(char(8), dateadd(dd, -7, getdate()), 112)+ '_' + convert(char(8), dateadd(dd, -1, getdate()), 112)
, @subscriptionID = rs.SubscriptionId
,  @body = 'Se adjunta listado con todos los datos de la última semana (del ' +
  substring(convert(char(8), dateadd(dd, -7, getdate()), 112), 7, 2) + '/' +
  substring(convert(char(8), dateadd(dd, -7, getdate()), 112), 5, 2) + '/' +
  substring(convert(char(8), dateadd(dd, -7, getdate()), 112), 1, 4) + ' al ' +
  substring(convert(char(8), dateadd(dd, -1, getdate()), 112), 7, 2) + '/' +
  substring(convert(char(8), dateadd(dd, -1, getdate()), 112), 5, 2) + '/' +
  substring(convert(char(8), dateadd(dd, -1, getdate()), 112), 1, 4) + ').',
  @asunto = 'Listado de datos de la semana del ' +
  substring(convert(char(8), dateadd(dd, -7, getdate()), 112), 7, 2) + '/' +
  substring(convert(char(8), dateadd(dd, -7, getdate()), 112), 5, 2) + '/' +
  substring(convert(char(8), dateadd(dd, -7, getdate()), 112), 1, 4) + ' al ' +
  substring(convert(char(8), dateadd(dd, -1, getdate()), 112), 7, 2) + '/' +
  substring(convert(char(8), dateadd(dd, -1, getdate()), 112), 5, 2) + '/' +
  substring(convert(char(8), dateadd(dd, -1, getdate()), 112), 1, 4)
FROM
        ReportSchedule rs
            INNER JOIN subscriptions s
                ON rs.subscriptionID = s.subscriptionID
            INNER JOIN dbo.[catalog] c
                ON rs.reportID = c.itemID
WHERE
  c.[path] = '/Listados/Listado Última Semana'

update dbo.[Catalog] set [path] = @path where itemID = @reportID

update subscriptions set
   ExtensionSettings = 
      replace(replace(cast(ExtensionSettings as nvarchar(max)), 'Se adjunta listado con todos los datos de la última semana.', @body), '@ReportName', @asunto)
where SubscriptionID = @subscriptionID

exec dbo.AddEvent @EventType='TimedSubscription', @EventData=@subscriptionID

Luego añadimos un segundo paso al job para dejar las cosas como estaban (yo le he añadido un delay de 1 minuto, que seguramente se pueda quitar, es para asegurarse de que el informe sale antes de volver a dejar cambiados los datos otra vez). Así, el segundo paso de ese job sería más o menos así:

waitfor delay '00:01'

--Ahora lo dejo como estaba
declare @reportID uniqueidentifier, @subscriptionID uniqueidentifier, @path nvarchar(255), @body nvarchar(max), @asunto nvarchar(max)

SELECT
  @reportID  = rs.reportID
, @path = '/Listados/Listado Última Semana'
, @subscriptionID = rs.SubscriptionId
,  @body = 'Se adjunta listado con todos los datos de la última semana (del ' +
  substring(convert(char(8), dateadd(dd, -7, getdate()), 112), 7, 2) + '/' +
  substring(convert(char(8), dateadd(dd, -7, getdate()), 112), 5, 2) + '/' +
  substring(convert(char(8), dateadd(dd, -7, getdate()), 112), 1, 4) + ' al ' +
  substring(convert(char(8), dateadd(dd, -1, getdate()), 112), 7, 2) + '/' +
  substring(convert(char(8), dateadd(dd, -1, getdate()), 112), 5, 2) + '/' +
  substring(convert(char(8), dateadd(dd, -1, getdate()), 112), 1, 4) + ').',
  @asunto = 'Listado de datos de la semana del ' +
  substring(convert(char(8), dateadd(dd, -7, getdate()), 112), 7, 2) + '/' +
  substring(convert(char(8), dateadd(dd, -7, getdate()), 112), 5, 2) + '/' +
  substring(convert(char(8), dateadd(dd, -7, getdate()), 112), 1, 4) + ' al ' +
  substring(convert(char(8), dateadd(dd, -1, getdate()), 112), 7, 2) + '/' +
  substring(convert(char(8), dateadd(dd, -1, getdate()), 112), 5, 2) + '/' +
  substring(convert(char(8), dateadd(dd, -1, getdate()), 112), 1, 4)
FROM
        ReportSchedule rs
            INNER JOIN subscriptions s
                ON rs.subscriptionID = s.subscriptionID
            INNER JOIN dbo.[catalog] c
                ON rs.reportID = c.itemID
WHERE
  c.[path] = '/Listado/Listado_Semana_' + convert(char(8), dateadd(dd, -7, getdate()), 112)+ '_' + convert(char(8), dateadd(dd, -1, getdate()), 112)

update dbo.[Catalog] set [path] = @path where itemID = @reportID

update subscriptions set
   ExtensionSettings = 
      replace(replace(cast(ExtensionSettings as nvarchar(max)), @body, 'Se adjunta listado con todos los datos de la última semana.'), @asunto, '@ReportName')
where SubscriptionID = @subscriptionID

Y ya está, hemos personalizado el asunto, el cuerpo del mensaje y el nombre del fichero. Pero con la misma táctica puede modificarse lo que se precise.

A raíz de un hilo del foro de SQL Server, en el que una persona preguntaba sobre cómo rotar un result set (link), sin contar con mucha experiencia en T-SQL, he preparado una pequeña guía que permita configurar estas tareas, algo complicadas, de una forma más sencilla.

Empecemos por los datos de ejemplo (provienen del mismo hilo): descargar

Y poco más que añadir que el propio código:

DECLARE @cols varchar(max), @cols_isnull varchar(max), @query nvarchar(max)

DECLARE @cod_semestre int, @cod_periodo int, @query_a_pivotar nvarchar(max),
  @campo_piv nvarchar(max), @campo_calc nvarchar(max), @campos_adicionales nvarchar(max)

--Sentencia parametrizada que se desea pivotar
select @query_a_pivotar = N'SELECT
  Persona.Apellido_1 +'' ''+ Persona.Apellido_2 +'', ''+ Persona.Nombres as Nombres,
  Semestre.Semestre, Semestre.Paralelo,
  Asignatura.Nombre AS Asignatura,
  Matricula_Asignatura.Estado
FROM
  Persona INNER JOIN
  Alumno ON Persona.Id_Persona = Alumno.Id_Persona INNER JOIN
  Matricula ON Alumno.Id_Alumno = Matricula.Id_Alumno INNER JOIN
  Matricula_Asignatura ON
     Matricula.Cod_Matricula = Matricula_Asignatura.Cod_Matricula INNER JOIN
  Asignatura ON Matricula_Asignatura.Cod_Asignatura = Asignatura.Cod_Asignatura INNER JOIN
  Distribucion ON
     Asignatura.Cod_Asignatura = Distribucion.Cod_Asignatura and
     Matricula.Cod_Periodo = Distribucion.Cod_Periodo INNER JOIN
  Semestre ON
     Matricula.Cod_Semestre = Semestre.Cod_Semestre AND
     Distribucion.Cod_Semestre = Semestre.Cod_Semestre
WHERE
  Matricula.Cod_Semestre = @pCod_Semestre AND Matricula.Cod_Periodo = @pCod_Periodo'

--Variables
select
  @cod_semestre = 27, @cod_periodo = 24,
  @campo_calc = 'max(Estado)',
  @campo_piv = 'Asignatura',
  @campos_adicionales = 'Nombres, Semestre, Paralelo'

; with ListaCols as (
  --> Incluye aquí la sentencia que devolvería la lista de columnas
  --> (llama "Cols" a dicho campo)
   Select Cols = A.Nombre
   from
     Distribucion D inner join
     Asignatura A on D.Cod_Asignatura = A.Cod_Asignatura
   where
     D.Cod_Semestre = @cod_semestre and
     D.Cod_Periodo = @cod_periodo
     )
--Construimos dos cadenas, para el caso en que quieran tratarse valores nulos
--En este ejemplo, es un campo alfanumérico
--Si fuera numérico el campo a computar, cambiar <''-''> por <'0'>
--Si no hay que gestionar nulos,
--sustituir @cols_isnull por @cols en el scritp del pivot dinámico
SELECT
  @cols = STUFF((
   SELECT '],[' + Cols
   from ListaCols
   ORDER BY Cols FOR XML PATH('')), 1, 2, '') + ']',
  @cols_isnull = STUFF((
   SELECT '], isnull([' + Cols + '], ''-'') as [' + Cols
   from ListaCols
   ORDER BY Cols FOR XML PATH('')), 1, 2, '') + ']'

--Construcción del pivot dinámico
SELECT @query = N'SELECT ' + @campos_adicionales +', ' + @cols_isnull +'
FROM
(' + @query_a_pivotar + ') p
PIVOT
( '+ @campo_calc + '
FOR ' + @campo_piv + ' IN
( '+ @cols +' )
) AS pvt
ORDER BY Nombres;'

--Por último, lo ejecutamos
exec sp_executesql @query,
  N'@pCod_Semestre int, @pCod_Periodo int', @cod_semestre, @cod_periodo

La indexación en SQL Server es un arte que se aprende poco a poco, requiere de bastante experiencia y también hace falta contar con un conocimiento importante de lo que son las tripas de las aplicaciones. Sin embargo, los primeros pasos son sencillos, esto es, crea un índice clustered en cada tabla, y si no sabes cuál poner, pon la clave primaria.

En el segundo escalón, en el que hoy me centraré, está dejar que sea el propio motor quien nos diga, basándose en lo que se ejecuta, qué índices vendrían bien para el rendimiento. No es algo para seguir al pie de la letra, pero ojo, es una estadística, está basado en datos. Y uno no siempre sabe si una sentencia de ejecuta 10 o 1000 veces. Según mi criterio, esta es la mejor fuente de información a la hora de indexar una instancia SQL Server administrada, ya que se crean los índices que realmente se demuestran como necesarios.

Aunque existen muchas sentencias de este tipo de similares características, esta sería una que se puede emplear para SQL Server 2005 y siguientes:

 

select d.statement as fully_qualified_object, user_seeks, avg_user_impact, equality_columns, d.inequality_columns, d.included_columns,
create_index = replace(‘create nonclustered index IX_’ + object_name(d.object_id, d.database_id) +’_A# on ‘ +
object_name(d.object_id, d.database_id) + ‘ (‘ + isnull(d.equality_columns + ‘,’, ”) + isnull(d.inequality_columns, ”) + ‘) ‘ + isnull(‘include (‘ + d.included_columns + ‘)’, ”) + ‘ with(online = on)’
, ‘,)’, ‘)’)
, d.database_id, d.object_id, d.index_handle
–, gs.*
from sys.dm_db_missing_index_groups g
join sys.dm_db_missing_index_group_stats gs on gs.group_handle = g.index_group_handle
join sys.dm_db_missing_index_details d on g.index_handle = d.index_handle
where user_seeks > 100
order by gs.user_seeks desc

 

La propia sentencia te da el script “create index” necesario (que habrá que renombrar), con la partícula “with (online=on)”, valido sólo para alguna ediciones de SQL Server. De forma muy resumida, lo que se obtiene son aquellos índices que mejorarían sentencias que se han lanzado más de 100 veces, indicando los campos que se consultan con igualdad, con desigualdad (como con un “>=”), los campos included, así como las veces que se anotó y el impacto que se le supone, ordenado por el número de ocurrencias de forma descendente. Es lo mismo que cuando se expone de forma gráfica un plan de ejecución en Management Studio y aparecen en verde un índice que se sugiere para mejorar la consulta.

Así, si se observa que esta consulta devuelve que la creación de un índice mejoraría en un 70% una sentencia que se ha ejecutado (desde el último reinicio del servidor, dato importante) unas 20.000 veces, puede uno plantearse la creación de dicho índice. Luego hay que evaluar también los campos que irían en el índice, su tamaño y el tamaño de la tabla, no sea que nos esté sugiriendo crear un índice con decenas de campos de gran tamaño.

Como la mayoría de los problemas de DTC, éstos se atribuyen al servidor de bases de datos, pero no es así, nos cuestiones de este servicio, cuyas transacciones pueden o no pasar por SQL Server. Eso no le libra al DBA de tener que resolverlos. A la larga, la razón es simple, lo hacemos porque podemos (y sabemos).

En general, la resolución de los problemas del DTC es bastante compleja porque es muy oscura y no hay muchas herramientas que permitan aportar datos concretos sobre qué está pasando con estas transacciones que no empiezan o no terminan.

Lo que yo suelo hacer es ir en tres pasos, cuando estemos hablando de transacciones que pasen por un servidor de bases de datos:

  • 1.- Comprobar el acceso al servidor con un ping al nombre del servidor SQL Server. Ejemplo: ping MiServidor
  • 2.- Comprobar el acceso al puerto en el que escucha SQL Server (suele ser el puerto 1433), con un telnet. Ejemplo: telnet MiServidor 1433
  • 3.- Comprobar las transacciones con dtctester (http://support.microsoft.com/kb/293799/es).

Cuando el problema está en el punto 3, el error que se arroja casi siempre es el mismo, que no se puede finalizar la transacción. Ahí entramos en un amplio compendio de posibles problemas, en su mayoría de ocasiones, problemas de comunicación, algún firewall que impide la comunicación. Si hay una DMZ por medio, pues más difícil todavía. Lo único bueno es que en cada casa, los problemas suelen ser muy repetitivos. Resuelto uno o dos casos, los demás se le parecerán mucho.

Tulasi 1 pc


Uso de cookies

Este sitio web utiliza cookies para que usted tenga la mejor experiencia de usuario. Si continúa navegando está dando su consentimiento para la aceptación de las mencionadas cookies y la aceptación de nuestra política de cookies, pinche el enlace para mayor información.plugin cookies