Introduction

This project is all about bringing a 2-D matrix builder to Microsoft SQL Server 2005 (or later). So why do we need this? Consider the following schema:

SQLMatrix1.png

Now, what we would like to get as a query result is the following :

SQLMatrix2.png

Notice that the headers are not columns names but the records available in the Table_B table. So how do you get this natively with Microsoft SQL Server? Well, you can't! Unless you use the SQL Server 2-D Matrix Builder.

This project is all about using the hosted .NET Common Langage Runtime in Microsoft SQL Server. All you have to do is uploading the TheMatrixSqlClr.dll assembly into your SQL Server instance and use it. The next two chapters describe how to install this assembly into your SQL Server instance and how to reuse TheMatrix stored procedure.

This project was built by Pascal Belaud, Developer Evangelist at Microsoft France French Blog (http://blogs.msdn.com/Pascal)). Pascal is also the author of a .NET code generator based on SQL Server 2005 called OlyMars (http://www.olymars.net).

Deployment

-- First you need to enable the use of .NET in your SLQ Server instance
exec sp_configure 'CLR Enabled', 1
reconfigure
GO

-- Since this assembly needs external access, your database needs to be TrustWorthy
Alter Database YourDatabase
Set Trustworthy On
GO

Use YourDatabase
GO

-- You need to deploy the assembly in your SQL Server instance
CREATE ASSEMBLY [TheSQLServerMatrix]
FROM '<Your path to the assembly here>\TheMatrixSqlClr.dll'
WITH PERMISSION_SET=EXTERNAL_ACCESS
GO

-- You need to declare a new User Defined Type
CREATE TYPE [QueryBuilder]
EXTERNAL NAME [TheSQLServerMatrix].[TheMatrixSqlClr.QueryBuilder]
GO

-- You need to declare a new stored procedure
CREATE PROCEDURE [TheMatrix]
  @rowsQuery [QueryBuilder]
, @rowsQuery_ValueMember nvarchar(max)
, @rowsQuery_DisplayMember nvarchar(max)
, @firstColumnHeader nvarchar(max)
, @columnsQuery [QueryBuilder]
, @columnsQuery_ValueMember nvarchar(max)
, @columnsQuery_DisplayMember nvarchar(max)
, @contentQuery [QueryBuilder]
, @contentQuery_ColumnValueMember nvarchar(max)
, @contentQuery_RowValueMember nvarchar(max)
, @contentQuery_DisplayName nvarchar(max)
, @contentQuery_MissingValue sql_variant
, @diagnose bit = 0

AS
EXTERNAL NAME [TheSQLServerMatrix].[TheMatrixSqlClr.StoredProcedures].[TheMatrix]


Usage

Now that the assembly is declared in our instance, we can use TheMatrix stored procedure like this:

-- Let's declare the first column rows
DECLARE @rowsQuery QueryBuilder
DECLARE @rowsQuery_ValueMember nvarchar(max)
DECLARE @rowsQuery_DisplayMember nvarchar(max)
SET @rowsQuery = QueryBuilder::New_QueryText('Select TableA_ID, TableA_Name From dbo.TableA Order by TableA_Name Asc')
SET @rowsQuery_ValueMember = 'TableA_ID'
SET @rowsQuery_DisplayMember = 'TableA_Name'

-- Let's declare the name of the first column header
DECLARE @firstColumnHeader nvarchar(max)
SET @firstColumnHeader = 'HEADER'

-- Let's declare the others columns headers
DECLARE @columnsQuery QueryBuilder
DECLARE @columnsQuery_ValueMember nvarchar(max)
DECLARE @columnsQuery_DisplayMember nvarchar(max)
SET @columnsQuery = QueryBuilder::New_QueryText('Select TableB_ID, TableB_Name From dbo.TableB Order By TableB_Name Asc')
SET @columnsQuery_ValueMember = 'TableB_ID'
SET @columnsQuery_DisplayMember = 'TableB_Name'

-- Let's declare how to fill the matrix content
DECLARE @contentQuery QueryBuilder
DECLARE @contentQuery_ColumnValueMember nvarchar(max)
DECLARE @contentQuery_RowValueMember nvarchar(max)
DECLARE @contentQuery_DisplayName nvarchar(max)
DECLARE @contentQuery_NullValue sql_variant
SET @contentQuery = QueryBuilder::New_StoredProcedure('spContent')
SET @contentQuery_ColumnValueMember = 'TableC_TableB_ID'
SET @contentQuery_RowValueMember = 'TableC_TableA_ID'
SET @contentQuery_DisplayName = 'TableC_Name'
SET @contentQuery_NullValue = '[NOT AVAILABLE]'

-- We want to see how the Matrix is going to execute all this
DECLARE @diagnose bit
SET @diagnose = 1

EXECUTE [TheMatrixDatabase].[dbo].[TheMatrix] 
   @rowsQuery
  ,@rowsQuery_ValueMember
  ,@rowsQuery_DisplayMember
  ,@firstColumnHeader

  ,@columnsQuery
  ,@columnsQuery_ValueMember
  ,@columnsQuery_DisplayMember

  ,@contentQuery
  ,@contentQuery_RowValueMember
  ,@contentQuery_ColumnValueMember
  ,@contentQuery_DisplayName
  ,@contentQuery_NullValue
  ,@diagnose


When you execute this, you get the following resulset :

HEADER TableB_5 TableB_6 TableB_7 TableB_8 TableB_9
TableA_1 TableC_1_5 TableC_1_6 TableC_1_7 TableC_1_8 TableC_1_9
TableA_2 [NOT AVAILABLE] TableC_2_6 TableC_2_7 TableC_2_8 TableC_2_9
TableA_3 TableC_3_5 [NOT AVAILABLE] TableC_3_7 TableC_3_8 TableC_3_9
TableA_4 TableC_4_ TableC_4_6 TableC_4_7 TableC_4_8 TableC_4_9



Notice that the Matrix allows you to call literal SQL queries or stored procedures. Those queries can also have input parameters if needed.

In the previous example, the matrix content is filled with the resultset returned by the spContent stored procedure:

Create Procedure [dbo].[spContent] As

Select TableC_TableA_ID, TableC_TableB_ID, TableC_Name From dbo.TableC



Since we asked for diagnostic information, here is the output :

14/02/2008 11:22:08.46875- The SQL Server 2-D Matrix Builder
Starting TheMatrix call

14/02/2008 11:22:08.484375- Starting calling the Rows query
Query: Select TableA_ID, TableA_Name From dbo.TableA Order by TableA_Name Asc
	Is Stored Procedure: False
	Parameters number: 0
14/02/2008 11:22:08.484375- ValueMember index for [TableA_ID] is: 0
14/02/2008 11:22:08.484375- DisplayMember index for [TableA_Name] is: 1
14/02/2008 11:22:08.484375- Ending calling the Rows query

14/02/2008 11:22:08.484375- Starting calling the Columns query
Query: Select TableB_ID, TableB_Name From dbo.TableB Order By TableB_Name Asc
	Is Stored Procedure: False
	Parameters number: 0
14/02/2008 11:22:08.484375- ValueMember index for [TableB_ID] is: 0
14/02/2008 11:22:08.484375- DisplayMember index for [TableB_Name] is: 1
14/02/2008 11:22:08.484375- Ending calling the Columns query

14/02/2008 11:22:08.484375- Starting initializing the Matrix content
14/02/2008 11:22:08.484375- Ending initializing the Matrix content

14/02/2008 11:22:08.484375- Starting calling the Content query
Query: spContent
	Is Stored Procedure: True
	Parameters number: 0
14/02/2008 11:22:08.484375- ColumnValueMember index for [TableC_TableB_ID] is: 1
14/02/2008 11:22:08.484375- RowValueMember index for [TableC_TableA_ID] is: 0
14/02/2008 11:22:08.484375- DisplayMember index for [TableC_Name] is: 2
14/02/2008 11:22:08.484375- Ending calling the Content query

14/02/2008 11:22:08.484375- Starting SqlMetaData description
14/02/2008 11:22:08.484375- SqlMetaData, name='HEADER', sqlDbType='NVarChar', maxLength=8, precision=0, scale=0
14/02/2008 11:22:08.5- SqlMetaData, name='TableB_5', sqlDbType='NVarChar', maxLength=15, precision=0, scale=0
14/02/2008 11:22:08.5- SqlMetaData, name='TableB_6', sqlDbType='NVarChar', maxLength=15, precision=0, scale=0
14/02/2008 11:22:08.5- SqlMetaData, name='TableB_7', sqlDbType='NVarChar', maxLength=15, precision=0, scale=0
14/02/2008 11:22:08.5- SqlMetaData, name='TableB_8', sqlDbType='NVarChar', maxLength=15, precision=0, scale=0
14/02/2008 11:22:08.5- SqlMetaData, name='TableB_9', sqlDbType='NVarChar', maxLength=15, precision=0, scale=0
14/02/2008 11:22:08.5- Ending SqlMetaData description

14/02/2008 11:22:08.5- Starting sending back the resultset

(4 row(s) affected)
14/02/2008 11:22:08.5- Ending sending back the resultset

14/02/2008 11:22:08.5- Ending TheMatrix call...


Documentation

SQLMatrix3.png

The QueryBuilder Used-Defined Type allows you to specify what query should be executed to fill:
  • The first column rows (Query 1)
  • The columns headers (Query 2)
  • The Matrix content (Query 3)

QueryBuilder.png


Once you have declared a variable of this type, you can invoke any of its static methods using the :: operator. If you intend to call a literal SQL query, use the NewQueryText method. If you plan to use a stored procedure instead, use the NewStoredProcedure method.

-- If you are using a literal SQL query
Declare @MyLiteralSQLQueryBuilder QueryBuilder
Set @MyLiteralSQLQueryBuilder::New_QueryText(N'Select MyColumn1, MyColumn2, MyColumn3 From MyTable1 Inner Join... Where MyColumn4 = @MyParameter')


-- If you are using a stored procedure
Declare @MyStoredProcedureQueryBuilder QueryBuilder
Set @MyStoredProcedureQueryBuilder::New_StoredProcedure(N'MyStoredProcedureName')


If your literal SQL query or your stored procedure need some input parameters, you can supply their values by calling the SetParameter method:

Declare @Value [your type here]
Set @MyLiteralSQLQueryBuilder::SetParameter(N'@MyParameter', @Value)


Declare @Value [your type here]
Set @MyStoredProcedureQueryBuilder::SetParameter(N'@MyParameter', @Value)


For the Matrix content query only, we need to infere the type of the columns that are returned in the resultset. Although, this product tries to infere this type, it might not be suitable in all cases. Therefore, you can help infere this type by calling one of the following method:
  • SetContentType(typeName)
  • SetContentType2(typeName, maxLength)
  • SetContentType3(typeName, precision, scale)

typeName must be one of the System.Data.SqlDbType enumeration value:

SqlDbType.png

Finally, once you have correctly declared the 3 QueryBuilder variables you need, you can call TheMatrix stored procedure.

The last thing we need to check for our three queries is the following:
  • Query 1 and Query 2 need to return two columns (you can return more than two columns but only two columns will be used):
    • one column must be the "Primary Key"
    • one column must be the "Display Value"
  • Query 3 needs to return three columns (you can return more than three columns but only three columns will be used):
    • one column must be the "Row Primary Key"
    • one column must be the "Column Primary Key"
    • one column must be the "Display Value" of the Matrix

We also need to supply two values:
  • The "Display Value" for the first column header
  • The "Display Value" to use if we have a missing value in the Matrix content returned by Query 3

SQLMatrix4.png

Those parameters will be supplied directly to TheMatrix stored procedure call.

More resources on SQL Server 2-D Matrix Builder

An french article is now available here: The SQL Server 2-D Matrix Builder (http://msdn2.microsoft.com/fr-fr/sqlserver/cc197167.aspx)
This article will also be translated in English very soon. Stay tuned!

Last edited Mar 5, 2008 at 9:33 PM by olymars, version 44