miércoles, 1 de septiembre de 2010

Buscar componentes en DTSX rápidamente

Un buen truco para revisar si en alguno de nuestros paquetes DTSX de integration services hacemos uso del componente obsoleto DTS 2000 es lanzar la siguiente consulta PowerShell.

get-childitem *.dtsx | select-string -pattern "Execute DTS 2000 Package Task" | gci -Name

jueves, 5 de agosto de 2010

Importar datos de excel 2007 ó 2010

Aviso a navegantes intrépidos que usen entornos SQL Server 2008 y/o 2008 R2. Cuando quereis importar datos desde SSMS usando el import wizard y el origen es un excel creado con la versión 2007 o 2010, acordaros de instalar los “2007 Office System Driver: Data Connectivity Components”. Sobre todo si os da un error que dice esto:

The 'Microsoft.ACE.OLEDB.12.0' provider is not registered on the local
machine.

Porque aunque el excel haya sido creado con Excel 2010 y lo tengais instalado…lo que os está pidiendo son los componentes de conectividad de Office 2007 (sino diria Microsoft.ACE.OLEDB.14.0)

Lo podeis encontrar aqui: http://www.microsoft.com/downloads/details.aspx?familyid=7554F536-8C28-4598-9B72-EF94E038C891&displaylang=en 

Salu2!

martes, 3 de agosto de 2010

Detectar consultas con referencias a bases de datos distintas a la actual

Un problema habitual en todo proyecto de migración, escalabilidad o rearquitectura de aplicaciones y/o servidores de bases de datos es la necesidad de conocer la interdependencia de bases de datos.

Los que me conocen, saben que yo apuesto siempre por molestar al cliente al mínimo y eso implica que aunque suelo pedir información relativa a la arquitectura actual, dependencias, etc, etc,…siempre acabo verificando por mi cuenta la realidad (parafraseando a house: “El paciente siempre miente, aunque no lo sepa” Smile )

Bien, si tenemos que pelearnos con un problema como el que comento (que por ejemplo hayan aplicaciones conectadas a la BBDD X, que lancen queries del estilo “Select * from Y.dbo.tabla_en_bbdd_y”), hay una solución bien sencilla, pero que obviamente tarda su tiempecito Smile

Consiste en usar trazas de profiler para capturar la actividad contra la BBDD. Una vez tenemos esta actividad, nos crearemos una BBDD o tabla en la que incorporaremos información relativa a database_id, databaseName y servername (se sobreentiende que esto se hace porque además del ejemplo que estoy exponiendo, hay que hacer algo parecido para referencias entre instancias, pero se sale del ámbito del post).

Una vez tenemos dicha tabla creada y rellenada (a la que llamaremos dbo.Databases), lo siguiente que tenemos que hacer por tanto es, a grandes rasgos:

  1. Obtener los SMTP: BatchCompleted events
  2. Obtener el databaseid de los mismos, junto a su TextData NOT NULL e información relevante como hostname, loginname,…y lo que queramos para posteriormente buscar datos en ellos
  3. Filtrar conexiones a bases de datos tipo pubs, adventureworks y northwind
  4. Por último, obtener aquel texto, en el que se haga referencia a cualquier databasename seguido por “.” y que además no sea el mismo databasename al que estaba la conexión atacando (referencia externa por tanto)

Obviamente pueden haber falsos positivos (igual, tenemos mala suerte y nos sale algun comentario donde se haga referencia,…), pero habrá positivos de haberlos.

No olvideis crear los índices adecuados en la tabla dbo.Databases para que la cosa no vaya lentísima.

Aqui os dejo el código para explorar los resultados:

--
-- ECB: Meta query to detect queries that references databases
--
--
declare @ServerName sysname = 'servidor_sql'
declare @trcFile nvarchar(max) = 'path_to_trc_file.trc'



;with subselect as(
select
dbs.dbid,
dbs.DBName,
textdata,
applicationName,
hostname,
loginname,
starttime
from ::fn_trace_gettable(@trcFile,default) trc
left join dbo.DataBases dbs on trc.DatabaseID = dbs.DBId
and dbs.ServerName = @ServerName
and dbs.dbid > 4 AND dbs.ServerName not in ('pubs','Northwind','Adventureworks')
where trc.textdata IS NOT NULL AND trc.EventClass = 12
)
select
trc.dbname as db_connected ,
dbs.dbName as db_referenced,
trc.applicationname,
trc.hostname,
trc.loginname,
trc.starttime,
cast(trc.textdata as nvarchar(1000)) as [definition]
from subselect trc, dbo.DataBases dbs
where dbs.servername = @ServerName
and dbs.DBId <> trc.DBId
and (trc.TextData LIKE '%'+dbs.dbname+'.%'
or textdata LIKE '%'+dbs.dbname+'].%'
)





Aqui veis el plan de ejecución, que siempre me gusta ver



image



Por último solo me queda comentar que esto mismo hay que hacerlo con los objetos de BBDD (procedimientos almacenados,funciones,…) y con servidores vinculados y aperturas tipo openrowset y similares.



Salu2!

martes, 27 de julio de 2010

Restaurar multiples bases de datos a la vez

En ocasiones hace falta realizar restauraciones masivas de ficheros de backup. Situaciones como resolver una catástrofe o realizar pruebas de migración, pueden requerirnos restaurar 40, 100 bases de datos…y aqui es donde entra este script.

Para el proyecto en el que estoy ahora involucrado, estoy realizando pruebas que involucran directamente restauraciones de más de 50 BBDD y claro…los informáticos no nos caracterizamos por nuesta pasión a las tareas repetitivas, asique…¿mejor que lo haga otro, no?. Pues bien, ese otro será nuestro SQL Server Smile

Los parámetros de inicio del siguiente script son:

  1. dbs: Cursor con los nombres de ficheros de backup a restaurar (si se llama mibbdd.bak, pues mibbdd)
  2. pathFisicoMDF: Ruta donde acabarán los ficheros de datos
  3. pathFIsicoLDF: Ruta donde acabarán los ficheros de log
  4. pathToBak: Ruta donde buscar los ficheros de backup .bak

Solo una cosa mas,

/*
Enrique Catala Bañuls
*/
declare @path nvarchar(max)
DECLARE @dbName sysname
DECLARE @logicalName sysname
declare @physicalName nvarchar(max)
declare @fileName nvarchar(max)
declare @sSql nvarchar(max)
declare @fileType char(1)
declare @pathToBak nvarchar(max)
declare @pathFisicoMDF nvarchar(max)
declare @pathFisicoLDF nvarchar(max)

/*********************************************************************************************/
/*********************************************************************************************/
--
-- FILL HERE DATA NEEDED TO THE RESTORNG PROCESS
--
DECLARE dbs CURSOR READ_ONLY
-- bases de datos restaurándose
FOR select 'bdeap'



select @pathFisicoMDF = N'e:\datos\',
@pathFisicoLDF = N'd:\logs\',
@pathToBak = 'E:\fulldiaria\'
/*********************************************************************************************/
/*********************************************************************************************/

create table #tmp
(
LogicalName sysname
,PhysicalName nvarchar(max)
,Type char(1)
,FileGroupName sysname NULL
,Size numeric(20,0)
,MaxSize numeric(20,0),
Fileid tinyint,
CreateLSN numeric(25,0),
DropLSN numeric(25, 0),
UniqueID uniqueidentifier,
ReadOnlyLSN numeric(25,0) NULL,
ReadWriteLSN numeric(25,0) NULL,
BackupSizeInBytes bigint,
SourceBlocSize int,
FileGroupId int,
LogGroupGUID uniqueidentifier NULL,
DifferentialBaseLSN numeric(25,0) NULL,
DifferentialBaseGUID uniqueidentifier,
IsReadOnly bit,
IsPresent bit,
TDEthumbprint varbinary(32) NULL
)

OPEN dbs
FETCH NEXT FROM dbs INTO @dbName
WHILE (@@fetch_status <> -1)
BEGIN
IF (@@fetch_status <> -2)
BEGIN

SELECT @sSql = '',
@path = 'restore filelistonly from disk = N'''+@pathToBak+@dbName+'.bak'''

--PRINT @path

insert #tmp
EXEC (@path)

--select * from #tmp

DECLARE dbs2 CURSOR READ_ONLY
FOR select logicalName,physicalName,type from #tmp
OPEN dbs2

FETCH NEXT FROM dbs2 INTO @logicalName,@physicalName,@fileType
WHILE (@@fetch_status <> -1)
BEGIN
IF (@@fetch_status <> -2)
BEGIN
set @fileName= substring(@physicalName,
2+len(@physicalName)-charindex('\',reverse(@physicalName)),
charindex('\',reverse(@physicalName)))

if @fileType = 'D'
set @sSql = @sSql+' MOVE N'''+@logicalName+''' TO N'''+@pathFisicoMDF+@fileName+''','
else
set @sSql = @sSql + ' MOVE N'''+@logicalName+''' TO N'''+@pathFisicoLDF+@fileName+''','

--print @logicalName
--print @pathFisicoMDF
--print @sSql

END
FETCH NEXT FROM dbs2 INTO @logicalName,@physicalName,@fileType
END

CLOSE dbs2
DEALLOCATE dbs2

--print @dbName
--print @pathToBak
--print @sSql
--SET @sSql = 'RESTORE DATABASE ['+@dbName+'] FROM DISK = N'''+ @pathToBak +'\' +@dbName + '.bak''
SET @sSql = '
RESTORE DATABASE ['+@dbName+'] FROM DISK = N'''+ @pathToBak +'\' +@dbName + '.bak''
WITH FILE = 1, '
+@sSql+
'
RECOVERY, NOUNLOAD, REPLACE, STATS = 10'
--print @sSql
exec(@sSql)

truncate table #tmp
END
FETCH NEXT FROM dbs INTO @dbName
END

CLOSE dbs
DEALLOCATE dbs
GO
drop table #tmp





jueves, 8 de julio de 2010

Transformar los resultados de SSUA en tupla

Si alguna vez has usado SQL Server Upgrade Advisor para analizar el estado actual de un servidor de SQL Server de cara a una migración, habrás podido notar que la herramienta en sí, aunque funciona como debe, peca en algunos aspectos…con solo tener que analizar más de 1 instancia ya te das cuenta de lo que te digo Smile

Bueno, pues si a alguien (como es mi caso) no le apetece tener que lidiar con los ficheros DE.xml de resultado exportados a .csv que genera la herramienta por defecto…que sepa que se puede muy facilmente hacer uno un “parser” de dicho XML para hacer con la información lo que mejor se quiera.

Para esto lo único que tenemos que hacer es generarnos el .xsd de resultado (Visual Studio lo hace automático por nosotros), leer el XML a un DataSet y luego lanzar esta “sencilla” query LINQ2DataSet Smile

 
 var q = from ia in ds.Tables["ItemAttribute"].AsEnumerable()
join ias in ds.Tables["ItemAttributes"].AsEnumerable()
on ia.Field<int>("ItemAttributes_id")
equals ias.Field<int>("ItemAttributes_id")
join item in ds.Tables["Item"].AsEnumerable()
on ias.Field<int>("Item_id")
equals item.Field<int>("Item_id")
join r in ds.Tables["Report"].AsEnumerable()
on item.Field<Nullable<byte>>("ItemId")
equals r.Field<Nullable<byte>>("ItemId")
join iss in ds.Tables["Issue"].AsEnumerable()
on r.Field<Nullable<byte>>("IssueId")
equals iss.Field<Nullable<byte>>("IssueId")
select new
{
ItemAttributesId = ia.Field<int>("ItemAttributes_id"),
Name = ia.Field<string>("Name"),
Value = ia.Field<string>("Value"),
Issue = iss.Field<string>("IssueDescription"),
IssueText = iss.Field<string>("IssueText"),
IssueType = iss.Field<string>("IssueType")
};



Ahora solo te queda usar el objeto “q” para añadirle o toquetearle lo que quieras…una pista?…¿Qué tal que tu parser sea capaz de darte estimaciones temporales basado en el tipo de “Issue”? Smile


Salu2!

martes, 6 de julio de 2010

Averiguar conexiones externas hacia nuestro SQL Server

Últimamente estoy participando en bastantes proyectos de migración de SQL Server 2000 a SQL Server 2008 R2. Independientemente de la arquitectura existente en cada cliente, siempre hemos de conocer la topologia de consultas que se lanzan y sobre todo QUIEN las lanza.

En un proyecto de migración nunca podemos dejar cabos sueltos y menos cuando uno de los cabos sueltos puede llevar a que aplicaciones (sean o no críticas) no funcionen.

La opción más sensata siempre es crear una traza de profiler para SQL Server 2000, que nos capture durante un tiempo prudencialmente amplio y significativo, la actividad de nuestro servidor

¿Qué nos interesa conocer?

  1. La consulta que ha sido lanzada (posteriormente lo utilizaremos para que SSUA analice si hay patrones conflictivos)
  2. El hostname desde donde se lanza
  3. El login utilizado
  4. El nombre de la aplicación
  5. La BBDD sobre la que se está ejecutando la consulta

 

Dicho esto, nos podemos hacer una idea de los eventos e información que necesitamos capturar en SQL Server profiler…y ahora viene la parte divertida…la explotación de esos datos Smile

-- Create table with data
--
CREATE TABLE [dbo].ExternalConnectionAnalysis(
[ServerName] [nvarchar](256) NOT NULL,
[databaseid] [int] NULL,
[applicationname] [nvarchar](256) NULL,
[hostname] [nvarchar](256) NULL,
[loginname] [nvarchar](256) NULL,
queries_executed bigint not null
) ON [PRIMARY]

GO

INSERT into dbo.ExternalConnectionAnalysis(ServerName, databaseid,
          applicationname, hostname, loginname,queries_executed)
SELECT 'atlante3' AS ServerName , databaseid, applicationname,
       hostname, loginname,COUNT(*)
FROM ::fn_trace_gettable('path_to_file.trc', default)
GROUP BY databaseid, applicationname, hostname, loginname
go



Tenemos esa opción que evidentemente te lee el fichero de traza .trc (y los que vengan detrás en caso de haberse creado con multiples ficheros) y agrupa por la información que queremos, o podemos optar por una forma algo más rebuscada y eficiente para procesar los datos como esta otra:



declare @srvname sysname = 'my_server'
declare @trcpath varchar(max) = 'path_to_file.trc'
;

with existent_data as(
select ServerName, databaseid,
applicationname,hostname,loginname
from dbo.ExternalConnectionAnalisys
where ServerName = @srvname
),
trc as (
SELECT @srvname AS ServerName
, trc.databaseid, trc.applicationname,
trc.hostname, trc.loginname
FROM ::fn_trace_gettable(@trcpath, default) trc
)
INSERT into dbo.ExternalConnectionAnalysis(ServerName, databaseid,
applicationname, hostname, loginname,queries_executed)
SELECT @srvname AS ServerName , trc.databaseid, trc.applicationname,
trc.hostname, trc.loginname ,COUNT(*)
FROM trc left join existent_data ed on
(trc.DatabaseID = ed.databaseid
and trc.ApplicationName = ed.applicationname
and trc.HostName = ed.hostname
and trc.LoginName = ed.loginname
)
where ed.databaseid is null or ed.applicationname is null or
ed.hostname is null or ed.loginname is null
GROUP BY trc.databaseid, trc.applicationname, trc.hostname, trc.loginname



La gran ventaja de utilizar la consulta anterior radica principalmente en que es una consulta que solo añadirá las nuevas filas con información que le vayamos proponiendo. Es decir, que como es normal, tendremos ficheros de traza a traves del tiempo (cada dia previsiblemente tendremos .trc nuevos) y los podremos procesar independiemente de tener todos los ficheros de traza y procesarlos de golpe.



Evidentemente es una gran ventaja…pero otra ventaja oculta es si miras un poco más alla de la simple consulta y te das cuenta de que al utilizar el left join y el group by, nuestro SQL Server ha generado un plan de ejecución eficiente mediante un fantástico MERGE JOIN



image



 



Queda a tu disposición probar la consulta sin el left join, mediante un cruce de los de toda la vida y ver que ocurre…y sobre todo el tiempo que tarda, por culpa del super LOOP JOIN que te mete Smile



 



Salu2!

domingo, 23 de mayo de 2010

Impartiré sesión en el lanzamiento de SQL Server 2008 R2

Hola, tengo la suerte de poder impartir junto a mi compañero y amigo Enrique Puig, una sesión sobre SQL Server Azure en el lanzamiento oficial de SQL Server 2008 R2 que tendrá lugar en Madrid el próximo dia 14 de Junio.

Si deseas inscribirte aqui va el link directo

Para más información: http://www.microsoft.com/spain/sql/eventos/lanzamientos2008r2/index.aspx