Shrink the Unshrinkable SQL Transaction Log

Various reasons may cause SQL Server to get in a rut and not empty the transaction log of a database. In my case, our database backups were failing without our knowledge for several weeks, so the backups were never successful, and the transaction logs of a few databases grew so large that the backup process would still not clear out the transaction log. In one case, we had a 187MB database with a 37GB transaction log!

The insanity had to stop! A handful of databases like this would put us over the top on that particular server's hard drive storage.

The SQL Server GUI for shrinking the database rendered no effect, and even using the DBCC SHRINKFILE command was not working.

The key, as explained by Pinal Dave, is to run the SHRINKFILE command twice, with an explicit backup log truncation in between both runs. This code here will get you up and running:

SQL:
  1. DBCC SHRINKFILE("MyDatabase_Log", 1)
  2. BACKUP LOG MyDatabase WITH TRUNCATE_ONLY
  3. DBCC SHRINKFILE("MyDatabase_Log", 1)

This freed up dozens of gigabytes on our server.

Using SQL to Retrieve SQL

At work, someone made a request that required me to look through potentially hundreds of views in dozens of databases on our SQL Server. I certainly didn't want to examine each one at a time. How could I speed up this process with code?

Well, you can find all of your views by querying the sysobjects table, and you can retrieve the SQL behind the views by querying the syscomments table. Something like this works well:

SQL:
  1. SELECT RTrim(sysobjects.name) AS ViewName,
  2.        RTrim(syscomments.text) AS ViewSQL
  3. FROM   sysobjects JOIN syscomments
  4. ON     syscomments.id=sysobjects.id
  5. WHERE  sysobjects.xtype='V' AND sysobjects.category=0

This will retrieve the SQL code and names of all the views in the current database. This simple query is the heart of the solution. But I would like to retrieve this information for all of the databases.

Well, it's easy enough to get a list of all the databases. The sysdatabases table in the master database has that list. You can query that table, perhaps filtering out some of the system or sample databases included with SQL Server:

SQL:
  1. SELECT name FROM master.dbo.sysdatabases
  2. WHERE name NOT IN ('tempdb','master','msdb','pubs','model')
  3. ORDER BY name

Now just combine this information. To accomplish this, we'll build a stored procedure that will create a temporary table, loop through the databases and query each one for its views, insert the view information into the temporary table, and return the temporary table.

Something like this will do the trick:

SQL:
  1. CREATE PROC dbo.selectViews   AS
  2. BEGIN
  3.  
  4. -- Vars
  5. DECLARE @dbname sysname
  6.  
  7. -- Temp Table
  8. CREATE TABLE #Results
  9. (  
  10.   DatabaseName varchar(200),
  11.   ViewName varchar(200),
  12.   ViewText nvarchar(4000)
  13. )
  14.  
  15. -- Loop Thru the Databases.
  16. DECLARE dbnames_cursor CURSOR
  17. FOR
  18.   SELECT name FROM master.dbo.sysdatabases
  19.   WHERE name NOT IN ('tempdb','master','msdb','pubs','model')
  20.   ORDER BY name
  21. OPEN dbnames_cursor
  22. FETCH NEXT FROM dbnames_cursor INTO @dbname
  23. WHILE (@@FETCH_STATUS <> -1)
  24. BEGIN
  25.   IF (@@FETCH_STATUS <> -2)
  26.   BEGIN   
  27.     -- Grab the Views of this Database and Put them in the Temp Table.
  28.     SET @dbname = RTRIM(@dbname)
  29.     INSERT INTO #Results
  30.     EXECUTE
  31.     (
  32.       'SELECT '''+@dbName+''' as DatabaseName, ' +
  33.       'RTrim(' + @dbname + '.dbo.sysobjects.name) as ViewName, ' +
  34.       'RTrim(' + @dbname + '.dbo.syscomments.text) as ViewText ' +
  35.       'FROM ' + @dbName + '.dbo.sysobjects join ' + @dbName + '.dbo.syscomments ' +
  36.       'ON ' + @dbName + '.dbo.syscomments.id=' + @dbName + '.dbo.sysobjects.id ' +
  37.       'WHERE ' + @dbname + '.dbo.sysobjects.xtype=''V'' and ' + @dbname + '.dbo.sysobjects.category=0 '
  38.     )
  39.   END
  40.   FETCH NEXT FROM dbnames_cursor INTO @dbname
  41. END
  42. CLOSE dbnames_cursor
  43. DEALLOCATE dbnames_cursor
  44. SELECT * FROM #Results order by DatabaseName, ViewName
  45. DROP TABLE #Results
  46.  
  47. END
  48. --

Now just execute the stored procedure and review its output.

SQL:
  1. exec selectViews

This portion of the solution just retrieves the data. After writing this, I developed a really short and simple ColdFusion application that would output the database name, view name, and SQL to a table, and used some simple JavaScript to make it easier to search and filter the views. The client-side methods used to view and work with the data are obviously up to you.

  Theme Brought to you by Directory Journal and Elegant Directory.