Click here to Skip to main content
15,897,704 members
Articles / Programming Languages / SQL

A Visual SQL Query Designer

Rate me:
Please Sign up or sign in to vote.
4.94/5 (74 votes)
23 Oct 2009CPOL15 min read 392.7K   23.1K   292  
This article describes the implementation of a QueryDesignerDialog class that allows users to create SQL queries based on a given OLEDB connection string.
Imports System.Text
Imports System.ComponentModel

Public Enum GroupByExtension
    None = 0
    Cube = 1
    Rollup = 2
    All = 3
End Enum

Public Class QueryBuilder

    ' ** Fields
    Private _distinct As Boolean
    Private _gbExtension As GroupByExtension
    Private _groupBy As Boolean
    Private _missingJoins As Boolean
    Private _queryFields As QueryFieldCollection
    Private _schema As OleDbSchema
    Private _sql As String = Nothing
    Private _sqlIsDirty As Boolean
    Private _tableCount As Integer
    Private _top As Integer

    ' ** Ctor
    Friend Sub New(ByVal schema As OleDbSchema)
        Me._schema = schema
        Me._queryFields = New QueryFieldCollection
        AddHandler Me._queryFields.ListChanged, New ListChangedEventHandler(AddressOf Me._queryFields_ListChanged)
    End Sub

    ' ** Event Handlers
    Private Sub _queryFields_ListChanged(ByVal sender As Object, ByVal e As ListChangedEventArgs)
        Me._sqlIsDirty = True
    End Sub

    ' ** Implementation
    Private Function BuildFromClause() As String
        Dim dt As DataTable
        Dim i As Integer
        Dim tables As New List(Of DataTable)
        Dim field As QueryField
        For Each field In Me.QueryFields
            Dim tableName As String = field.Table
            Dim table As DataTable = Me._schema.Tables.Item(tableName)
            If Not ((table Is Nothing) OrElse tables.Contains(table)) Then
                tables.Add(table)
            End If
        Next
        Me._tableCount = tables.Count
        Dim qTables As New List(Of DataTable)
        Dim done As Boolean = False
        Do While ((qTables.Count < tables.Count) AndAlso Not done)
            done = True
            For Each dt In tables
                If Me.InsertRelatedTable(dt, qTables) Then
                    done = False
                End If
            Next
        Loop
        Dim qJoins As New List(Of String)
        Dim index As Integer
        For index = 0 To (qTables.Count - 1) - 1
            Dim dt1 As DataTable = qTables.Item(index)
            Dim dt2 As DataTable = qTables.Item((index + 1))
            Dim dr As DataRelation = Me.GetRelation(dt1, dt2)
            qJoins.Add(String.Format("{0}.{1} = {2}.{3}", New Object() {OleDbSchema.GetFullTableName(dr.ParentTable), dr.ParentColumns(0).ColumnName, OleDbSchema.GetFullTableName(dr.ChildTable), dr.ChildColumns(0).ColumnName}))
        Next index
        Dim sb As New StringBuilder
        For i = 0 To (qTables.Count - 1) - 1
            dt = qTables.Item(i)
            If (sb.Length > 0) Then
                sb.Append(vbNewLine + vbTab)
            End If
            sb.AppendFormat("({0} INNER JOIN", OleDbSchema.GetFullTableName(dt))
        Next i
        sb.AppendFormat(" {0}", OleDbSchema.GetFullTableName(qTables.Item((qTables.Count - 1))))
        i = (qJoins.Count - 1)
        Do While (i >= 0)
            Dim join As String = qJoins.Item(i)
            sb.AppendFormat(vbNewLine + vbTab & "ON {0})", join)
            i -= 1
        Loop
        Me._missingJoins = (qTables.Count < tables.Count)
        If Me._missingJoins Then
            For Each dt In tables
                If Not qTables.Contains(dt) Then
                    sb.AppendFormat(", {0}", OleDbSchema.GetFullTableName(dt))
                    qTables.Add(dt)
                End If
            Next
        End If
        Return sb.ToString
    End Function

    Private Function BuildGroupByClause() As String
        Dim sb As New StringBuilder
        If Me.GroupBy Then
            Dim field As QueryField
            For Each field In Me.QueryFields
                If (field.GroupBy = GroupingAggregate.GroupBy) Then
                    If (sb.Length > 0) Then
                        sb.Append("," & vbNewLine + vbTab)
                    End If
                    Dim item As String = field.GetFullName
                    sb.Append(item)
                End If
            Next
            Select Case Me.GroupByExtension
                Case GroupByExtension.Cube
                    sb.Append(" WITH CUBE")
                    Exit Select
                Case GroupByExtension.Rollup
                    sb.Append(" WITH ROLLUP")
                    Exit Select
                Case GroupByExtension.All
                    Return ("ALL " & sb.ToString)
            End Select
        End If
        Return sb.ToString
    End Function

    Private Function BuildOrderByClause() As String
        Dim sb As New StringBuilder
        Dim field As QueryField
        For Each field In Me.QueryFields
            If (field.Sort <> Sort.NoSort) Then
                If (sb.Length > 0) Then
                    sb.Append("," & vbNewLine + vbTab)
                End If
                Dim item As String = field.GetFullName(True)
                sb.Append(item)
                If (field.Sort = Sort.Descending) Then
                    sb.Append(" DESC")
                End If
            End If
        Next
        Return sb.ToString
    End Function

    Private Function BuildSelectClause() As String
        Dim sb As New StringBuilder
        Dim field As QueryField
        For Each field In Me.QueryFields
            If field.Output Then
                If (sb.Length > 0) Then
                    sb.Append("," & vbNewLine + vbTab)
                End If
                Dim item As String = field.GetFullName(Me.GroupBy)
                sb.Append(item)
                If Not String.IsNullOrEmpty(field.Alias) Then
                    sb.AppendFormat(" AS {0}", OleDbSchema.BracketName(field.Alias))
                End If
            End If
        Next
        Return sb.ToString
    End Function

    Private Function BuildSqlStatement() As String
        If ((Me.QueryFields.Count = 0) OrElse (Me._schema Is Nothing)) Then
            Me._tableCount = 0
            Me._missingJoins = False
            Return String.Empty
        End If
        Dim sb As New StringBuilder
        sb.Append("SELECT ")
        If Me.Distinct Then
            sb.Append("DISTINCT ")
        End If
        If (Me.Top > 0) Then
            sb.AppendFormat("TOP {0} ", Me.Top)
        End If
        sb.Append(vbNewLine + vbTab)
        sb.Append(Me.BuildSelectClause)
        sb.AppendFormat(ChrW(13) & ChrW(10) & "FROM" & vbNewLine + vbTab & "{0}", Me.BuildFromClause)
        If Me.GroupBy Then
            Dim groupBy As String = Me.BuildGroupByClause
            If (groupBy.Length > 0) Then
                sb.AppendFormat(ChrW(13) & ChrW(10) & "GROUP BY" & vbNewLine + vbTab & "{0}", groupBy)
            End If
            Dim having As String = Me.BuildWhereClause
            If (having.Length > 0) Then
                sb.AppendFormat(ChrW(13) & ChrW(10) & "HAVING" & vbNewLine + vbTab & "{0}", having)
            End If
        Else
            Dim where As String = Me.BuildWhereClause
            If (where.Length > 0) Then
                sb.AppendFormat(ChrW(13) & ChrW(10) & "WHERE" & vbNewLine + vbTab & "{0}", where)
            End If
        End If
        Dim orderBy As String = Me.BuildOrderByClause
        If (orderBy.Length > 0) Then
            sb.AppendFormat(ChrW(13) & ChrW(10) & "ORDER BY" & vbNewLine + vbTab & "{0}", orderBy)
        End If
        sb.Append(";"c)
        Return sb.ToString
    End Function

    Private Function BuildWhereClause() As String
        Dim sb As New StringBuilder
        Dim field As QueryField
        For Each field In Me.QueryFields
            If (field.Filter.Length > 0) Then
                Dim item As String = field.GetFilterExpression
                If (item.Length > 0) Then
                    If (sb.Length > 0) Then
                        sb.Append(" AND" & vbNewLine + vbTab)
                    End If
                    sb.Append(item)
                End If
            End If
        Next
        Return sb.ToString
    End Function

    Private Function GetRelation(ByVal dt1 As DataTable, ByVal dt2 As DataTable) As DataRelation
        Dim dr As DataRelation
        For Each dr In Me._schema.Relations
            If (((dr.ParentTable Is dt1) AndAlso (dr.ChildTable Is dt2)) OrElse ((dr.ParentTable Is dt2) AndAlso (dr.ChildTable Is dt1))) Then
                Return dr
            End If
        Next
        Return Nothing
    End Function

    Private Function InsertRelatedTable(ByVal dt As DataTable, ByVal list As List(Of DataTable)) As Boolean
        If Not list.Contains(dt) Then
            If (list.Count = 0) Then
                list.Add(dt)
                Return True
            End If
            Dim index As Integer = 0
            Do While (index <= list.Count)
                Dim before As Boolean = ((index = 0) OrElse (Not Me.GetRelation(dt, list.Item((index - 1))) Is Nothing))
                Dim after As Boolean = ((index = list.Count) OrElse (Not Me.GetRelation(dt, list.Item(index)) Is Nothing))
                If (before AndAlso after) Then
                    list.Insert(index, dt)
                    Return True
                End If
                index += 1
            Loop
        End If
        Return False
    End Function

    ' ** Properties
    Public Property ConnectionString() As String
        Get
            Return Me._schema.ConnectionString
        End Get
        Set(ByVal value As String)
            If (Me._schema.ConnectionString <> value) Then
                Me._schema.ConnectionString = value
                Me._sql = Nothing
                Me.QueryFields.Clear()
            End If
        End Set
    End Property

    Public Property Distinct() As Boolean
        Get
            Return Me._distinct
        End Get
        Set(ByVal value As Boolean)
            Me._distinct = value
            Me._sql = Nothing
        End Set
    End Property

    Public Property GroupBy() As Boolean
        Get
            Return Me._groupBy
        End Get
        Set(ByVal value As Boolean)
            If (Me._groupBy <> value) Then
                Me._groupBy = value
                Me._sql = Nothing
            End If
        End Set
    End Property

    Public Property GroupByExtension() As GroupByExtension
        Get
            Return Me._gbExtension
        End Get
        Set(ByVal value As GroupByExtension)
            If (Me._gbExtension <> value) Then
                Me._gbExtension = value
                Me._sql = Nothing
            End If
        End Set
    End Property

    Public ReadOnly Property QueryFields() As QueryFieldCollection
        Get
            Return Me._queryFields
        End Get
    End Property

    Friend ReadOnly Property Schema() As OleDbSchema
        Get
            Return Me._schema
        End Get
    End Property

    Public ReadOnly Property Sql() As String
        Get
            If ((Me._sql Is Nothing) OrElse Me._sqlIsDirty) Then
                Me._sqlIsDirty = False
                Me._sql = Me.BuildSqlStatement
            End If
            Return Me._sql
        End Get
    End Property

    Public Property Top() As Integer
        Get
            Return Me._top
        End Get
        Set(ByVal value As Integer)
            Me._top = value
            Me._sql = Nothing
        End Set
    End Property

End Class

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer
Brazil Brazil
Software Architect/Developer with several years experience creating and delivering software.

Full-stack Web development (including React, Firebase, TypeScript, HTML, CSS), Entity Framework, C#, MS SQL Server.

Passionate about new technologies and always keen to learn new things as well as improve on existing skills.

Comments and Discussions