Run a Stored Procedure when SQL Server starts

Recently I needed to setup a SQL Server box so it had access to a mapped drive to support a legacy application. I created the below stored procedure, which utilises the subst command. to get this done.

CREATE PROCEDURE usp_mapDDrive
AS
BEGIN
	EXEC master.dbo.xp_cmdshell 'Subst d: c:\', no_output;
END
GO

I needed a way of ensuring this mapped drive was always available to SQL Server. After considering various ways of doing this I settled with sp_procoption. This is a neat little system stored procedure that you can use to execute a user proc at startup. Setting this up is easy.

exec sp_procoption @ProcName = usp_mapDDrive,
     @OptionName = 'STARTUP',
     @OptionValue = 'ON';

Turning it off again is simple.

exec sp_procoption @ProcName = usp_mapDDrive,
     @OptionName = 'STARTUP',
     @OptionValue = 'OFF';

Two caveats; you must create your stored procedure in the master database and it cannot contain input or output parameters.

Get TFL Tube data with Powershell

The London Datastore has loads of datasets available that we can use for free. One of the datasets available is a list of TFL Station Locations. The station location feed is a geo-coded KML feed of most of London Underground, DLR and London Overground stations. Here’s Powershell script that will extract this data from a url and write it to a pipe-delimited file ready for import into the database of your choice.

?View Code POWERSHELL
$xml = New-Object XML
$url = "http://www.tfl.gov.uk/tfl/syndication/feeds/stations.kml"
$csvFile = "$env:UserProfile\Desktop\tfl_tubes.csv";
 
# Empty file if it already exists
Set-Content -Path $csvFile $null;
# Add headers to file
Add-Content -Path $csvFile "Station|Address|Coordinates";
 
# Load the xml
$xml.Load($url);
$stations = $xml.kml.Document.Placemark;
 
foreach($station in $stations)
{
	$name = $station.name;
	$description = $station.description;
	$coordinates = $station.Point.coordinates
	# This data needs cleaning a bit
	$name = $name.Trim();
	$description = $description.Trim();
	$coordinates = $coordinates.Trim();
	# Ad line to the csv file
	Add-Content -Path $csvFile "$name|$description|$coordinates";
}

After running the script check your desktop for a file called tfl_tubes.csv which should look something like below.

TFL Station Locations csv data file

Free Database Sync Tools

I’m a big fan of Redgate SQL Compare but it’s been good to see the arrival of a few free alternatives. Life previous to these tools really does seem like the stone age now eliminating those "oh $h**, I forgot about that!" moments. I’d always go for Redgate every time but, if you don’t have it in your budget, these tools may be worth checking out.

OpenDBDiff

This is an open source project hosted on Codeplex that has database compare and, crucially, synchronisation abilities. I’ve used this tool in a few production deployments and have found it useful. I don’t fully trust it yet, it took a while to trust Redgate, but I haven’t encountered any major difficulties yet. I’ve had issues with synchronisation scripts and dependency ordering but nothing I have found too frustrating.

The user interface is basic, but well designed, and it’s easy to get productive straight away.

OpenDBDiff Main Screen

You can’t stay within the tool to synchronise your databases but, as there is a non-functional "Actions Report" tab, it seems this is on the roadmap.

Open DBDiff can synchronize the following object types.

  • Tables (including Table Options like vardecimal, text in row, etc.)
  • Columns (including Computed Columns, XML options, Identities, etc.)
  • Constraints
  • Indexes (and XML Indexes)
  • XML Schemas
  • Table Types
  • User Data Types (UDT)
  • CLR Objects (Assemblies, CLR-UDT, CLR-Stored Procedure, CLR-Triggers)
  • Triggers (including DDL Triggers)
  • Synonyms
  • Schemas
  • File groups
  • Views
  • Functions
  • Store Procedures
  • Partition Functions/Schemes
  • Users
  • Roles

OpenDBDiff Syncronisation Script

I did have trouble connecting to different instances of SQL Server on the same machine. This shouldn’t matter much for production use as we’d normally be synchronising different boxes.

Starinix Database Compare

This is a freeware tool that is able to compare MySQL, Access and SQL Server databases and any combinations thereof. Not sure this would be useful in my case but I guess someone might do (I can already tell you your MySQL and SQL Server procedures do not match!).

Starinix Database Compare

Starinix Database Compare

Key features

  • Take snapshots of the structure of the database and review it later.
  • Snapshots can be saved anywhere, allowing you to take snapshots of any database for reviewing later.
  • Compare two online database connections.
  • Compare an online database connection to an off-line snapshot.
  • Compare two off-line snapshots.
  • Use integrated or user based security.
  • Compares: views, constraints, stored procedures, functions, tables and fields.
  • Compare Access database to an SQL Server database.
  • Compare Access database to a MySQL database.
  • Compare Access database to another Access database.
  • Compare MySQL Database to an SQL Server Database.
  • Compare SQL Server 2000 to SQL Server 2005.

I’ve done some quick testing of this tool and it does seem to be accurate, spotting all of my edits made to tables, procedures and functions. The interface is ok but does need some polish. For example, it would be a nice touch for the "SQL View" scroll bar to scroll both the source and destination views. Identifying differences to large procedures isn’t as easy as it should be but clicking "Script Comparison" launches an external diff tool that does a better job.

Starinix Procedure Compare

This is strictly a compare tool, meaning there are no synchronisation abilities, so you’ll have to do a little leg work to implement any needed changes.

Overall there’s no clear winner for me out of these two free tools. Both products have easy to use GUIs. OpenDBDiff clearly has the edge as a synchronisation tool but Starinix has a few nice touches like the external diff tool and the snapshot feature.

Console input with Powershell

I’m looking at building some Powershell scripts that can accept user input to perform different tasks with a wizard style interface. As it happens this is fairly easily achieved with the Read-Host cmdlet. Here’s a quick script showing how such a powershell script may look.

?View Code POWERSHELL
function mainMenu()
{
	Clear-Host;
	Write-Host "============";
	Write-Host "= MAINMENU =";
	Write-Host "============";
	Write-Host "1. Press '1' for this option";
	Write-Host "2. Press '2' for this option";
	Write-Host "3. Press '3' for this option";
	Write-Host "4. Press '4' for this option";
}
 
function returnMenu($option)
{
	Clear-Host;
	Write-Host "You chose option $option";
	Write-Host "Press any key to return to the main menu.";
	$host.UI.RawUI.ReadKey("NoEcho,IncludeKeyDown");
}
 
do
{
	mainMenu;
	$input = Read-Host "Enter a number for an option or type `"quit`" to finish."
	switch ($input)
	{
		"1"
		{
			returnMenu $input;
		}
		"2"
		{
			returnMenu $input;
		}
		"3"
		{
			returnMenu $input;		
		}
		"4"
		{
			returnMenu $input;		
		}
		"quit"
		{
			# nothing
		}
		default
		{
			Clear-Host;
			Write-Host "Invalid input. Please enter a valid option. Press any key to continue.";
			$host.UI.RawUI.ReadKey("NoEcho,IncludeKeyDown");
		}
	}
 
} until ($input -eq "quit");
 
Clear-Host;

powershell console input menu

powershell console input menu 1

SSIS: Make your output files dynamic part 2

A few weeks ago I blogged about my attempts to make dynamic output files in ssis. The idea here was to make an ssis package, producing a text file output, that would cope with complete changes to the data source. If you wanted to add a column all you needed to do was change the stored procedure definition that the data was derived from. While this was functional it did have a couple of issues.

  • The Script Task seems to buckle for XML data types giving an unsupported conversion error. (Use the Person.Contact table in the AdventureWorks database to view this issue).
  • The package does not seem to cope with a large number of rows. Execution appears to freeze for datasets containing somewhat over 1,000 rows. (Use the Person.Address table in the AdventureWorks database to view this issue). I’m wondering if this is due to size limitation with the System.Object type.

Here’s quick run through of how I resolved the issues. The basic idea was to do all of the data access in VB.Net rather than using an Execute SQL Task to store a resultset in a System.Object variable.

Open BIDS and create a new Integration Services Project.

First add a string variable to the project as illustrated below.

ssis filename variable

Set the EvaluateAsExpression property to true and enter the following expression.

 (DT_WSTR,4)YEAR(GETDATE()) + "_"
    + RIGHT("0" + (DT_WSTR,2)MONTH(GETDATE()), 2) + "_"
    + RIGHT("0" + (DT_WSTR,2)DAY( GETDATE()), 2) + "_"
    + RIGHT("0" + (DT_WSTR,2)DATEPART("hh", GETDATE()), 2) + "_" + RIGHT("0" + (DT_WSTR,2)DATEPART("mi", GETDATE()), 2) + "_" + RIGHT("0" + (DT_WSTR,2)DATEPART("ss", GETDATE()), 2) + ".txt"

This will provide us with a datetime stamped filename looking similar to 2010_02_20_15_40_36.txt.

Next add another string variable called query and enter a statement to execute a stored procedure that returns a resultset. Any one will do as the package should be agnostic to the results.

ssis query variable

I’m using the below stored procedure running against the AdventureWorks sample database.

USE [AdventureWorks]
GO
/****** Object:  StoredProcedure [dbo].[usp_test]    Script Date: 02/20/2010 15:48:49 ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
-- =============================================
-- Author:		Rhys Campbell
-- Create date: 2010-02-20
-- Description:	Test proc
-- =============================================
ALTER PROCEDURE [dbo].[usp_test]
AS
BEGIN
	-- SET NOCOUNT ON added to prevent extra result sets from
	-- interfering with SELECT statements.
	SET NOCOUNT ON;
 
    	-- Insert statements for procedure here
	SELECT *
	FROM Person.Contact;
 
END

Next add an ADO.NET Connection Manager to the project pointing this at the database containing your stored procedure. Rename the connection manager to DBConnection as we will be referencing this by name in code later.

Drop a Script Task onto the designer and edit its properties. Add the filename and query variables to the ReadOnlyVariables property.

ssis script_task properties

Set the script language to Microsoft Visual Basic 2008 and then click "Edit Script". Paste the below code into the editor.

Imports System
Imports System.Data
Imports System.Math
Imports Microsoft.SqlServer.Dts.Runtime

 _
 _
Partial Public Class ScriptMain
	Inherits Microsoft.SqlServer.Dts.Tasks.ScriptTask.VSTARTScriptObjectModelBase

	Enum ScriptResults
		Success = Microsoft.SqlServer.Dts.Runtime.DTSExecResult.Success
		Failure = Microsoft.SqlServer.Dts.Runtime.DTSExecResult.Failure
	End Enum

    Public Sub Main()

        ' Get the datetime stamped filename
        Dim filename As String = Dts.Variables("filename").Value.ToString
        ' Get the user desktop directory. We'll write the output file to here
        ' You probably want to use some other location for production purposes
        Dim userProfileDir As String = Environment.GetFolderPath(Environment.SpecialFolder.DesktopDirectory).ToString

        ' Get the Connection string. Must be called "DBConnection"
        Dim connStr As String = Me.Dts.Connections.Item("DBConnection").ConnectionString.ToString

        ' Get the query set in the ssis variables
        Dim query As String = Dts.Variables("query").Value.ToString

        ' Setup DB access stuff
        Dim con As SqlClient.SqlConnection = New SqlClient.SqlConnection(connStr)
        Dim comm As SqlClient.SqlCommand = New SqlClient.SqlCommand(query, con)
        Dim reader As SqlClient.SqlDataReader

        ' Open the connection
        con.Open()

        ' Get results
        reader = comm.ExecuteReader()

        ' temp variable to hold lines
        Dim line As String = ""

        ' Get the column names from the resultset
        For index As Integer = 0 To (reader.FieldCount - 1)
            line &= reader.GetName(index) & "|"
        Next

        ' remove last pipe
        line = line.Substring(0, line.Length - 1)

        ' write the column headers to the text file
        appendToTextFile(userProfileDir & "\" & filename, line)

        ' Write each row in the resultset to the text file
        While reader.Read()

            line = ""
            ' Add each column value to line
            For index As Integer = 0 To (reader.FieldCount - 1)
                ' Removing any pipes and newline to avoid screwing up our file
                line &= reader.GetValue(index).ToString.Replace("|", "").Replace(ControlChars.CrLf, vbNullString) & "|"
            Next

            ' remove last pipe and add a new line
            line = line.Substring(0, line.Length - 1)

            ' write the line to the output file
            appendToTextFile(userProfileDir & "\" & filename, line)

        End While

        ' clean up
        con.Close()
        comm = Nothing
        reader = Nothing
        con = Nothing

        Dts.TaskResult = ScriptResults.Success

    End Sub
    Public Sub appendToTextFile(ByVal file As String, ByVal line As String)

        ' Create a writer object that appends to a text file if it exists
        Dim objWriter As New System.IO.StreamWriter(file, True)
        objWriter.WriteLine(line)
        objWriter.Close()
        objWriter = Nothing

    End Sub

End Class

Now execute the package and check your desktop for the output file. My output file looked like below.

ssis output file

Now alter your stored procedure so it returns a different resultset. Here’s another one I used in the AdventureWorks database.

USE [AdventureWorks]
GO
/****** Object:  StoredProcedure [dbo].[usp_test]    Script Date: 02/20/2010 19:20:55 ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
-- =============================================
-- Author:		Rhys Campbell
-- Create date: 2010-02-20
-- Description:	Test proc
-- =============================================
ALTER PROCEDURE [dbo].[usp_test]
AS
BEGIN
	-- SET NOCOUNT ON added to prevent extra result sets from
	-- interfering with SELECT statements.
	SET NOCOUNT ON;
 
    -- Insert statements for procedure here
	SELECT *
	FROM Person.Address;
 
END

Run the package again and you should find another output file on your desktop.

ssis output file 2

I’ve not thoroughly tested this code but it appears my initial issues have been resolved. This method does take significantly longer to write the output file than the traditional data flow method. This shouldn’t be a huge issue for the purposes I’m thinking of using it in so I should be soon deploying this into a production environment.

Breaking my Non-Equi Join cherry

There’s few SQL techniques you seem to keep in the cupboard gathering dust. I don’t think I’ve ever needed to use RIGHT JOIN outside of the classroom. I can recall using FULL OUTER JOIN, just once, to show an employer how not-in-sync their "integrated system" was. Today I broke my professional Non-Equi JOIN cherry!

I basically had one table of appointments and another table providing appointment banding by a date range.  The banding wasn’t consistent, spanning weeks and months,  so there was no possibility of using an equi-join or doing anything with datetime arithmetic. Perhaps that non-equi join thing I remember reading in the textbook will do?

Here’s a quick run-through of a similar situation. Create some tables and insert some test data. (This first example is in SQL Server 2005)

CREATE TABLE dbo.Appointments
(
	AppointId INTEGER IDENTITY (1,1) NOT NULL PRIMARY KEY CLUSTERED,
	AppointmentDateTime DATETIME NOT NULL,
	FirstName VARCHAR(50) NOT NULL,
	LastName VARCHAR(50) NOT NULL,
	Reason VARCHAR(50) NULL
);
GO
 
CREATE TABLE AppointmentBands
(
	AppointBandId INTEGER IDENTITY(1,1) NOT NULL PRIMARY KEY CLUSTERED,
	StartDateTime DATETIME NOT NULL,
	EndDateTime DATETIME NOT NULL
);
GO
 
-- Insert Test Data
INSERT INTO dbo.Appointments
(
	AppointmentDateTime,
	FirstName,
	LastName,
	Reason
)
SELECT '2010-02-18T17:00:00',
	   'Rhys',
	   'Campbell',
	   'Eye Test'
UNION ALL
SELECT '2010-02-23T12:00:00',
	   'John',
	   'Smith',
	   'Ear Test'
UNION ALL
SELECT '2010-02-28T14:00:00',
	   'Frank',
	   'Zappa',
	   'Eye Test';
GO
 
INSERT INTO dbo.AppointmentBands
(
	StartDateTime,
	EndDateTime
)
SELECT '2010-02-18T00:00:00',
	   '2010-02-22T23:59:59'
UNION ALL
SELECT '2010-02-23T00:00:00',
	   '2010-02-27T23:59:59'
UNION ALL
SELECT '2010-02-28T00:00:00',
	   '2010-03-01T23:59:59';

I needed to identify which appointment band each record belonged to. It’s a trivial example here, but the real life situation involved hundreds of thousands of appointments and a few thousand appointment bands. The solution involved using a BETWEEN in the join clause.

SELECT app.*, band.AppointBandId
FROM dbo.Appointments app
INNER JOIN dbo.AppointmentBands band
	ON app.AppointmentDateTime BETWEEN band.StartDateTime AND band.EndDateTime;

appointments non equi join

Such a simple, elegant, single query solution. Perhaps we need to dust these things off from time-to-time?

Here’s the same example, a’la MySQL

CREATE TABLE Appointments
(
	AppointId INTEGER AUTO_INCREMENT NOT NULL PRIMARY KEY,
	AppointmentDateTime DATETIME NOT NULL,
	FirstName VARCHAR(50) NOT NULL,
	LastName VARCHAR(50) NOT NULL,
	Reason VARCHAR(50) NULL
);
 
CREATE TABLE AppointmentBands
(
	AppointBandId INTEGER AUTO_INCREMENT NOT NULL PRIMARY KEY,
	StartDateTime DATETIME NOT NULL,
	EndDateTime DATETIME NOT NULL
);
 
-- Insert Test Data
INSERT INTO Appointments
(
	AppointmentDateTime,
	FirstName,
	LastName,
	Reason
)
SELECT '2010-02-18T17:00:00',
	   'Rhys',
	   'Campbell',
	   'Eye Test'
UNION ALL
SELECT '2010-02-23T12:00:00',
	   'John',
	   'Smith',
	   'Ear Test'
UNION ALL
SELECT '2010-02-28T14:00:00',
	   'Frank',
	   'Zappa',
	   'Eye Test';
 
INSERT INTO AppointmentBands
(
	StartDateTime,
	EndDateTime
)
SELECT '2010-02-18T00:00:00',
	   '2010-02-22T23:59:59'
UNION ALL
SELECT '2010-02-23T00:00:00',
	   '2010-02-27T23:59:59'
UNION ALL
SELECT '2010-02-28T00:00:00',
	   '2010-03-01T23:59:59';
SELECT app.*, band.AppointBandId
FROM Appointments app
INNER JOIN AppointmentBands band
	ON app.AppointmentDateTime BETWEEN band.StartDateTime AND band.EndDateTime;

MySQL non equi join

The ‘DataSourceView’ with ‘ID’ = ‘Adventure Works DW2008′ doesn’t exist in the collection.

One of my goals for this year has been to learn more about SQL Server Analysis Services. So I’ve bought myself a (very fat) book on the subject and have started to work my way through it. A few days ago I built the first example cube in Chapter two. Coming back to it this evening, I opened the project in BIDS, and was presented with the following error in the cube designer.

ssas designer data source view collection error

I recalled renaming the DataSource View, and have experienced similar issues with renaming objects in SSIS, so I decided to check out the properties of the DataSourceView.

ssas data source view

Mmmm, the ObjectId here has a ‘1′ on the end which implies to me it has been copied and pasted from an original. This info appears to be read only here so I decided to dive into the cubes xml. The xml of a cube can be viewed by right-clicking on it in Solution Explorer and choosing "View Code".

ssas cube xml thumb The DataSourceView with ID = Adventure Works DW2008 doesnt exist in the collection.

I found one reference to Adventure Works DW2008 in this XML between <DataSourceViewId> tags. I changed this to the Id of Adventure Works DW2008 1 as below.

ssas cube xml2 thumb The DataSourceView with ID = Adventure Works DW2008 doesnt exist in the collection.

I then saved the project and went back to the cube designer.

ssas designer data source view error fixed

Excellent. I have repaired my broken cube! Now on with Chapter three!

MySQL clone of sp_spaceused

Following on from yesterdays blog post, a MySQL clone of sp_MsForEachTable, here’s an attempt at a clone of sp_spaceused. There’s a few issues to be aware of involving the storage engine in use. For example the row count is accurate for MyISAM while with InnoDb it seems to be just an estimate. This estimate can vary from execution-to-execution. The procedure derives its information from INFORMATION_SCHEMA.TABLES. To get started create the below procedure in the database you wish to use it in.

DELIMITER $$
 
DROP PROCEDURE IF EXISTS `usp_mysql_spaceused`$$
 
CREATE PROCEDURE `usp_mysql_spaceused`(IN var_tablename VARCHAR(255))
    LANGUAGE SQL
    NOT DETERMINISTIC
    COMMENT 'Clone of MSSQL sp_spaceused'
    BEGIN
	# Based on http://msdn.microsoft.com/en-us/library/ms188776.aspx
	IF var_tablename = '' OR var_tablename IS NULL THEN
 
		-- db info, unallocated_space only reported for InnoDB tables
		SELECT table_schema AS database_name,
		       CONCAT(ROUND(SUM(data_length + index_length + data_free)/ 1024 / 1024, 2), '(MB)') AS database_size,
		       CONCAT(ROUND(SUM(data_free) / 1024 /1024, 2), '(MB)') AS unallocated_space
		FROM information_schema.TABLES
		WHERE table_schema = DATABASE()
		GROUP BY table_schema;
 
		SELECT CONCAT(ROUND(SUM(data_length + index_length + data_free) / 1024 /1024, 2), '(MB)') AS reserved,
		       CONCAT(ROUND(SUM(data_length) / 1024 /1024, 2), '(MB)') AS data,
		       CONCAT(ROUND(SUM(index_length) / 1024 /1024, 2), '(MB)') AS index_size,
		       CONCAT(ROUND(SUM(data_free) / 1024 /1024, 2), '(MB)') AS unused
		FROM information_schema.TABLES
		WHERE TABLE_SCHEMA = DATABASE();
 
	ELSE
 
		-- table info
		-- does the table exist in the current db?
		IF NOT EXISTS (SELECT 1 FROM information_schema.TABLES WHERE TABLE_NAME = var_tablename) THEN
 
			SELECT 'Table does not exist.' AS error;
		ELSE
 
			-- rows is estimate based for InnoDb (often a fair bit out)
			-- MyISAM is accurate
			SELECT var_tablename AS `name`,
			       table_rows AS rows,
			       CONCAT(ROUND(SUM(data_length + index_length + data_free) / 1024 /1024, 2), '(MB)') AS reserved,
			       CONCAT(ROUND(SUM(data_length) / 1024 /1024, 2), '(MB)') AS data,
			       CONCAT(ROUND(SUM(index_length) / 1024 /1024, 2), '(MB)') AS index_size,
			       CONCAT(ROUND(SUM(data_free) / 1024 /1024, 2), '(MB)') AS unused -- InnoDb only MySQL 5.1.28
			FROM information_schema.TABLES
			WHERE TABLE_SCHEMA = DATABASE()
			AND TABLE_NAME = var_tablename;
 
		END IF;
 
	END IF;
 
    END$$
 
DELIMITER ;

Usage is as follows.

Get database size information

To get database level information the procedure should be called with an empty string or NULL.

-- Empty string or null to get db info
CALL usp_mysql_spaceused('');

Two resultset are returned. Similar to the ones described on the sp_spaceused documentation page.

usp_mysql_spaceused resultset 1

usp_mysql_spaceused resultset 2

Get table size information

To get information on the size of a table just call the procedure with the appropriate table name.

CALL usp_mysql_spaceused('City');

usp mysql spaceused table resultset thumb MySQL clone of sp spaceused

MySQL clone of sp_msforeachtable

Many SQL Server DBAs and Developers get a  lot of use out of the undocumented sp_MsForEachTable system stored procedure. Here’s an attempt at creating a functional version for MySQL. The procedure makes use of prepared statements, which do have some limitations, so some uses may not translate across. This is far from production ready so use with lots of caution.

Create the below stored procedure in the database you wish to use it in.

DELIMITER $$
 
DROP PROCEDURE IF EXISTS `usp_mysql_foreachtable`$$
 
CREATE PROCEDURE `usp_mysql_foreachtable`(IN sql_string VARCHAR(1000))
    LANGUAGE SQL
    NOT DETERMINISTIC
    COMMENT 'Functional clone of sp_MsForEachTable'
    BEGIN
 
	DECLARE var_tablename VARCHAR(100);
	DECLARE last_row BIT;
 
	DECLARE table_cursor CURSOR FOR SELECT TABLE_NAME
					FROM information_schema.TABLES
					WHERE TABLE_TYPE = 'BASE TABLE'
					AND TABLE_SCHEMA = DATABASE();
 
	DECLARE CONTINUE HANDLER FOR NOT FOUND SET last_row = 1;
 
	OPEN table_cursor;
	FETCH table_cursor INTO var_tablename;
 
	SET last_row = 0;
	SET @var = '';
 
	lbl_table_cursor: LOOP
 
		SET @qry = REPLACE(sql_string, '?', var_tablename);
 
		PREPARE q FROM @qry;
		EXECUTE q;
		DEALLOCATE PREPARE q;
 
		FETCH table_cursor INTO var_tablename;
		IF last_row = 1 THEN
			LEAVE lbl_table_cursor;
		END IF;
	END LOOP lbl_table_cursor;
 
	CLOSE table_cursor;
 
    END$$
 
DELIMITER ;

Usage is quite simple, just pass in a query with a ‘?’ in place of where the table name should be. I have verified the below use cases function as expected.

Select 5 rows from each table

CALL usp_mysql_foreachtable('SELECT * FROM ? LIMIT 5;');

Count the number of rows in each table

CALL usp_mysql_foreachtable('SELECT ''?'', COUNT(*) AS Rows FROM ?');

Repair all tables in the database

CALL usp_mysql_foreachtable('REPAIR TABLE ?');

Change all tables to use the InnoDb storage engine

CALL usp_mysql_foreachtable('ALTER TABLE ? ENGINE=InnoDB');

Reset the AUTO_INCREMENT to zero in all tables

CALL usp_mysql_foreachtable('ALTER TABLE ? AUTO_INCREMENT = 0');

Managing Index Fragmentation with Powershell

Here’s a Powershell script that can be used to manage index fragmentation in SQL Server databases. The strategy I’ve used in the script is based on a recommendation from Pinal Dave (blog | twitter) in his article  Difference Between Index Rebuild and Index Reorganize Explained with T-SQL Script. Just set the $sqlserver and $database variables to something appropriate for your environment. Enjoy!

?View Code POWERSHELL
# Load SMO
[System.Reflection.Assembly]::LoadWithPartialName('Microsoft.SqlServer.SMO') | Out-Null;
 
# Set sql server and database name here
$sqlserver = "localhost\sql2005";
$database = "AdventureWorks";
 
$srv = New-Object "Microsoft.SqlServer.Management.SMO.Server" $sqlserver;
$db = New-Object ("Microsoft.SqlServer.Management.SMO.Database");
$db = $srv.Databases[$database];
 
# Get table count
$table_count = $db.Tables.Count;
$i = 0;
 
# First script out the drops
foreach($table in $db.Tables)
{
	Write-Progress -Activity "Checking table $table" -PercentComplete (($i / $table_count) * 100) -Status "Processing indexes" -Id 1;
	$i++;
	foreach($index in $table.Indexes)
	{
		$index_name = $index.Name;
		Write-Progress -Activity "Checking table $table" -PercentComplete (($i / $table_count) * 100) -Status "Processing index $index_name" -Id 1;
		# Get the fragmentation stats
		$frag_stats = $index.EnumFragmentation();
 
		# Get the properties we need to work with the index
		$frag_stats | ForEach-Object {
						$Index_Name = $_.Index_Name;
						$Index_Type = $_.Index_Type;
						$Average_Fragmentation = $_.AverageFragmentation;
									};
		Write-Host -ForegroundColor Green "$Index_Type $Index_Name has a fragmentation percentage of $Average_Fragmentation";
 
		# Here we decide what to do based on the level on fragmentation
		if ($Average_Fragmentation -gt 40.00)
		{
			Write-Host -ForegroundColor Red "$Index_Name is more than 40% fragmented and will be rebuilt.";
			$index.Rebuild();
			Write-Host -ForegroundColor Green "$Index_Name has been rebuilt.";
		}
		elseif($Average_Fragmentation -ge 10.00 -and $Average_Fragmentation -le 40.00)
		{
			Write-Host -ForegroundColor Red "$Index_Name is between 10-40% fragmented and will be reorganized.";
			$index.Reorganize();
			Write-Host -ForegroundColor Green "$Index_Name has been reorganized.";
		}
		else
		{
			Write-Host -ForegroundColor Red "$Index_Name is healthy, with $Average_Fragmentation% fragmentation, and will be left alone.";
		}
 
	}
}
Write-Progress -Activity "Finished processing `"$database`" indexes." -PercentComplete 100 -Status "Done" -Id 1;
Start-Sleep -Seconds 2;

AdventureWork Index Management with Powershell