- Sessions that our mentors delivered at various conferences.
- Articles discussing both technical issues and Solid Quality activities.
- Blogs maintained by our mentors.
Since our mentors are based all around the world and speak various languages, we¹ve divided all these resources according to language. Please choose your preferred language by clicking the buttons above
No doubt data modeling is a process where you can add a lot of creativity. No doubt there is not a single solution for a business problem with relational model. However, does this mean that data modeling is more art than science? How much creativity is right?
I would say that database design is definitely not an art. It is based on science. Of course, you need some creativity. But is creativity limited to artists only? In any profession you need some creativity. However, would you buy a system that your company is going to depend on from an »artist«? A good model has predictable behavior. If the behavior is not predictable, it is not because db designing would be art; it is because the designer does not know her/his profession. I think people abuse word "art" just because they do not know about design enough.
I met so many times very »creative« models. There is a whole branch of modelers I call "inventors"; however, do please note that I mean this in a sarcastic way. It is very simple: if you do not know a lot, you are forced to invent. Interestingly, they mention "theory" a lot as well. Basically, they are "improving" the "theory". Typical examples would be people who invent "XML databases", "OO databases" and similar, and by the way they criticize relational model without really understanding it.
However, do we have to follow the established ways, without using brains? There is another kind of modelers I call "theorists". There are modelers who never use their mind and always follow the authorities in the area, explaining they are following the "theory". Note that they typically mix the real theory, which has foundations in science, mathematics, with words of well-known authorities. For example, types 1, 2, and 3 for handling the SCD problem have nothing to do with any theory; they are just proposals by Ralph Kimball, and since Kimball is an authority, this kind of modelers does not dare to use own brains to try to find a better solution.
So what is the right mix of established ways vs. creativity? First of all, you should always use your brains and common sense. A real intellectual doubts in everything. Do please doubt in words of authorities, scientists, politicians… Try to find better solutions. However, use extreme caution before you claim you invented something. It was probably already invented for thousand times; it is probably just you who does not know anything about it. Do not forget: if you don't know, you are doomed on inventing. Or on calling you an "artist".
InfoPath como aplicación es capaz de generar documentos XML que contienen no solo la información también la estructura del mismo.
Utilizando la herramienta XSD.exe es posible generar una clase que facilite manipular dicha información (http://msdn.microsoft.com/en-us/library/bb251017.aspx). Sin embargo, es específica para cada documento.
En muchas oportunidades sucede que se requiere codificar (por ejemplo un flujo de trabajo), para múltiples documentos y formatos.
Para facilitar estas actividades, se me ocurrió definir una clase que administre genéricamente el contenido de documentos InfoPath.
Esta clase recibe un elemento de una biblioteca de documentos InfoPath de SharePoint y carga el documento en memoria para facilitar su manipulación.
Comencemos con la lista de importaciones y variables necesarias.
Imports System.Xml.Serialization Imports System.Xml ''' <summary> ''' Permite manipular el contenido de un documento de InfoPath en algo fácilmente accesible por código ''' </summary> ''' <remarks>Carga el contenido del documento, adjunto a una biblioteca de documentos de MOSS ''' y crea listas fácilmente accesibles tanto de los atributos como de los valores (elementos) ''' </remarks> Public Class InfoPathConverter ''' <summary> ''' Documento conteniendo todo el documento InfoPath ''' </summary> ''' <remarks></remarks> Dim xDoc As XDocument = Nothing ''' <summary> ''' Lista de atributos ''' </summary> ''' <remarks></remarks> Dim atrs As Dictionary(Of String, XAttribute) ''' <summary> ''' lista de valores (elementos) ''' </summary> ''' <remarks></remarks> Dim els As Dictionary(Of String, XElement)
Imports System.Xml.Serialization
Imports System.Xml
''' <summary>
''' Permite manipular el contenido de un documento de InfoPath en algo fácilmente accesible por código
''' </summary>
''' <remarks>Carga el contenido del documento, adjunto a una biblioteca de documentos de MOSS
''' y crea listas fácilmente accesibles tanto de los atributos como de los valores (elementos)
''' </remarks>
Public Class InfoPathConverter
''' Documento conteniendo todo el documento InfoPath
''' <remarks></remarks>
Dim xDoc As XDocument = Nothing
''' Lista de atributos
Dim atrs As Dictionary(Of String, XAttribute)
''' lista de valores (elementos)
Dim els As Dictionary(Of String, XElement)
Una de las características que me suelen ser necesarias es la de asegurarme que existan ciertas propiedades y atributos en el documento ya que son utilizados en la lógica del flujo.
Por ello, la clase expone 2 propiedades para contener los atributos y los campos que son requeridos.
''' <summary> ''' Lista de nombres de atributos requeridos en el documento ''' </summary> ''' <remarks></remarks> Private mAtributes As List(Of String) ''' <summary> ''' Define que atributos son necesarios en el documento para considerarlo válido ''' </summary> ''' <value></value> ''' <returns></returns> ''' <remarks></remarks> Public ReadOnly Property AttributesNeeded() As List(Of String) Get If mAtributes Is Nothing Then mAtributes = New List(Of String) mAtributes.Add("Etapa") End If Return mAtributes End Get End Property Private mFieldsNeeded As List(Of String) ''' <summary> ''' Define que campos son necesarios en el documento para considerarlo válido ''' </summary> ''' <value></value> ''' <returns></returns> ''' <remarks></remarks> Public ReadOnly Property FieldsNeeded() As List(Of String) Get If mFieldsNeeded Is Nothing Then mFieldsNeeded = New List(Of String) End If Return mFieldsNeeded End Get End Property
''' Lista de nombres de atributos requeridos en el documento
Private mAtributes As List(Of String)
''' Define que atributos son necesarios en el documento para considerarlo válido
''' <value></value>
''' <returns></returns>
Public ReadOnly Property AttributesNeeded() As List(Of String)
Get
If mAtributes Is Nothing Then
mAtributes = New List(Of String)
mAtributes.Add("Etapa")
End If
Return mAtributes
End Get
End Property
Private mFieldsNeeded As List(Of String)
''' Define que campos son necesarios en el documento para considerarlo válido
Public ReadOnly Property FieldsNeeded() As List(Of String)
If mFieldsNeeded Is Nothing Then
mFieldsNeeded = New List(Of String)
Return mFieldsNeeded
''' <summary> ''' Carga en las listas el contenido del documento InfoPath ''' </summary> ''' <param name="item">Item de la biblioteca de docuemntos, conteniendo el mismo</param> ''' <remarks>Carga el contenido del documento, adjunto a una biblioteca de documentos de MOSS ''' y crea listas fácilmente accesibles tanto de los atributos como de los valores (elementos) ''' </remarks> Sub getDocumentContent(ByVal item As SPListItem) 'La propiedad File expone el documento InfoPath Dim xtextr As New XmlTextReader(item.File.OpenBinaryStream) 'Se carga entonces el documento desde el Stream xDoc = XDocument.Load(xtextr) 'Obtiene los atributos atrs = (From x As XAttribute In xDoc.Root.Attributes).ToDictionary( _ Of String)(Function(x) x.Name.LocalName.ToUpper) ' Evalúa si todos los atributos requeridos están pesentes For Each s As String In AttributesNeeded Dim lookFor As String = s.ToUpper If (From a As XAttribute In atrs.Values _ Where a.Name.LocalName.ToUpper = lookFor).ToArray.Length < 1 Then Throw New Exception("Formulario inválido: le falta el atributo " & s) End If Next ' Evalúa lo mismo para los campos (Elements) els = (From v As XElement In xDoc.Root.Elements).ToDictionary( _ Of String)(Function(v) v.Name.LocalName.ToUpper) For Each s As String In FieldsNeeded Dim lookFor As String = s.ToUpper If (From a As XElement In els.Values _ Where a.Name.LocalName.ToUpper = lookFor).ToArray.Length < 1 Then Throw New Exception("Formulario inválido: le falta el campo " & s) End If Next End Sub Teniendo el documento en memoria, se pueden exponer los valores y asignarles nuevos con estos métodos: ''' <summary> ''' Cambia el valor de un atributo ''' </summary> ''' <param name="what">Nombre del atributo</param> ''' <param name="newValue">Nuevo Valor</param> ''' <remarks></remarks> Sub ChangeAttribute(ByVal what As String, ByVal newValue As String) If atrs.ContainsKey(what.ToUpper) Then atrs(what.ToUpper).Value = newValue End If End Sub ''' <summary> ''' Obtiene el valor de uhn atributo ''' </summary> ''' <param name="what">Nombre del atirbuto</param> ''' <returns>Contenido del mismo (siempr como cadena de caracteres) o valor nulo si no existe ''' </returns> ''' <remarks></remarks> Function ReadAttribute(ByVal what As String) As String If atrs.ContainsKey(what.ToUpper) Then Return atrs(what.ToUpper).Value Else Return Nothing End If End Function ''' <summary> ''' Cambia el valor de un elemento ''' </summary> ''' <param name="what">Nombre del elemento</param> ''' <param name="newValue">Valor a asignar</param> ''' <remarks></remarks> Sub ChangeValue(ByVal what As String, ByVal newValue As String) If els.ContainsKey(what.ToUpper) Then els(what.ToUpper).Value = newValue End If End Sub ''' <summary> ''' Obtiene le valor de un elemento ''' </summary> ''' <param name="what">Nombre del elemento a leer</param> ''' <returns>Valor del elemento (siempre como cadena de caracteres), o valor nulo si no existe ''' </returns> ''' <remarks></remarks> Function ReadValue(ByVal what As String) As String If els.ContainsKey(what.ToUpper) Then Return els(what.ToUpper).Value Else Throw New KeyNotFoundException("El campo " & what & " no se encuentra en el documento") End If End Function
''' Carga en las listas el contenido del documento InfoPath
''' <param name="item">Item de la biblioteca de docuemntos, conteniendo el mismo</param>
Sub getDocumentContent(ByVal item As SPListItem)
'La propiedad File expone el documento InfoPath
Dim xtextr As New XmlTextReader(item.File.OpenBinaryStream)
'Se carga entonces el documento desde el Stream
xDoc = XDocument.Load(xtextr)
'Obtiene los atributos
atrs = (From x As XAttribute In xDoc.Root.Attributes).ToDictionary( _
Of String)(Function(x) x.Name.LocalName.ToUpper)
' Evalúa si todos los atributos requeridos están pesentes
For Each s As String In AttributesNeeded
Dim lookFor As String = s.ToUpper
If (From a As XAttribute In atrs.Values _
Where a.Name.LocalName.ToUpper = lookFor).ToArray.Length < 1 Then
Throw New Exception("Formulario inválido: le falta el atributo " & s)
Next
' Evalúa lo mismo para los campos (Elements)
els = (From v As XElement In xDoc.Root.Elements).ToDictionary( _
Of String)(Function(v) v.Name.LocalName.ToUpper)
For Each s As String In FieldsNeeded
If (From a As XElement In els.Values _
Throw New Exception("Formulario inválido: le falta el campo " & s)
End Sub
Teniendo el documento en memoria, se pueden exponer los valores y asignarles nuevos con estos métodos:
''' Cambia el valor de un atributo
''' <param name="what">Nombre del atributo</param>
''' <param name="newValue">Nuevo Valor</param>
Sub ChangeAttribute(ByVal what As String, ByVal newValue As String)
If atrs.ContainsKey(what.ToUpper) Then
atrs(what.ToUpper).Value = newValue
''' Obtiene el valor de uhn atributo
''' <param name="what">Nombre del atirbuto</param>
''' <returns>Contenido del mismo (siempr como cadena de caracteres) o valor nulo si no existe
''' </returns>
Function ReadAttribute(ByVal what As String) As String
Return atrs(what.ToUpper).Value
Else
Return Nothing
End Function
''' Cambia el valor de un elemento
''' <param name="what">Nombre del elemento</param>
''' <param name="newValue">Valor a asignar</param>
Sub ChangeValue(ByVal what As String, ByVal newValue As String)
If els.ContainsKey(what.ToUpper) Then
els(what.ToUpper).Value = newValue
''' Obtiene le valor de un elemento
''' <param name="what">Nombre del elemento a leer</param>
''' <returns>Valor del elemento (siempre como cadena de caracteres), o valor nulo si no existe
Function ReadValue(ByVal what As String) As String
Return els(what.ToUpper).Value
Throw New KeyNotFoundException("El campo " & what & " no se encuentra en el documento")
Finalmente ento0nces, con un método Save, se puede guardar en la lista de origen... o en otra. Para ello, se le envía al método el ListItem que contendrá el documento.
''' <summary> ''' Guarda el contenido de los valores y atributos en el documento InfoPath actualizando los mismos ''' </summary> ''' <param name="item"></param> ''' <remarks></remarks> Public Sub Save(ByVal item As SPListItem) item.File.CheckOut() Dim memoryStream As New IO.MemoryStream() Dim sett As New XmlWriterSettings sett.Encoding = System.Text.Encoding.UTF8 sett.OmitXmlDeclaration = False Dim xmlTextWriter As XmlWriter = XmlWriter.Create(memoryStream, sett) xDoc.Save(xmlTextWriter) xmlTextWriter.Flush() memoryStream.Position = 0 item.File.SaveBinary(memoryStream.GetBuffer) item.File.CheckIn("") End Sub
''' Guarda el contenido de los valores y atributos en el documento InfoPath actualizando los mismos
''' <param name="item"></param>
Public Sub Save(ByVal item As SPListItem)
item.File.CheckOut()
Dim memoryStream As New IO.MemoryStream()
Dim sett As New XmlWriterSettings
sett.Encoding = System.Text.Encoding.UTF8
sett.OmitXmlDeclaration = False
Dim xmlTextWriter As XmlWriter = XmlWriter.Create(memoryStream, sett)
xDoc.Save(xmlTextWriter)
xmlTextWriter.Flush()
memoryStream.Position = 0
item.File.SaveBinary(memoryStream.GetBuffer)
item.File.CheckIn("")
Si si, ya sé. quieres verlo completo. Pues aquí está la clase entera. http://blogs.solidq.com/ES/dseara/Archivos/InfoPathConverter.zip
CREATE TABLE [dbo].[Roles](
[RoleID] [uniqueidentifier] NOT NULL,
[RoleName] [nvarchar](260) NOT NULL,
[Description] [nvarchar](512) NULL,
[TaskMask] [nvarchar](32) NOT NULL,
[RoleFlags] [tinyint] NOT NULL,
CONSTRAINT [PK_Roles] PRIMARY KEY NONCLUSTERED
(
[RoleID] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
GO
CREATE UNIQUE CLUSTERED INDEX [IX_Roles] ON [dbo].[Roles]
[RoleName] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF, IGNORE_DUP_KEY = OFF, DROP_EXISTING = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
INSERT [dbo].[Roles] ([RoleID], [RoleName], [Description], [TaskMask], [RoleFlags]) VALUES (N'114e91eb-1868-4cf7-8e9d-f43a19813b7f', N'Browser', N'May view folders, reports and subscribe to reports.', N'0010101001000100', 0)
INSERT [dbo].[Roles] ([RoleID], [RoleName], [Description], [TaskMask], [RoleFlags]) VALUES (N'9bc89cad-3d9a-491d-be02-75be1579af2c', N'Content Manager', N'May manage content in the Report Server. This includes folders, reports and resources.', N'1111111111111111', 0)
INSERT [dbo].[Roles] ([RoleID], [RoleName], [Description], [TaskMask], [RoleFlags]) VALUES (N'0286b7fb-a18b-4fa4-bb59-adb7cf28df73', N'Model Item Browser', N'Allows users to view model items in a particular model.', N'1', 2)
INSERT [dbo].[Roles] ([RoleID], [RoleName], [Description], [TaskMask], [RoleFlags]) VALUES (N'29c8f388-b3f6-4a4d-a7d9-eed72f3c0bd2', N'My Reports', N'May publish reports and linked reports; manage folders, reports and resources in a users My Reports folder.', N'0111111111011000', 0)
INSERT [dbo].[Roles] ([RoleID], [RoleName], [Description], [TaskMask], [RoleFlags]) VALUES (N'5d9fa665-e315-4249-b519-0e3668f3d49d', N'Publisher', N'May publish reports and linked reports to the Report Server.', N'0101010100001010', 0)
INSERT [dbo].[Roles] ([RoleID], [RoleName], [Description], [TaskMask], [RoleFlags]) VALUES (N'e51e9553-4f47-4619-a943-05020a373862', N'Report Builder', N'May view report definitions.', N'0010101001000101', 0)
INSERT [dbo].[Roles] ([RoleID], [RoleName], [Description], [TaskMask], [RoleFlags]) VALUES (N'8ca532d7-7e5c-4f18-8976-d2d4ffd95472', N'System Administrator', N'View and modify system role assignments, system role definitions, system properties, and shared schedules.', N'110101011', 1)
INSERT [dbo].[Roles] ([RoleID], [RoleName], [Description], [TaskMask], [RoleFlags]) VALUES (N'a5f16ea9-fb16-4898-b8f1-9d05351f56de', N'System User', N'View system properties and shared schedules.', N'001010001', 1)
Podemos elegir al generar el script la versión de SQL Server (2000, 2005 o 2008) Como pequeño tirón de orejas , indicar que el ejemplo anterior ha sido generado para "SQL Server 2008" y vemos que no se están utilizando los nuevos constructores de fila para generar los insert.
Quizás el aspecto más importante, por el control que nos permite tener sobre nuestros servidores SQL Server, es el nuevo marco de trabajo de gestión declarativo, que nos permite definir políticas que deben de cumplirse en instancias, bases de datos u objetos, pero esta novedad se analiza en el artículo SQL Server 2008 Declarative Management Framework, por lo que no entraremos a fondo en su estudio, pero que debemos de mencionar al menos.
Otra de las novedades interesantes es la posibilidad de cifrar, de forma transparente a usuarios y aplicaciones, nuestras bases de datos. En SQL Server 2005, se introdujo una arquitectura de cifrado, que nos permitía utilizar funciones para cifrar, utilizando certificados o claves, los datos almacenados en las bases de datos SQL Server 2005. Sin embargo, eso nos exigía establecer procesos que realizasen las operaciones de cifrado y descifrado de datos, y además, con algunas restricciones que fundamentalmente afectaban al rendimiento. En SQL Server 2008, a través de la característica denominada Transparent Data Encryption(TDE), que solventa esas limitaciones, del modo más rápido y eficiente posible: se cifra la base de datos completa. El cifrado de datos con TDE se realiza a nivel de Entrada / Salida, a través del Buffer Pool. Esto quiere decir, que cuando se habilita una base de datos para TDE, se cifran todos los ficheros de dicha base de datos, tanto de datos como de log de transacciones, y el cifrado y descifrado se realizará durante las operaciones de lectura y escritura en disco. La gran ventaja de esta aproximación, como hemos comentado, es que lo hace cien por cien transparente a las aplicaciones, sin embargo, el problema es que los datos en memoria no se encuentran cifrados, y determinadas operaciones, como paginación de Sistema Operativo, o un Dump de memoria, pueden llevar disco los datos sin cifrar. Sin embargo, lo que si se tiene en cuenta, es que cuando habilitamos TDE en una base de datos, automáticamente se configura tempdb con TDE también, para que todos los objetos temporales que sea necesario crear, sean también cifrados. Del mismo modo, tanto las instantáneas, como las copias de seguridad, pueden sacar provecho de esta característica y también se cifrarán. Especial mención, requiere la copia de seguridad. Como ocurre siempre en estos casos, deberemos de disponer de copias de seguridad de los objetos (certificados o claves) que utilicemos con TDE, para que en caso de que necesitemos realizar una restauración, seamos capaces de poder leer los datos. Si no disponemos de esos objetos, no seremos capaces de restaurar una copia de seguridad cifrada con TDE.
Existen algunos cambios, que aunque no son excesivamente relevantes, si merecen mencionarse, en los roles de la base de datos msdb. En primer lugar los roles para la gestión de seguridad de SQL Server Integration Services cambian de nombre y pasan de llamarse db_dtsadmin, db_dtsltduser y db_dtsoperator se han renombrado respectivamente a db_ssisadmin, db_ssisltduser y db_ssisoperator. En el caso de que estemos realizando una actualización desde SQL Server 2005, los roles antigüos se mantendrán y se añadirán como miembros de estos nuevos roles para mantener la compatibilidad.
Además de estos cambios de nomenclatura, aparecen nuevos roles de base de datos, para dar soporte a algunas de las nuevas funcionalidades, especialmente para las nuevas características de gestión. Dispondremos en la base de datos msdb, de los siguientes nuevos roles:
· ServerGroupAdministratorRole y ServerGroupReaderRole, que nos permiten gestionar los grupos de servidores para administración centralizada.
· PolicyAdministratorRole, que nos permite definir los usuarios administradores de la gestión basada en políticas
· dc_admin, dc_operator y dc_proxy, que utilizaremos con el componente data collector
El pasado día 21 de abril en Madrid y el día 22 de abril en Barcelona tuvo lugar el evento Frameworks y herramientas RAD de código abierto y propietario para soluciones .NET. Donde presenté nuestro Solid RAD, una herramienta para el desarrollo rápido de aplicaciones centradas en datos con una arquitectura de dos y tres capas.
Si estáis interesados en Solid RAD Tenéis disponibles unos vídeos en mms://solidq.com/SolidRad .
NOTA: copiad y pegad mms://solidq.com/SolidRad en la barra de direcciones de IE para ver los vídeos.
Como lo prometido es deuda, y como más vale tarde que nuncaJ, aquí van las demos de las novedades de BI para SQL Server 2008.
Novedades SSAS 2008 Demo
Esta demo consta de dos partes:
1. Se hace un breve repaso sobre las nuevas DMVs creadas en 2008 para monitorizar Analisis Services.
2. Se crea una solución SSAS desde cero mostrando los nuevos asistentes para la generación de cubos y dimensiones.
Novedades SSIS 2008 Demo
Esta demo consta de
1. Muestra un ejemplo de cómo trabajar con los nuevos tipos de datos fecha y hora de SQL 2008-03-18.
2. Las novedades para la transformación Lookup y como trabajar con ellas.
3. Un vistazo rápido sobre los conectores ADO.NET
4. Como trabajar con la nueva tarea data profiling y como visionar posteriormente los datos obtenidos a través del data Profile viewer.
Novedades SSRS 2008 Demo
Esta demo hace un repaso por el nuevo diseñador de informes, muestra como trabajar con el nuevo objeto Tablix así como un repaso sobre las mejoras de los gráficos y los nuevos controles gauge.
Un saludo a todos
I have been playing around with the latest SQL Server 2008 CTP while trying to keep both my performance tuning and DBA classes updated with the latest changes. When I review new releases of SQL Server, I tend to evaluate the release by determining which features and functionalities that will help me solve problems that I could not solve or could not solve easily in earlier releases.
One of the problems that did not have easy solutions in earlier versions was the one faced when dealing with large amounts of data. Many times I have incorporated many different strategies to lessen the impact of large amounts of data on queries by trying to lessen the amount of data contained within the indexes on a table. This often involved partitioning the tables in SQL Server 2005 or archiving data in earlier versions. Any method that worked to create a smaller set of active data to be queried during each SELECT statement and a smaller index to be maintained during data modifications.
One of the great new performance improvements in SQL Server 2008 is the new filtered index. Filtered indexes basically allow you to create a filter on an index – think WHERE clause. The index B-Tree will only contain the rows of data that meet the filtering criteria. This allows you to reduce the amount of data contained in an index which means that you are also reducing the data affected in an index by data modifications.
I am going to walk through a very small and simple demo that illustrates the impact of filtered indexes.
First let’s create a demo table with some demo data
SET NOCOUNT ON
--Create demo table
IF OBJECT_ID('TestTable','table') IS NOT NULL
DROP TABLE TestTable
CREATE TABLE TestTable
(colID INT IDENTITY(1,1)
,colDate DATETIME
,colSession INT
,colDesc VARCHAR(100)
)
--Insert data into table
DECLARE @loop INT
SET @loop = 1000
WHILE @loop > 0
BEGIN
INSERT INTO TestTable (colDate,colSession,colDesc ) VALUES ('20070301',1,'20070301-1' )
INSERT INTO TestTable (colDate,colSession,colDesc ) VALUES ('20070401',1,'20070401-1' )
INSERT INTO TestTable (colDate,colSession,colDesc ) VALUES ('20070501',1,'20070501-1' )
INSERT INTO TestTable (colDate,colSession,colDesc ) VALUES ('20070601',1,'20070601-1' )
INSERT INTO TestTable (colDate,colSession,colDesc ) VALUES ('20070701',1,'20070701-1' )
INSERT INTO TestTable (colDate,colSession,colDesc ) VALUES ('20070801',1,'20070801-1' )
INSERT INTO TestTable (colDate,colSession,colDesc ) VALUES ('20070901',1,'20070901-1' )
INSERT INTO TestTable (colDate,colSession,colDesc ) VALUES ('20071001',1,'20071001-1' )
INSERT INTO TestTable (colDate,colSession,colDesc ) VALUES ('20071101',1,'20071101-1' )
INSERT INTO TestTable (colDate,colSession,colDesc ) VALUES ('20071201',1,'20071201-1' )
INSERT INTO TestTable (colDate,colSession,colDesc ) VALUES ('20080101',1,'20080101-1' )
INSERT INTO TestTable (colDate,colSession,colDesc ) VALUES ('20080201',1,'20080201-1' )
INSERT INTO TestTable (colDate,colSession,colDesc ) VALUES ('20080301',1,'20080301-1' )
INSERT INTO TestTable (colDate,colSession,colDesc ) VALUES ('20070301',2,'20070301-2' )
INSERT INTO TestTable (colDate,colSession,colDesc ) VALUES ('20070401',2,'20070401-2' )
INSERT INTO TestTable (colDate,colSession,colDesc ) VALUES ('20070501',2,'20070501-2' )
INSERT INTO TestTable (colDate,colSession,colDesc ) VALUES