This site will work and look better in a browser that supports web standards, but it is accessible to any browser or Internet device.
N U T S H E L L | |
Module | Map |
---|---|
Description | Maps a list or a matrix to an arbitrary DML statement |
Environment | SQL Server 2000/2005 |
Author | Michael Sorens |
Since | CleanCode 0.9.17 |
Map provides a configurable, flexible mapping capability for SQL Server 2000 or later. It allows you to map an array or list onto a DML statement template and invoke the enumerated instances of that statement. If each DML statement instance returns a result set, you may elect to have the result sets individually or to accumulate them into a single result set.
This general-purpose stored procedure enhances the expressive power of SQL by performing some useful array, matrix, and list-processing tasks. For example, with one line of code you can:
Besides the description here, I have also published an article discussing the practical uses of this tool -- Bring Array Mapping Capabilities to SQL -- available on the DevX online magazine.
Map @StmtTemplate, @MainArray, @AuxArray, @Permute, @Accumulate, @AccumulateFilter, @Verbose, @Process where: @StmtTemplate ::= [DML statement] @MainArray ::= "'" { [main item] "," } [main item] "'" | [select statement] @AuxArray ::= "'" { [aux item] "," } [aux item] "'" | [select statement] @Permute ::= "1" to perform all permutations, or "0" to match indices @Accumulate ::= "1" for single result set; "0" for individual result sets @AccumulateFilter ::= [filtering predicate] @Verbose ::= "0"-"3" for no diagnostic output or progressively more @Process ::= "1" to execute; "0" to only show generated statements
Map is particularly handy for reducing multiple result sets into a single result set, whether the source is a series of queries with similarly shaped result sets or a single super query (a la sp_msForEachTable).
(local)
notation connects to your database server, but SQL Server 2005 Express users may need to adjust this. The installation wizard of SQL Server 2005 Express lets you create a default instance or a named instance of SQL Server Express. This Instance Name page of the Books Online reference states: "SQL Server Express always installs a named instance (SQLExpress) unless you select a default instance. This behavior differs from SQL Server 2005, which installs a default instance unless a named instance is selected." If you selected the default instance when you installed the DB, then (local)
correctly specifies your server. However, if you installed as a named instance, you need to change the @SERVER definition in Map.sql to (local)\SQLEXPRESS
.
SELECT count(*) from <MAIN_ARG>to
SELECT count(*) as Count from <MAIN_ARG>
EXEC sp_msForEachTable " select '?' as Name"That query generates many result sets. When accumulating, all the result sets are stored in a temporary table created on the fly, whose definition is inferred from the first result set. Unless that initial result set just happens to have the longest string in that field of any value from all the following results sets, the subsequent result sets attempt to put a string that is too long into the field, yielding an error. You must provide explicit guidance to tell Map that the name field should have room for a longer string, irrespective of how short or how long the value in the first result set is, as in:
EXEC sp_msForEachTable "select convert(varchar(128), '?') as Name"
EXEC sp_msForEachTable "select convert(varchar(128), '?') as Name where '?' like '%Vendor%' "Simply wrapping that with Map and enabling @Accumulate automatically filters out any empty result sets. Using the AdventureWorks database, this returns a single result set with 4 rows:
EXEC Map @StmtTemplate = 'EXEC sp_msForEachTable "select convert(varchar(128), ''?'') as Name where ''?'' like ''%Vendor%'' " ', @Accumulate = 1To use a stored procedure instead of a SELECT statement, you need to do it a bit differently. Recall that when you enable @Accumulate, Map collects its result sets into a temporary table and then emits the contents of that populated table with a simple SELECT statement at the end, e.g.
SELECT * FROM #Map_Temp_Table
You may add a filtering predicate to this statement via the @AccumulateFilter parameter. As an example, this invocation displays only the non-empty tables returned from sp_spaceused:
EXEC Map @StmtTemplate = "execute sp_spaceused <MAIN_ARG>", @MainArray = 'Users, Permissions', @Accumulate = 1, @AccumulateFilter = "rows > 0"
MAIN_ARG
just change the definition of @MAIN_TOKEN
.
CREATE
statements to generate indexes based on existing database structures.
Here is an interesting query I found (see here) to generate the code to recreate all indexes of a database:
SELECT 'CREATE ' + CASE IS_UNIQUE WHEN 1 THEN ' UNIQUE' ELSE '' END + CASE IS_CLUSTERED WHEN 1 THEN ' CLUSTERED' ELSE '' END + ' INDEX [' + INDEX_NAME + '] ON [dbo].[' + TABLE_NAME + ']' + ' (' + COLUMN_LIST + ') ON [' + FILE_GROUP + '];' from vAllIndexesThat statement uses 6 fields to build the query. To use it with Map, that statement becomes the @MainArray parameter instead of the @StmtTemplate parameter. Then just set the @StmtTemplate to <MAIN_ARG> as in:
EXEC Map @StmtTemplate = '<MAIN_ARG>', @MainArray='SELECT ''CREATE '' + CASE IS_UNIQUE WHEN 1 THEN '' UNIQUE'' ELSE '''' END + CASE IS_CLUSTERED WHEN 1 THEN '' CLUSTERED'' ELSE '''' END + '' INDEX ['' + INDEX_NAME + ''] ON [dbo].['' + TABLE_NAME + '']'' + '' ('' + COLUMN_LIST + '') ON ['' + FILE_GROUP + ''];'' from vAllIndexes'
Map 'DBCC checkident(''<MAIN_ARG>'', RESEED, <AUX_ARG>)', 'TableA,TableB' ,'5010,10100'
Map 'select count(*) from <MAIN_ARG>', 'TableA,TableB', null
Map 'SELECT <AUX_ARG> from <MAIN_ARG>', 'TableA,TableB' ,'id1,id2',1
Map 'INSERT INTO TableX VALUES(<MAIN_ARG>, 0, 0)', 'SELECT Type from CustTypes', null
Some concepts are adapted from Eliminating Cursors.