Category Archives: SQLServerPedia syndication

Gerar DACPAC usando Powershell v2

Para quem leu meu post anterior de Gerar DACPAC usando Powershell percebeu que é um código muito simples, passível de erros como:

  • digitar o nome do banco errado,
  • símbolos nos nomes dos bancos,
  • não escolher um local ou o nome do servidor ele gera erro no script…

coisas bem primárias…

Dessa vez fiz algumas melhorias,,, como:

  • Tela mais simpática
  • conecta no servidor de banco e lista as bases
  • multi seleção de bases
  • não deixa você continuar se não selecionar um destino e colocar o nome do servidor
  • Não fecha sozinho
  • deixa na tela do posh o código para, caso precise, você copie e execute novamente
  • coloca o mouse como ampulheta
  • abre o diretório de destino quando acaba a tarefa
  • e localiza o executável do gerador de dacpac,,, desde que esteja no C:\

Uma coisa muito importante, o código é livre para modificação, sinta-se a vontade de fazer qualquer modificação que queira,, se achar que sua versão ficou melhor, ou achou algum problema e corrigiu, mande o código, vamos melhorar,,,,

Existe um detalhe para rodar esta versão sem problemas,,, você precisa ter o Windows atualizado além do Windows Management Framework 4.0 e do .Net Frame Work 4.5.2.


cls
$data = get-date -format "_yyyyMMdd"
$WindowTitle = "Gerador de DACPAC"
$LabelPath = "Caminho:"
$LabelServer = "Servidor:"

$aplicacao = (Get-ChildItem -Path "C:\Program Files (x86)\Microsoft SQL Server" -Include "SqlPackage.exe" -Recurse | % { $_.FullName } | Select-Object -First 1)

$x = @()

[void] [System.Reflection.Assembly]::LoadWithPartialName("System.Windows.Forms")
[void] [System.Reflection.Assembly]::LoadWithPartialName("System.Drawing")

$objForm = New-Object System.Windows.Forms.Form
$objForm.Text = $WindowTitle
$objForm.Size = New-Object System.Drawing.Size(290,270)
$objForm.StartPosition = "CenterScreen"
$objForm.FormBorderStyle = 'FixedSingle'

$objForm.KeyPreview = $True
$objForm.Add_KeyDown({if ($_.KeyCode -eq "Escape")
{$objForm.Close()}})

$OKButton = New-Object System.Windows.Forms.Button
$OKButton.Location = New-Object System.Drawing.Size(75,215)
$OKButton.Size = New-Object System.Drawing.Size(75,23)
$OKButton.Text = "OK"

$OKButton.Add_Click(
{$i=0
if ( $textbox2.Text -eq '' )
{

}

else
{
foreach ($objItem in $objListbox.SelectedItems)
{
#[System.Windows.Forms.Application]::UseWaitCursor=$true
$objForm.Cursor=[System.Windows.Forms.Cursors]::WaitCursor
$local = $textbox2.Text
$servidor = $textBoxServerName.Text
Write-Progress -Activity "Gerando DACPAC" -status "Gerando DACPAC para $objItem" -percentComplete ($i++ / ($objListbox.SelectedItems).Count*100)
$exec = ("'$aplicacao'" + " /a:Extract /ssn:"+$servidor +" /sdn:"+$objItem +" /tf:"+"'$local'"+"\"+$objItem+$data+".dacpac")
#$exec = (C:\"Program Files (x86)"\"Microsoft SQL Server"\110\DAC\bin\SqlPackage.exe /a:Extract /ssn:$servidor /sdn:$objItem /tf:$local\$objItem$data+.dacpac)
write-host $exec
Invoke-expression "& $exec"
#[System.Windows.Forms.Application]::UseWaitCursor=$false
$objForm.Cursor=[System.Windows.Forms.Cursors]::Default
}
#Invoke-Item "$local"
}
if ( $checkbox1.Checked -eq $true -and $textbox2.Text -ne '')
{
Invoke-Item "$local"
}

else
{

}

#$objForm.Close()
})

$objForm.Controls.Add($OKButton)

$CancelButton = New-Object System.Windows.Forms.Button
$CancelButton.Location = New-Object System.Drawing.Size(150,215)
$CancelButton.Size = New-Object System.Drawing.Size(75,23)
$CancelButton.Text = "Fechar"
$CancelButton.Add_Click({$objForm.Close()})
$objForm.Controls.Add($CancelButton)

# create your checkbox
$checkbox1 = new-object System.Windows.Forms.checkbox
$checkbox1.Location = new-object System.Drawing.Size(260,66)
$checkbox1.Size = new-object System.Drawing.Size(250,50)
$checkbox1.Checked = $true
$objForm.Controls.Add($checkbox1)

$objLabel = New-Object System.Windows.Forms.Label
$objLabel.Location = New-Object System.Drawing.Size(10,120)
$objLabel.Size = New-Object System.Drawing.Size(280,20)
$objLabel.Text = "Selecione a base que deseja extrair o DACPAC:"
$objForm.Controls.Add($objLabel)

$objListbox = New-Object System.Windows.Forms.Listbox
$objListbox.Location = New-Object System.Drawing.Size(10,140)
$objListbox.Size = New-Object System.Drawing.Size(260,20)
$objLabel2 = New-Object System.Windows.Forms.Label
$objLabel2.Location = New-Object System.Drawing.Size(10,10)
$objLabel2.Size = New-Object System.Drawing.Size(280,20)
$objLabel2.Text = "Servidor:"
$objForm.Controls.Add($objLabel2)

# Create textbox for the Server name
$textBoxServerName = New-Object System.Windows.Forms.TextBox
$textBoxServerName.DataBindings.DefaultDataSourceUpdateMode = 0
$textBoxServerName.Location = New-Object System.Drawing.Size(10,30)
$textBoxServerName.Name = "ServerName"
$textBoxServerName.Size = New-Object System.Drawing.Size(100,10)
$textBoxServerName.TabIndex = 0
$objForm.Controls.Add($textBoxServerName)
$objLabel2 = New-Object System.Windows.Forms.Label
$objLabel2.Location = New-Object System.Drawing.Size(10,60)
$objLabel2.Size = New-Object System.Drawing.Size(280,20)
$objLabel2.Text = "Destino:"
$objForm.Controls.Add($objLabel2)

# Create textbox used to file destination
$textBox2 = New-Object System.Windows.Forms.TextBox
$textBox2.DataBindings.DefaultDataSourceUpdateMode = 0
$textBox2.Location = New-Object System.Drawing.Size(10,80)
$textBox2.Name = "textBox2"
$textBox2.Size = New-Object System.Drawing.Size(150,10)
$textBox2.TabIndex = 0
$objForm.Controls.Add($textBox2)

$app = New-Object -ComObject Shell.Application
$destino_OnClick=
{
#TODO: Place custom script here

try {
$browseForFolderOptions = 0
if ($NoNewFolderButton) { $browseForFolderOptions += 512 }

$folder = $app.BrowseForFolder(0, $Message, $browseForFolderOptions, $InitialDirectory)
#if ($folder) { $selectedDirectory = $folder.Self.Path } else { $selectedDirectory = '' }
#[System.Runtime.Interopservices.Marshal]::ReleaseComObject($app) > $null
$textbox2.Text = $folder.Self.Path #$selectedDirectory #.FileName.ToString()

}
catch {
[System.Windows.Forms.MessageBox]::Show(
$_.Exception.Message,
"Error",
[System.Windows.Forms.MessageBoxButtons]::OK,
[System.Windows.Forms.MessageBoxIcon]::Error
)
}

}
$conectar_OnClick=
{
#TODO: Place custom script here

if ( $textBoxServerName.Text -eq '' )
{

}

else
{
$objForm.Cursor=[System.Windows.Forms.Cursors]::WaitCursor
$dataSource = $textBoxServerName.Text

$database = "master"
$connectionString = "Server=$dataSource;Database=$database;Integrated Security=True;"

$query = "select name from sys.databases where database_id > 4"

$connection = New-Object System.Data.SqlClient.SqlConnection
$connection.ConnectionString = $connectionString
$connection.Open()
$command = $connection.CreateCommand()
$command.CommandText = $query
$result = $command.ExecuteReader()
$table = new-object “System.Data.DataTable”
$table.Load($result)
#$table
$objListbox.datasource=$table.Item(0)
$connection.Close()
$objForm.Cursor=[System.Windows.Forms.Cursors]::Default

}

}

$conectar = New-Object System.Windows.Forms.Button
$conectar.Location = New-Object System.Drawing.Size(200,30)
$conectar.Size = New-Object System.Drawing.Size(60,20)
#$button2.TabIndex = 1
$conectar.Text = "Conectar"
$conectar.UseVisualStyleBackColor = $True
$conectar.add_Click($conectar_OnClick)
$objForm.Controls.Add($conectar)
$destino = New-Object System.Windows.Forms.Button
$destino.Location = New-Object System.Drawing.Size(200,80)
$destino.Size = New-Object System.Drawing.Size(50,20)
#$button2.TabIndex = 1
$destino.Text = "..."
$destino.UseVisualStyleBackColor = $True
$destino.add_Click($destino_OnClick)
$objForm.Controls.Add($destino)

$objListbox.SelectionMode = "MultiExtended"

$objListbox.Height = 70
$objForm.Controls.Add($objListbox)
$objForm.Topmost = $True

$objForm.Add_Shown({$objForm.Activate()})
[void] $objForm.ShowDialog()

$x

[polldaddy poll=8777809]

Gerar DACPAC usando PowerShell

O ciclo de vida da maioria dos bancos envolve desenvolvedores e DBA´s compartilhando scripts para atualização de objetos e atividades de manutenção.

Existem algumas formas de fazer isso:

  • gerar scripts de objetos
  • Proporcionar acesso as partes interessadas (piada… eu sei…) para gerar os scripts
  • DACPAC´s
  • Backup/Restore
  • etc..

Um pouco de contexto antes,,,

Um DAC (aplicativo da camada de dados) é uma entidade lógica de gerenciamento de banco de dados que define todos os objetos do SQL Server, como tabelas, exibições e objetos de instância, incluindo logons, associados a um banco de dados de usuário.

Todos tem seus prós e contras… não é a intenção deste post tratar isso…

Para quem gosta de gerar scripts dos objetos lembra que tem um outro post que faz isso…

A vantagem do DACPAC é que você pode usa-lo para comparação entre um projeto do TFS e esse pacote extraído da produção ou, caso você seja o DBA, você pode comparar esse pacote contra a sua base de produção ou uma base em homologação. Gerar os scripts para igualar os ambientes ou apenas conseguir ter ideia das diferenças.


Add-Type -AssemblyName System.Drawing
Add-Type -AssemblyName System.Windows.Forms

$data = get-date -format "_yyyyMMdd"
$WindowTitle = "Gerador de DACPAC"
$LabelPath = "Caminho:"
$LabelServer = "Servidor:"

# Create the Label.
$label = New-Object System.Windows.Forms.Label
$label.Location = New-Object System.Drawing.Size(10,20)
$label.Size = New-Object System.Drawing.Size(280,20)
$label.AutoSize = $true
$label.Text = $LabelPath

# Create the TextBox used to capture the user's text.
$textBox = New-Object System.Windows.Forms.TextBox
$textBox.Location = New-Object System.Drawing.Size(10,40)
$textBox.Size = New-Object System.Drawing.Size(575,200)
$textBox.AcceptsReturn = $true
$textBox.AcceptsTab = $false
$textBox.Multiline = $true
$textBox.ScrollBars = 'Both'
$textBox.Text = $DefaultText

# Create textbox used to file destination
$textBox2 = New-Object System.Windows.Forms.TextBox
$textBox2.DataBindings.DefaultDataSourceUpdateMode = 0
$textBox2.Location = New-Object System.Drawing.Size(65,20)
$textBox2.Name = "textBox2"
$textBox2.Size = New-Object System.Drawing.Size(150,10)
$textBox2.TabIndex = 0
# Create the Label.
$label2 = New-Object System.Windows.Forms.Label
$label2.Location = New-Object System.Drawing.Size(420,20)
$label2.AutoSize = $true
$label2.Text = $LabelServer

# Create textbox for the Server name
$textBoxServerName = New-Object System.Windows.Forms.TextBox
$textBoxServerName.DataBindings.DefaultDataSourceUpdateMode = 0
$textBoxServerName.Location = New-Object System.Drawing.Size(480,20)
$textBoxServerName.Name = "textBox2"
$textBoxServerName.Size = New-Object System.Drawing.Size(100,10)
$textBoxServerName.TabIndex = 0

# Create the OK button.
$okButton = New-Object System.Windows.Forms.Button
$okButton.Location = New-Object System.Drawing.Size(415,250)
$okButton.Size = New-Object System.Drawing.Size(75,25)
$okButton.Text = "OK"
$okButton.Add_Click({ $form.Tag = $textBox.Text; $form.Close() })

# Create the Cancel button.
$cancelButton = New-Object System.Windows.Forms.Button
$cancelButton.Location = New-Object System.Drawing.Size(510,250)
$cancelButton.Size = New-Object System.Drawing.Size(75,25)
$cancelButton.Text = "Cancel"
$cancelButton.Add_Click({ $form.Close() })

$app = New-Object -ComObject Shell.Application
$button2_OnClick=
{
try {

$browseForFolderOptions = 0
if ($NoNewFolderButton) { $browseForFolderOptions += 512 }
$folder = $app.BrowseForFolder(0, $Message, $browseForFolderOptions, $InitialDirectory)
$textbox2.Text = $folder.Self.Path #$selectedDirectory #.FileName.ToString()
}
catch {
[System.Windows.Forms.MessageBox]::Show(
$_.Exception.Message,
"Error",
[System.Windows.Forms.MessageBoxButtons]::OK,
[System.Windows.Forms.MessageBoxIcon]::Error
)
}

}
$button2 = New-Object System.Windows.Forms.Button
$button2.Location = New-Object System.Drawing.Size(215,20)
$button2.Size = New-Object System.Drawing.Size(50,20)
$button2.Text = "..."
$button2.UseVisualStyleBackColor = $True
$button2.add_Click($button2_OnClick)
# Create the form.
$form = New-Object System.Windows.Forms.Form
$form.Text = $WindowTitle
$form.Size = New-Object System.Drawing.Size(610,320)
$form.FormBorderStyle = 'FixedSingle'
$form.StartPosition = "CenterScreen"
$form.AutoSizeMode = 'GrowAndShrink'
$form.Topmost = $True
$form.AcceptButton = $okButton
$form.CancelButton = $cancelButton
$form.ShowInTaskbar = $true

# Add all of the controls to the form.
$form.Controls.Add($label)
$form.Controls.Add($label2)
$form.Controls.Add($textBox)
$form.Controls.Add($okButton)
$form.Controls.Add($cancelButton)
$form.Controls.Add($textBox2)
$form.Controls.Add($textBoxServerName)
$form.Controls.Add($button2)

# Initialize and show the form.
$form.Add_Shown({$form.Activate()})
$form.ShowDialog() > $null

$srv = $textBoxServerName.Text
$local = $textbox2.Text
$bases = $textBox.Text.Split("`n")|%{$_.trim()}
$i=0
foreach ($s in $bases)
{
Write-Progress -Activity "Gerando DACPAC" -status "Gerando DACPAC para $s" -percentComplete ($i++ / $bases.count*100)
$GoBigSon= (C:\"Program Files (x86)"\"Microsoft SQL Server"\110\DAC\bin\SqlPackage.exe /a:Extract /ssn:$srv /sdn:$s /tf:$local\$s$data.dacpac )
}

Como parece de praxe,,, tem uns pequenos bugs:

  • Quando clicar no “…” para mapear o caminho, se não aparecer uma janela pop-up, movimenta a janela do app um pouco para o lado, ela ficou atrás da janela inicial,,, isso é um bug que acontece de vez em quando,,,
  • se vc clicar diversas vezes no “…” ele vai ficar abrindo diversas vezes…
  • ainda não estou tratando outros tipos de entrada no campo texto,,,, você deve colocar um banco abaixo do outro,,,
  • e sim,,, ele fecha a janela quando você clica em OK,,,

Ele ficou até que bem simpático…

 

Com os campos preenchidos

 

E tem até barra de status… olha que chique…

 

Powershell, Excel e SQL uma combinação excelente

Uma coisa muito legal em automatizar tarefas é a dificuldade que temos em planejar o que vai ser executado, a forma que vai ser executado e manter da forma mais genérica possível para garantir que vai funcionar na maior parte dos ambientes sem precisar sofrer muita alteração…

Agora imagina uma rotina qualquer em que você precisa ficar pegando resultados colocando no excel para fazer alguma graça interessante para alguém…

O posh abaixo faz exatamente isso. Ele conecta em um servidor que você definir, pega o nome dos bancos e executa a mesma query em cada um dos bancos,,,

até aí nenhuma novidade, certo?

O legal é que o resultado já vai para o excel, formatado e com auto filtro… e cada guia é a resposta de um banco deste servidor.

Você pode alterar a query para executar o que quiser, fragmentação de índice? blz…. um único select de uma única base? sem problemas,,, ou verificar estimativa de compactação de tabelas e índices usando compactação PAGE? é para isso que estamos aqui….


$servers = "SERVIDOR"

#lista as bases de dados para entrar no looping
$databases =
@'
SELECT
name
FROM sys.sysdatabases
where dbid>4
'@

$resultsDB = (invoke-sqlcmd -ServerInstance $servers -Query $databases).name

#query para ser executada em cada base do looping
$query2 =
@'
declare @scanupd TABLE
(
table_name sysname NULL
,index_name sysname NULL
,partition int NULL
, index_id int NULL
,index_type nvarchar(12) NULL
,percent_scan bigint NULL
,percent_update bigint NULL
)

DECLARE @CompressionSavingsEstimate table
(
SchemaName sysname NOT NULL,
ObjectName sysname NOT NULL,
IndexName sysname NOT NULL,
IndexType nvarchar(60) NOT NULL,
PartitionNum int NOT NULL,
CompressionType nvarchar(10) NOT NULL,
[size_with_current_compression_setting (KB)] bigint NOT NULL,
[size_with_requested_compression_setting (KB)] bigint NOT NULL,
[sample_size_with_current_compression_setting (KB)] bigint NOT NULL,
[sample_size_with_requested_compression_setting (KB)] bigint NOT NULL,
percent_scan bigint NULL,
percent_update bigint NULL
PRIMARY KEY (SchemaName, ObjectName, IndexName, IndexType, PartitionNum, CompressionType)
);
DECLARE @ProcResult table
(
[object_name] sysname NOT NULL,
[schema_name] sysname NOT NULL,
[index_id] int NOT NULL,
[partition_number] int NOT NULL,
[size_with_current_compression_setting (KB)] bigint NOT NULL,
[size_with_requested_compression_setting (KB)] bigint NOT NULL,
[sample_size_with_current_compression_setting (KB)] bigint NOT NULL,
[sample_size_with_requested_compression_setting (KB)] bigint NOT NULL
);
DECLARE @SchemaName sysname;
DECLARE @ObjectName sysname;
DECLARE @IndexID int;
DECLARE @IndexName sysname;
DECLARE @IndexType nvarchar(60);
DECLARE @PartitionNum int;
DECLARE @CompTypeNum tinyint;
DECLARE @CompressionType nvarchar(60);

SET NOCOUNT ON;

DECLARE CompressedIndex INSENSITIVE CURSOR FOR
SELECT s.name AS SchemaName,
o.name AS ObjectName,
i.index_id AS IndexID,
COALESCE(i.name, '<HEAP>') AS IndexName,
i.type_desc AS IndexType,
p.partition_number AS PartitionNum
FROM sys.schemas AS s
INNER JOIN sys.objects AS o
ON s.schema_id = o.schema_id
INNER JOIN sys.indexes AS i
ON o.object_id = i.object_id
INNER JOIN sys.partitions AS p
ON o.object_id = p.object_id
AND
i.index_id = p.index_id
WHERE o.type_desc IN ('USER_TABLE','VIEW')
AND p.data_compression_desc NOT IN ('PAGE','ROW');

OPEN CompressedIndex;

WHILE 1 = 1
BEGIN
FETCH NEXT FROM CompressedIndex
INTO @SchemaName, @ObjectName, @IndexID, @IndexName, @IndexType, @PartitionNum;

IF @@FETCH_STATUS <> 0
BREAK;

SELECT @CompTypeNum = 2;
WHILE @CompTypeNum <= 2
BEGIN
SELECT @CompressionType = CASE @CompTypeNum
WHEN 0 THEN 'NONE'
WHEN 1 THEN 'ROW'
WHEN 2 THEN 'PAGE'
END;

DELETE FROM @ProcResult;

-- RAISERROR('Estimating compression savings using "%s" compression for object "%s.%s", index "%s", partition %d...', 10, 1, @CompressionType, @SchemaName, @ObjectName, @IndexName, @PartitionNum);

INSERT INTO @ProcResult
EXEC sp_estimate_data_compression_savings @schema_name = @SchemaName,
@object_name = @ObjectName,
@index_id = @IndexID,
@partition_number = @PartitionNum,
@data_compression = @CompressionType;

INSERT INTO @CompressionSavingsEstimate
(
SchemaName,
ObjectName,
IndexName,
IndexType,
PartitionNum,
CompressionType,
[size_with_current_compression_setting (KB)],
[size_with_requested_compression_setting (KB)],
[sample_size_with_current_compression_setting (KB)],
[sample_size_with_requested_compression_setting (KB)]
)
SELECT [schema_name],
[object_name],
@IndexName,
@IndexType,
[partition_number],
@CompressionType,
[size_with_current_compression_setting (KB)],
[size_with_requested_compression_setting (KB)],
[sample_size_with_current_compression_setting (KB)],
[sample_size_with_requested_compression_setting (KB)]
FROM @ProcResult;

SELECT @CompTypeNum += 1;
END;
END;

CLOSE CompressedIndex;
DEALLOCATE CompressedIndex;
insert into @scanupd(table_name, index_name, partition,index_id,index_type,percent_scan,percent_update)
SELECT o.NAME AS [Table_Name]
,x.NAME AS [Index_Name]
,i.partition_number AS [Partition]
,i.index_id AS [Index_ID]
,x.type_desc AS [Index_Type]
,i.range_scan_count * 100.0 / (i.range_scan_count + i.leaf_insert_count + i.leaf_delete_count + i.leaf_update_count + i.leaf_page_merge_count + i.singleton_lookup_count) AS [Percent_Scan]
,i.leaf_update_count * 100.0 / (i.range_scan_count + i.leaf_insert_count + i.leaf_delete_count + i.leaf_update_count + i.leaf_page_merge_count + i.singleton_lookup_count) AS [Percent_Update]
FROM sys.dm_db_index_operational_stats(db_id(), NULL, NULL, NULL) i
JOIN sys.objects o ON o.object_id = i.object_id
JOIN sys.indexes x ON x.object_id = i.object_id
AND x.index_id = i.index_id
WHERE (i.range_scan_count + i.leaf_insert_count + i.leaf_delete_count + leaf_update_count + i.leaf_page_merge_count + i.singleton_lookup_count) != 0
AND objectproperty(i.object_id, 'IsUserTable') = 1
ORDER BY [Percent_Scan] DESC
SELECT CompressionType,
IndexName,
IndexType,
ObjectName,
PartitionNum,
CASE WHEN t.[percent scan] >100 then 100 else [percent scan] END AS [percent scan],
CASE WHEN t.[percent update] >100 then 100 else [percent update] END AS [percent update],
SchemaName,
[size_with_current_compression_setting (KB)],
[size_with_requested_compression_setting (KB)],
([size_with_requested_compression_setting (KB)]/NULLIF([size_with_current_compression_setting (KB)],0)*100) AS [X percent compact],
CASE WHEN (([size_with_requested_compression_setting (KB)]/NULLIF([size_with_current_compression_setting (KB)],0)*100) <=80 OR ([size_with_requested_compression_setting (KB)]/NULLIF([size_with_current_compression_setting (KB)],0)*100) IS NULL) AND [t].[percent scan] >=t.[percent update] THEN 'Compactar' ELSE 'Provavelmente Nao' END as [Z result]
FROM (
SELECT CompressionType,
IndexName,
IndexType,
ObjectName,
PartitionNum,
sum(s.percent_scan) AS [percent scan],
sum(s.percent_update) AS [percent update],
SchemaName,
cast(AVG([size_with_current_compression_setting (KB)]) AS money) AS [size_with_current_compression_setting (KB)],
cast(AVG([size_with_requested_compression_setting (KB)]) AS money) AS [size_with_requested_compression_setting (KB)]
FROM @CompressionSavingsEstimate CSE
INNER JOIN @scanupd AS s
ON CSE.ObjectName = S.table_name
GROUP BY GROUPING SETS (
(CompressionType),
(SchemaName, ObjectName, IndexName, IndexType, PartitionNum, CompressionType)
)
)AS t
ORDER BY SchemaName, ObjectName, IndexName, IndexType, PartitionNum, CompressionType DESC;

SET NOCOUNT OFF;
'@

# abre o excel
$xl = new-object -comobject excel.application
$wb = $xl.Workbooks.Add()
#$ExcelWorkSheet = $wb.Worksheets.Add()
$xl.Visible = $true
#$rowCount = 2

foreach ($s in $resultsDB)
{
$s #pode ser removido
$ExcelWorkSheet = $wb.Worksheets.Add()
$ExcelWorkSheet.Name = $s #usa o nome do banco como nome da planilha
$rowCount = 2
$results = invoke-sqlcmd -ServerInstance $servers -Query $query2 -Database $s -querytimeout ([int]::MaxValue) #timeout nunca !!!

foreach ($r in $results)
{
$r #pode ser removido
$ColCount = 1
$ExcelWorkSheet.Cells.Item($rowCount,$ColCount).Value2 = $servers
$ColCount = 2
for ($i=1; $i -le $r.ItemArray.Count; $i ++)
{
$ExcelWorkSheet.Cells.Item($rowCount,$ColCount).Value2 = $r.ItemArray[$i-1]
$ColCount ++
}
$rowCount ++
}
# adiciona o cabeçalho das colunas
$headers = $results | Get-Member -Membertype property
$ExcelWorkSheet.Cells.Item(1,1).Value2 = 'server'
$h = 2

foreach ($header in $headers)
{
$ExcelWorkSheet.Cells.Item(1,$h).Value2 = $header.name
$h ++
}
## Formata a planilha do Excel
$listObject = $ExcelWorkSheet.ListObjects.Add([Microsoft.Office.Interop.Excel.XlListObjectSourceType]::xlSrcRange, $ExcelWorkSheet.UsedRange, $null,[Microsoft.Office.Interop.Excel.XlYesNoGuess]::xlYes,$null)
$listObject.Name = "User Table"
$listObject.TableStyle = "TableStyleLight10"
## ajusta o tamanho das colunas
$ExcelWorkSheet.UsedRange.Columns.Autofit() | Out-Null

}

PS.: ainda estou tentando entender qual a birra do powershell em querer colocar o resultado das colunas em ordem alfabética,,, assim que resolver este detalhe atualizo o código,,,

T-SQL Tuesday #57 – SQL Family and community

Para este mês o anfitrião do T-SQL Tuesday #57 é o Sr. Jeffery Verheul  (B|T)

Quando começamos como DBA´s, começamos lendo alguns livros, vendo alguns materiais na internet, participando de alguns treinamentos, etc… No começo acaba sendo bem assim,,, simples,,,

Aí você se depara com algum problema um pouco mais complicado,,, ou precisa fazer uma feature mais específica funcionar,,, e começa a perceber que existem mais pessoas mundo a fora que também mexem com essa coisa de SQL…

Ai descobre os BLOG´s,  fóruns de discussão, a hash #SQLHelp e vê que existem não mais,,, mas muito mais pessoas interessadas nessa coisinha chamada SQL… que o mundo não é só você com seu banquinho, mas com uma infinidade de pessoas precisando de ajuda e muitas outras dispostas a ajudar…

Quando você achou que já sabia de tudo que podia existir no SQL sempre vem alguém com mais e mais informação ou melhor forma ou solução para coisas que talvez você nem tivesse ideia que poderia acontecer…

Quantas vezes você não passou a noite em claro tentando resolver problemas e depois de colocar a pergunta no Twitter com a hashtag #SQLHelp teve a resposta em menos de 5 min? vinda de um cara que está provavelmente na Nova Zelândia que uma vez já passou por esse problema e fez um post em um blog  qualquer com a solução?

Após um tempo você percebe que existem várias fomas de interação, Ex.:

  • SQL Saturday – palestras presenciais gratuitas,
  • 24h de SQL – 24 horas de palestras online gratuitas
  • WebCasts – 1h30 sobre um assunto específico
  • #SQLHelp – ajuda
  • SQLPASS – Principal evento presencial (pago) da comunidade, só tem feras…
  • Fórum – a forma mais básica de P/R
  • Sites e mais sites… – olha você aqui lendo no meu site… 😀

E o melhor de tudo, tirando o SQLPASS e o twitter, tudo em português! Não tem nem como reclamar…

Quer começar a fazer parte? acesse o SQLPass faça um cadastro e veja as comunidades que existem na sua região… é de graça…

Isso é comunidade, pessoas ajudando pessoas. Não para massagear egos, mas pelo grande prazer em criar um lugar melhor, fazer as coisas melhores.

PS: uma coisa muito importante: Seja educado e agradeça! Se alguém gastou tempo para te ajudar a resolver algum problema ou para te dar um rumo para alguma coisa o mínimo da sua parte é agradecer.

Contar VLF

Esse script é bem simples, ele vai acessar todas as bases da instância e vai contar quantos VLF´s existem em cada base.
Nada muito complexo.
Qual a importância disso? Muitos VLF´s podem acarretar em um restore muito lento, ou uma inicialização da base muito demorada.
Isso acontece, em muitos casos, por crescimento desordenado do transaction log (crescimento em porcentagem, ou em tamanhos pequenos).
Podemos colocar da seguinte forma: Falta de planejamento e atenção.
Quer saber mais sobre VLF? esse vídeo é bem legal.
Existe alguma forma de corrigir isso? Claro… Não é bonito…
Vamos fazer um quiz, coloquem um comentário com a maior quantidade de VLF´s que vocês tem no ambiente…


CREATE TABLE #temp (
 RecoveryUnitID VARCHAR(255) --essa coluna só existe a partir do 2012, comente esta linha se for executar do 2008r2 para traz
 , FileID VARCHAR(255)
 , FileSize VARCHAR(255)
 , StartOffset VARCHAR(255)
 , FSeqNo VARCHAR(255)
 , [Status] VARCHAR(255)
 , Parity VARCHAR(255)
 , CreateLSN VARCHAR(255)
 )

CREATE TABLE #DBCCResults (
 ServerName VARCHAR(255)
 , DBName VARCHAR(255)
 , VLF bigint
 )

EXEC master.dbo.sp_MSforeachdb
 @command1 = 'USE ? INSERT INTO #temp EXECUTE (''DBCC LOGINFO'')'
 , @command2 = 'INSERT INTO #DBCCResults SELECT @@SERVERNAME, ''?'', count(1) FROM #temp'
 , @command3 = 'TRUNCATE TABLE #temp'

 SELECT DISTINCT
 ServerName
 , DBName
 , VLF
 FROM #DBCCResults
 ORDER BY 3 DESC

 DROP TABLE #temp, #DBCCResults;

SQL Saturday #284 – Porto Alegre

E ai pessoal…

Fiz o upload dos slides da minha apresentação no SQL Saturday #284

Já havia apresentado algumas coisas através dos webcasts da comunidade, mas essa foi minha primeira experiência com um público de carne e osso…

Se vocês gostaram tentarei fazer isso mais vezes…

[slideshare id=34977839&doc=encriptarpreciso-140521201520-phpapp02]

Os arquivos das demos estão aqui.

Challenge Accepted and Now Completed !!!

Vocês conhecem a expressão: “Mais feliz que pinto no lixo”? ou “Mais feliz que filho de barbeiro em quermesse”?

hoje recebi o seguinte email do pessoal da Microsoft:

SIM !!! Depois de passar por todo o calvário desde o treinamento,,, ir algumas vezes até os EUA para fazer o exame teórico… consegui passar nas provas necessárias para a certificação…

Oficialmente….

e agora com o direito de ter um post no The Master Blog

Atualização !!!

E agora no link do Meet the Masters tem meu nome

Demorou mas agora ta no Transcript:

 

SQL Saturday #245 – Rio de Janeiro

Teremos um grande evento da comunidade SQL no Brasil…. o SQL Saturday #245

O SQL Saturday é totalmente gratuito e focado em SQL Server, um dia inteiro de palestras técnicas. No momento o evento encontra-se com o Call for Speakers aberto, ou seja, recebendo inúmeras propostas de palestras, tendo até agora já recebido propostas de palestrantes de vários locais do Brasil e inclusive de outros países !!!

Trata-se da 2a edição do evento, que ocorreu com muito sucesso em abril de 2012, contando na época com mais de 300 participantes.

SQL Saturday é uma marca de eventos já conhecida mundialmente pela alta qualidade técnica. A marca pertence a uma organização chamada SQL PASS, cujo trabalho envolve apoiar grupos de usuários de SQL Server – denominados de PASS Chapter – por todo o mundo.

O SQL PASS cede a marca, bem como algumas ferramentas on-line, patrocinio e orientação para que o PASS Chapter da região realize o evento. No Rio de Janeiro o evento está sendo realizado pelo PASS Chapter devSQL, o mais antigo PASS Chapter do Brasil, liderado por Dennes Torres (t | b).

As inscrições para o evento estão abertas e, como citado antes, é totalmente gratuito. Trata-se do 2o evento realizado no Brasil. O primeiro, realizado em São Paulo por outro PASS Chapter, ficou totalmente lotado antes mesmo da grade de palestras ser definida.

Evento : SQL Saturday #245 – Rio de Janeiro

Data : 31/08

Horário : Abertura as 08:00hs, inicio das palestras as 09:00hs até as 18hs

Local : Universidade Veiga de Almeida

Endereço : Rua Ibituruna, 108, tijuca, próximo a estação São Cristóvão do metrô

Site do Evento : http://www.sqlsaturday.com/245/eventhome.aspx

Maldito seja Case Sensitive

Apenas para deixar claro: Não sou contra a utilização de CS e AS.

O desenvolvedor tem que lembrar que quando a base está com o collation em CS e ninguém alterou a coluna da pesquisa para CI ele vai ter resultados diferentes quando usa essa coluna em um distinct ou no where…

A algum tempo um desenvolvedor veio conversar comigo porque ele estava tendo problemas em um resultado de um select,,, coisa pouca, em uma tabela de uns 1kk de registros ele queria usar distinct para recuperar umas informações, quando ele usava o order by ele mostrava muitos resultados parecidos…

Lembrei ele que a base era CS e que ou teria que usar UPPER/LOWER ou passar um collation para a coluna da pesquisa ele ficou me olhando como se eu fosse louco…. ta certo,,, ele não estava errado,,, mas era uma solução rápida e indolor…

Exemplo:

CREATE DATABASE [BDTESTE]
COLLATE Latin1_General_100_CS_AS
GO

USE BDTESTE
GO

create table tbl_teste(
nome varchar(20)
)
insert into tbl_teste
select 'teste'
union
select 'Teste'
union
select 'tEstE
union
select 'TESTE'

SELECT *
from tbl_teste

select distinct(nome)
from tbl_teste

select distinct(UPPER(nome))
from tbl_teste

select distinct(nome) COLLATE sql_latin1_general_cp1_ci_ai
from tbl_teste

Existem outras formas de solucionar isso,,, trocar o collation da coluna direto na tabela, criar uma view, etc etc…

Pressão de processador?

A algumas semanas atrás eu estava ajudando um cliente a localizar problemas de lentidão no ambiente dele. Não havia um ponto exato, não era sempre na mesma aplicação, muitas vezes nem a mesma unidade, isso ajudava muito na localização do problema,,,

Um dos analistas de desenvolvimento me indagou se o servidor de SQL estava sofrendo com problemas de processador,,, em alguns momentos o servidor apresentava alguns picos em um dos núcleos mas nada muito longo muito menos alarmante.

Com a query abaixo, mostrei que não havia problemas de processamento,,, pelo menos naquele momento…

 Select signal_wait_time_ms=sum(signal_wait_time_ms)<br>,'%signal (cpu) waits' = cast(100.0 * sum(signal_wait_time_ms) / sum (wait_time_ms) as numeric(20,2))<br>,resource_wait_time_ms=sum(wait_time_ms - signal_wait_time_ms)<br>,'%resource waits'= cast(100.0 * sum(wait_time_ms - signal_wait_time_ms) / sum (wait_time_ms) as numeric(20,2))<br>From sys.dm_os_wait_stats 

O resultado é o seguinte:

Agora vem a pergunta: Legal, mas idaí?

O que nos interessa nesse resultado é o %signal (cpu) waits e o %resource waits.

A matemática é bem simples: quanto mais %recouce waits e menos %signal (cpu) waits melhor, quer dizer que, nesse momento não está havendo problemas de pressão com processador, não quer dizer que daqui a 1 segundo não comece a ter, mas no momento da execução dessa query não havia problema.

English version

A few weeks ago i was helping a client to find a slow problem on his environment. Don´t had a exact point, not always the same apllication, even the same place and this help a lot to find the problem.

One of the developers ask me if the SQL Server had problems with processos pressure,,, in a few moments on the day the server show a top processes in one of the cores, but was not the problem.

With the query below i show that we don´t have any problem with the processor, in that moment…

 Select signal_wait_time_ms=sum(signal_wait_time_ms)<br>,'%signal (cpu) waits' = cast(100.0 * sum(signal_wait_time_ms) / sum (wait_time_ms) as numeric(20,2))<br>,resource_wait_time_ms=sum(wait_time_ms - signal_wait_time_ms)<br>,'%resource waits'= cast(100.0 * sum(wait_time_ms - signal_wait_time_ms) / sum (wait_time_ms) as numeric(20,2))<br>From sys.dm_os_wait_stats 

The result was:

And then comes the question: Cool, but WTF?

The important is %signal (cpu) waits and %resource waits.

The mathematic is simple: more %recouce waits and lass %signal (cpu) waits is better, in that moment the server don´t have processos pressure.

Sorry about the poor English, i´ll fix at night.