Si alguna vez te haz preguntado cómo insertar el valor NULL en una columna mientras vez los registros de una tabla en Eterprise Manager, lo único que debes hacer es CTRL+0 (la tecla del número cero mientras presionas la tecla CTRL) al estar en la casilla que deseas modificar.
Buena Idea Mal Implementada
Una de las razones por la cual decidí escribir este artículo es porque alguien me sugirió utilizar el procedimiento de sistema SP_DEPENDS para encontrar las tablas que se usan en un SP (procedimiento almacenado). Al iniciar mis pruebas con el SP_DEPENDS utilizando la base de datos AdventureWorks todo parecía andar muy bien, pero cuando lo utilicé en una base de datos que ha estado funcionando por varios años, descubrí algunas fallas en él.
La idea principal del procedimiento SP_DEPENDS y la tabla de sistema SYSDEPENDS es la de tener un mecanismo que permita guardar información de la dependencia entre los objetos de la base de datos. Por ejemplo, si tenemos TABLA A, TABLA B y un SP C que haga referencia a ambas tablas mediante una consulta SELECT, podríamos ejecutar el SP_DEPENDS pasando el nombre de cualquiera de los tres objetos y veríamos que A esta relacionado a C, B esta relacionado a C y que C esta relacionado a A y B. Ahora bien, si ejecutamos el comando DROP TABLE A y luego recreamos la TABLA A, lo lógico seria que si ejecutamos el SP_DEPENDS nuevamente deberíamos obtener el mismo resultado que vimos anteriormente –pero no es así.
La falla esta en que cuando ejecutamos el comando DROP , todos los registros relacionados con el objeto que estamos eliminando se borran del SYSDEPENDS y aunque volvamos a crear el mismo objeto, las relaciones no se crean nuevamente. Volvamos al ejemplo anterior para explicarlo más detalladamente. Si se ejecuta un DROP TABLE A, entonces todos los registros en la tabla SYSDEPENDS de la TABLA A se borran y la dependencia A > C se desaparece. Al ejecutar el SP_DEPENDS con el objeto C como parámetro podremos observar que la unica relacion que existe es con la TABLA B aunque se aya creado la TABLA A nuevamente.
Así que bien, manténganse alejados del uso del SP_DEPENDS y SYSDEPENDS por ahora. Yo estaré haciendo pruebas con el servidor SQL 2005 a ver si Microsoft se tomó el tiempo y arregló este problema.
Puedes descargar el código aquí y no te olvides de añadir tus comentarios.
RECURSOS: –The Guru’s Guide to Transact-SQL
–Transact-SQL Programming
The Object Within
Finding tables referenced in a stored procedure
Recently, I was asked for an easy way to list the tables used in a stored procedure. I looked in my list of previously used scripts and found one that did not fully satisfy the requirements of the task. So I decided to come up with a solution that will accurately list all tables referenced in a stored procedure. The solution is pretty straight forward and I’ll describe some of the 12 steps in the code that helped me find all referenced tables within it.
Overview of the problem
Sometimes it is necessary to know which tables are referenced or used in a stored procedure. If the stored procedure you’re working with, is one of those mega procedures of thousands of lines of code, it will be very tedious to go through line by line to find all tables referenced within the procedure.
While developing this solution, I encountered some issues rooted in the fact that stored procedures may be written in many different ways, especially the areas that make reference to tables. For example, take a look at the code in List 1 and see three ways you may write the same query.
There were so many variables that made the development of this solution very interesting. The sample code in List 1 reveals only the SELECT statement with three possible ways I would find reference to tables in stored procedures. Take into consideration that we need to account for the UPDATE, INSERT, and DELETE statements and all the different ways we may find them in the body of stored procedures.
SELECT a.Col1 b.Col2 FROM Table1 a, Table2 b WHERE a.ID = b.ID
SELECT a.Col1, b.Col2 FROM Table1 a, Table2 b WHERE a.ID = b.ID
SELECT a.Col1, b.Col2 FROM Table1 a INNER JOIN Table2 b ON a.ID = b.ID
First, I created two temporary tables. Table one (dbo.#Tables) will hold the name of tables and views from the database and table two (dbo.#Procedure) will hold the text from the stored procedure. Later in the script, I’ll verify the list of possible table names and view names found in the stored procedure (dbo.#Procedure) against the values in the first table (dbo.#Tables)to validate possible matches with the real object names.
Table one is populated with data from the SYSOBJECTS table WHERE type IN(‘U’,’V’). Notice in List 2 how I convert the data extracted from sysname data type to varchar to avoid any data comparison problems later in the code. I also made sure to TRIM the values from the SYSOBJECTS table.
INSERT dbo.#Tables(TableName) SELECT CONVERT(VARCHAR(35), LTRIM(RTRIM(Name))) FROM dbo.SYSOBJECTS WHERE Type IN ('U','V')
You may need to check the maximum object name length in your SYSOBECTS table and update the table definition for dbo.#Tables and the CONVERT statement in List 2 in case the value of 35 is not long enough.
I used the SP_HELPTEXT extended procedure to populate table two. This process will insert one record per line of code returned by the extended procedure.
The following steps will modify the values stored in table two. I replaced any tabs, line feeds, and carriage returns with two spaces which will enable me to use the CHARINDEX() function later on in the solution. I also removed any rows with comments, temporary objects and the CREATE PROCEDURE statement since none of these rows should have the table names I’m looking for. I then looked for the first reference to a real table and removed all rows previous to this row.
The code in List 3 shows how I marked any rows with possible table names by setting the "PossibleTable" column to 1 and then removed all rows with NULL value in the same column.
SELECT @PossibleTable = Texto FROM dbo.Procedure WHERE Texto LIKE '%,%' AND Texto NOT LIKE '%(%'; IF @PossibleTable IS NOT NULL BEGIN INSERT dbo.#Procedure (PossibleTable, Texto) SELECT 1 AS PossibleTable, TableNames FROM dbo.fnParseCommaDelimitedList (@PossibleTable); END
The UDF (User Defined Function) in List 4 is used on those rows that may have multiple table names separated by commas. The UDF takes one single parameter of up to 255 characters long and returns a table. It basically inserts one row in the return table for each value separated by a comma from the input parameter.
CREATE FUNCTION dbo.fnParseCommaDelimitedList ( @myString VARCHAR (255)) RETURNS @TableNames TABLE ( TableNames VARCHAR (255)) AS BEGIN DECLARE @myPosition AS INT, @myLen AS INT, @myStart AS INT; SELECT @myPosition = -1, @myString = @myString + ',', @myLen = LEN(@myString), @myStart = 1; WHILE @myPosition < @myLen BEGIN INSERT @TableNames SELECT SUBSTRING(@myString, @myStart, CHARINDEX(',', @myString, @myPosition + 1) - @myStart); SELECT @myPosition = CHARINDEX(',', @myString, @myPosition + 1); SELECT @myStart = @myPosition; END RETURN; END
Conclusion
I have tested this solution against many stored procedures written in many different ways and I have found one condition that will cause some problems. It is on how you write your stored procedures and the width of your lines of code. You’ll need to increase the length of the "Texto" column in the dbo.#Procedure table If there are lines of code in your procedure longer than 255 characters. Make sure you have the necessary user rights to create the objects for this solution.
Download the code 01042006.sql and feel free to post your comments about this solution.
RESOURCES: BOL (MS SQL Books Online)
Generate T-SQL Script for a List of Views
USE:
CLEAR $sSRCServer = "TUIRA" $sSRCDatabase = "AdventureWorks" $oSO = New-SqlScriptingOptions $oSO.Permissions = $true $oSO.IncludeIfNotExists = $true $a = Get-Content "C:tempviewlist.txt" foreach ($o in $a) { Get-SqlDatabase $sSRCServer $sSRCDatabase|Get-SqlView -Name $o| ` Get-SqlScripter -scriptingOptions $oSO WRITE("GO `n`n`n") }
WMI Date Conversion
USE: To convert the GMT data returned from error logs to a readable date time value.
CLEAR $sHostName = "TUIRA" $aLogName = "Application" $FromDate = [datetime]::today $WMIQuery = "LogFile='$aLogName' and Type='Error' and TimeGenerated>='$FromDate'" Get-WmiObject Win32_NTLogEvent -ComputerName $sHostName ` -Filter "$WMIQuery" ` -ErrorAction SilentlyContinue | ` SELECT SourceName, ` @{Name = "TimeGenerated"; EXPRESSION = {$_.ConvertToDateTime($_.TimeGenerated)}}, ` Message
Top 25 Most Expensive Stored Procedures
USE: These queries will display the top 25 most expensive stored procedures that are still in the cache.
SET NOCOUNT ON --[ REVIEW MOST EXPENSIVE TOP 25 CACHED STORED PROCEDURES ] --[ LOGICAL READS RELATE TO MEMORY PRESSURE ] SELECT TOP(25) A.name [SP Name] , B.total_logical_reads [TotalLogicalReads] , B.total_logical_reads / B.execution_count [AvgerageLogicalReads] , B.execution_count , ISNULL(B.execution_count / DATEDIFF(SECOND, B.cached_time, GETDATE()), 0) [Calls/Second] , B.total_elapsed_time , B.total_elapsed_time / B.execution_count [AverageElapsedTime] , B.cached_time FROM sys.procedures A INNER JOIN sys.dm_exec_procedure_stats B ON A.[object_id] = B.[object_id] WHERE B.database_id = DB_ID() ORDER BY B.total_logical_reads DESC; --[ REVIEW MOST EXPENSIVE TOP 25 CACHED STORED PROCEDURES ] --[ PHYSICAL READS RELATE TO IO PRESSURE ] SELECT TOP(25) A.name [SP Name] , B.total_physical_reads [TotalphysicalReads] , B.total_physical_reads / B.execution_count [AvgeragePhysicalReads] , B.execution_count , ISNULL(B.execution_count / DATEDIFF(SECOND, B.cached_time, GETDATE()), 0) [Calls/Second] , B.total_elapsed_time , B.total_elapsed_time / B.execution_count [AvgerageElapsedTime] , B.cached_time FROM sys.procedures A INNER JOIN sys.dm_exec_procedure_stats B ON A.[object_id] = B.[object_id] WHERE B.database_id = DB_ID() ORDER BY B.total_physical_reads DESC;
PowerShell and Outlook
This script was written by ed wilson at Use PowerShell to Data Mine Your Outlook Inbox
CLEAR Function Get-OutlookInBox { Add-type -assembly "Microsoft.Office.Interop.Outlook" | out-null $olFolders = "Microsoft.Office.Interop.Outlook.olDefaultFolders" -as [type] $outlook = new-object -comobject outlook.application $namespace = $outlook.GetNameSpace("MAPI") $folder = ($namespace.getDefaultFolder($olFolders::olFolderInBox)).folders|WHERE {$_.Name -eq "DDUMP"} $folder.items | Select-Object -Property Subject, ReceivedTime, Importance, SenderName } #end function Get-OutlookInbox Get-OutlookInBox |where { $_.ReceivedTime -gt [datetime]"6/25/12"} | sort importance
Script Out All Indexes and Primary Keys from a Database
USE: To generate T-SQL code for all indexes and PK constraints from a database.
CLEAR $sSRCServer = "TPW520" $sSRCDatabase = "iSQLPS" $oSO = New-SqlScriptingOptions $oSO.ClusteredIndexes = $true $oSO.Indexes = $true $oSO.NonClusteredIndexes = $true $oSO.IncludeIfNotExists = $true $oTables = Get-SqlDatabase $sSRCServer $sSRCDatabase|Get-SqlTable foreach ($oTable in $oTables){ $oT = $oTable.Indexes foreach ($oIndex in $oT) { $DDL = Get-SqlScripter $oIndex -scriptingOptions $oSO "$DDL `nGO `n`n" } }
Create Time String
USE: This function will return a string representation of time.
CREATE FUNCTION [dbo].[fnCreateTimeString] ( @seconds int) RETURNS varchar(75) AS BEGIN DECLARE @h int, @m int, @s int, @secs int, @BuildDate varchar(75), @hour varchar(75), @minute varchar(50), @second varchar(25) SELECT @secs = @seconds SELECT @h = @secs / 3600 SELECT @secs = @secs - (@h * 3600) SELECT @m = @secs / 60 SELECT @secs = @secs - (@m * 60) SELECT @s = @secs SELECT @second = CASE WHEN @s = 1 THEN CAST(@s AS varchar) +' second' ELSE CAST(@s AS varchar) +' seconds' END SELECT @minute = CASE WHEN @m = 1 THEN CAST(@m AS varchar) +' minute with '+ @second ELSE CAST(@m AS varchar) +' minutes with '+ @second END SELECT @hour = CASE WHEN @h = 1 THEN CAST(@h AS varchar) +' hour '+ @minute + @second ELSE CAST(@h AS varchar) +' hours '+ @minute + @second END IF @h = 0 BEGIN IF @m = 0 BEGIN SELECT @BuildDate = @second END ELSE BEGIN SELECT @BuildDate = @minute END END ELSE BEGIN SELECT @BuildDate = @hour END RETURN CONVERT(varchar(75), @BuildDate) END GO
Index Information
USE: To display index information.
SET NOCOUNT ON SELECT DB_NAME(IPS.database_id) AS [database_name] , OBJECT_NAME(IPS.object_id, IPS.database_id) AS [object_name] , QUOTENAME(I.name) [index_name] , CASE WHEN avg_fragmentation_in_percent < 10 THEN 'LOW' WHEN avg_fragmentation_in_percent < 30 THEN 'MEDIUM' WHEN avg_fragmentation_in_percent < 50 THEN 'HIGH' ELSE 'EXTREME' END [fragmentation_indicator] , IPS.index_id , IPS.index_type_desc , IPS.avg_fragmentation_in_percent , IPS.fragment_count , IPS.avg_fragment_size_in_pages , IPS.page_count FROM sys.dm_db_index_physical_stats (DB_ID(), NULL, NULL, NULL , 'LIMITED') AS IPS INNER JOIN sys.indexes I ON IPS.object_id = I.object_id AND IPS.index_id = I.index_id ORDER BY [object_name], [index_name] GO