Click here to Skip to main content
Email Password   helpLost your password?

Introduction

Admit it. We have all done it. And we all thought it was the best -and only- way of doing it. When we were building on-the-fly SQL statements to fire against our databases, we were all building up huge strings containing all the applicable WHERE clauses. Endlessly attaching more and more statements to our SQL strings, bugs and the risk of SQL Injection attacks were very likely to happen. And hell, it even made our code look ugly and unmanageable!

This had to stop. But how? Some people would say to use Stored Procedures. But that doesn't really fix the problem. You would still have to dynamically build up your SQL statement, and you would only be moving your problem to the database side, while even keeping the SQL injection hazard. Besides this 'solution', there are probably tons of other options you could consider, but they all leave you with the same basic challenge: doing it nice and safe.

While I was building C# templates for my online DAL (Data Access Layer) generation tool www.code-engine.com, I wanted to provide an easy way to custom-query the database. Unlike with previous templates I had developed, I didn't want to use "string queries" to query the DB anymore. I was more than fed-up with this messy way of getting my data. I wanted something clean. Something intuitive. Something flexible. Something I could simply tell to SELECT data from some table, JOIN with some others, use a number of WHERE clauses, GROUP BY some column, and return me only the TOP x records.

I started developing the SelectQueryBuilder class with exactly this functionality in mind. It exposes a number of properties and methods that you can call to easily put together a SELECT statement. And once the BuildQuery() or BuildCommand() method is called, it provides you with either a good-old "string query", or a full-blown DbCommand object using command parameters to query the data.

Using the code

Let me start off by showing you the old-school way of creating a SELECT statement. I will then give you a quick demonstration of how you can do the same with the SelectQueryBuilder class.

The old-school way of doing it

The code below illustrates the 'old-school' way of building up a SELECT statement, using some kind of variable to keep track of which concatenation operator should be used (WHERE, or AND), while exposing your database to possible SQL injection attacks.

string statement = "SELECT TOP " + maxRecords + " * FROM Customers ";
string whereConcatenator = "WHERE ";

if (companyNameTextBox.Text.Length > 0) 
{
    statement += whereConcatenator;
    statement += "CompanyName like '" + companyNameTextBox.Text + "%' ";
    whereConcatenator = "AND ";
}
if (cityTextBox.Text.Length > 0)
{
    statement += whereConcatenator;
    statement += "City like '" + cityTextBox.Text + "%' ";
    whereConcatenator = "AND ";
}
if (countryComboBox.SelectedItem != null)
{
    statement += whereConcatenator;
    statement += "Country = '" + countryComboBox.SelectedItem + "' ";
    whereConcatenator = "AND ";
}

I'm pretty sure the code above seems familiar to you! If it doesn't, you have either been living on Mars for the past 10 years, or you haven't ever coded a database-driven application with search functionality. Let me tell you this though: this way of querying your database is not acceptable anymore! It is ugly and unsafe.

The SelectQueryBuilder way of doing it

The same query can be built using the SelectQueryBuilder class.

SelectQueryBuilder query = new SelectQueryBuilder();
query.SelectFromTable("Customers");
query.SelectAllColumns();
query.TopRecords = maxRecords;

if (companyNameTextBox.Text.Length > 0)
    query.AddWhere("CompanyName", Comparison.Like, 
                    companyNameTextBox.Text + "%");

if (cityTextBox.Text.Length > 0)
    query.AddWhere("City", Comparison.Like, 
                   cityTextBox.Text + "%");

if (countryComboBox.SelectedItem != null)
    query.AddWhere("Country", Comparison.Equals, 
                  countryComboBox.SelectedItem);

string statement = query.BuildQuery();

// or, have a DbCommand object built

// for even more safety against SQL Injection attacks:

query.SetDbProviderFactory(
      DbProviderFactories.GetFactory(
      "System.Data.SqlClient")); 
DbCommand command = query.BuildCommand();

As you can see, this approach is much more intuitive than bluntly concatenating strings together. Also, considering the SQL Injection hazard of the first example, the SELECT query generated by the SelectQueryBuilder will be completely safe, no matter what the contents of the used TextBoxes are. It's really that simple!

Using SQL functions

If you want to use SQL functions in your queries, you can use the SqlLiteral class to wrap the function calls in. The best way to explain what this class does exactly is to show it to you with a little code sample:

SelectQueryBuilder query = new SelectQueryBuilder();
query.SelectFromTable("Orders");
query.AddWhere("OrderDate", Comparison.LessOrEquals, 
                       new SqlLiteral("getDate()"));

If we wouldn't have wrapped the getDate() function call in the SqlLiteral class, the built query would have produced WHERE OrderDate <= 'getDate()'. Of course, we want this function to appear in the statement without the single quotes around it. This is where the SqlLiteral class comes in handy: it copies the given string directly to the output, without formatting it as a string. The output will now be WHERE OrderDate <= getDate() instead!

Using Joins in your queries

To create JOINs to other tables, you can use the AddJoin method. The following code shows you how to create an INNER JOIN from table Orders to Customers:

SelectQueryBuilder query = new SelectQueryBuilder();
query.SelectFromTable("Orders");

query.AddJoin(JoinType.InnerJoin, 
              "Customers", "CustomerID", 
              Comparison.Equals, 
              "Orders", "CustomerID");

query.AddWhere("Customers.City", 
      Comparison.Equals, "London");

This code selects all orders from customers that are situated in London. Once the BuildQuery method is called, it will produce the following SQL statement:

SELECT Orders.* 
FROM Orders 
INNER JOIN Customers ON Orders.CustomerID = Customers.CustomerID  
WHERE (Customers.City = 'London')

Note that, by default, the query that is built only selects * from the selected table (Orders.*, in this example). If you also want to select the columns from any joined table, you have to select them explicitly. You can do this by calling query.SelectColumns("Orders.*", "Customers.*");.

Building Count queries

If you want to execute a Count query on your database, you can use the SelectCount method as shown below:

query.SelectCount();

In more complex Count queries, you might want to use a GROUP BY statement. Take a look at the sample below, it demonstrates how to use the GroupBy and AddHaving methods.

SelectQueryBuilder query = new SelectQueryBuilder();
query.SelectColumns("count(*) AS Count", "ShipCity");
query.SelectFromTable("Orders");
query.GroupBy("ShipCity");
query.AddHaving("ShipCity", Comparison.NotEquals, "Amsterdam");
query.AddOrderBy("count(*)", Sorting.Descending);

The code above selects the number of orders per city, sorts by the number of orders, and leaves out orders shipped to Amsterdam. The output of the BuildQuery method will now be:

SELECT count(*) AS Count, ShipCity 
FROM Orders 
GROUP BY ShipCity  
HAVING  (ShipCity <> 'Amsterdam')    
ORDER BY count(*) DESC

Complex WHERE statements

Have you ever gazed at the built-in query builders of Microsoft Access or SQL Server and wondered if you could build queries in the same way, containing multiple levels of ANDs and ORs, without having to bother about the right location for the () symbols? From code? Yes? So have I!

Well, guess what. You can do it with the SelectQueryBuilder class! You can add multiple levels of WHERE statements to your query. By default, all calls to query.AddWhere are placed on the first level of the query. You can compare this first level to the first 'Criteria' column in the SQL Server query builder; levels 2,3,4 and so on are represented by the 'Or...' columns in the grid.

Take a look at the following screenshot of the SQL Server query builder, in which I have quickly put together a simple -bogus- SELECT statement:

As you can see, I created a query that selects all orders from the customer 'VINET', which were placed before 1-1-2005, and orders from customer 'TOMSP', which were placed before 30-6-2004, or after 1-1-2006. (Please don't ask why anyone would want a query like this specific one, but hey.. it's just an example.) This query can be built as follows:

SelectQueryBuilder query = new SelectQueryBuilder();
query.SelectFromTable("Orders");

// Add 'Criteria' column to level 1

query.AddWhere("CustomerID", Comparison.Equals, 
                                   "VINET", 1);
query.AddWhere("OrderDate", Comparison.LessThan, 
                     new DateTime(2005,1,1), 1);

// Add first 'Or...' column to level 2

query.AddWhere("CustomerID", Comparison.Equals, "TOMSP", 2);
query.AddWhere("OrderDate", Comparison.LessThan, 
                    new DateTime(2004,6,30), 2);

// Add second 'Or...' column to level 3

query.AddWhere("CustomerID", Comparison.Equals, 
                                   "TOMSP", 3);
query.AddWhere("OrderDate", Comparison.GreaterThan, 
                        new DateTime(2006,1,1), 3);

When calling BuildQuery, all defined levels will be ORed together, and you end up with (almost) the same query that SQL Server would have prepared for you.

And to make things even more complicated, if you look at the created statement closely, you would probably say: "I would have put the two latter statements together in one statement, using an OR between the two dates". And you would have been right. In the SQL Server query builder, this would look like this:

It is possible to do this with the SelectQueryBuilder also, by creating 'nested WHERE clauses'. This is how to do it:

SelectQueryBuilder query = new SelectQueryBuilder();
query.SelectFromTable("Orders");

// Add 'Criteria' column to level 1

query.AddWhere("CustomerID", Comparison.Equals, "VINET", 1);
query.AddWhere("OrderDate", Comparison.LessThan, 
                     new DateTime(2005,1,1), 1);

// Add 'Or...' column to level 2

query.AddWhere("CustomerID", 
                Comparison.Equals, "TOMSP", 2);

// Add the date selection clause

WhereClause clause = 
    query.AddWhere("OrderDate", Comparison.LessThan, 
                        new DateTime(2004,6,30), 2);

// Add a nested clause to the captured clause

clause.AddClause(LogicOperator.Or, 
       Comparison.GreaterThan, new DateTime(2006,1,1));

Notice that I capture a WhereClause object, which is returned from the AddWhere call. I then call clause.AddClause to create the nested clause, and choose to OR it to the first clause by specifying LogicOperator.Or. The statement it produces is as follows:

SELECT Orders.* 
FROM Orders  
WHERE  
(
    (CustomerID = 'VINET') 
    AND (OrderDate < '2005/01/01 12:00:00')
)  
OR 
(
    (CustomerID = 'TOMSP') 
    AND (OrderDate < '2004/06/30 12:00:00' OR 
         OrderDate > '2006/01/01 12:00:00')
)

Please note that the dates in this example contain '12:00:00'. This is because I omitted the time in the DateTime constructor. But that's just laziness on my side. If I had used new DateTime(2006,1,1,0,0,0), the date strings would have contained '00:00:00' instead.

Conclusion

I realize that most of you out there won't be using all the functionality in this extensively. But it is all in there, so if you've been waiting for something like this to come along, this is your party! I was having fun writing this article, rediscovering what exactly I had built a couple of months ago. I hope you enjoy using the code just as much as I do!

Like I mentioned in my introduction, the SelectQueryBuilder is part of the CodeEngine Framework. This framework also contains a DeleteQueryBuilder, a UpdateQueryBuilder, and a InsertQueryBuilder. I use these builders in the code that is generated by my C# DAL generator. You can download a copy of the framework DLL at www.code-engine.com. I will publish the source code of the other query builders in time. Meanwhile, if you have any questions, comments, or suggestions, don't hesitate to contact me!

You must Sign In to use this message board.
 
 
Per page   
 FirstPrevNext
Generalaggregate functions
McRam
22:12 22 Nov '09  
can it be used with already built in functions like sum, avg, etc..

McRam

Generalseems to be confused when using the UPPER function in addwhere()
zephyrprime
10:59 5 Oct '09  
When I use a where clause that calls a function like UPPER(), it seems to confuse the BuildCommand().

query.AddWhere("UPPER(first_name)", Enums.Comparison.Like, "%bob%", 1)

The query returned by a call to BuildCommand will look like the following:

SELECT first_name, last_name FROM table1 WHERE (UPPER(first_name) LIKE @p1_UPPER(first_name)) OR (UPPER(last_name) LIKE @p2_UPPER(last_name)) ORDER BY full_name ASC

SelectQueryBuilder is trying to use unpermittable characters like "(" and ")" in it's parameter names.
GeneralGreat work! Tables relations representation?
_mns
0:36 15 Sep '09  
Great work! Smile It helps me a lot!

Anyone has any idea how to represent table relations(arrows between tables)? (like in query designer in Sql Server) D'Oh!

Thanks!
Generalvery cool
Donsw
16:25 12 Sep '09  
We will be trying this and see how it might work in our projects. Do you have any updates since 2006? That is the last year on your website as well. How well does to work with other databases besides mssql?

cheers,
Donsw
My Recent Article : Backup of Data files - Full and Incremental

GeneralWhat does SelectFromTables() do?
zephyrprime
11:08 6 Aug '09  
Great module by the way. It's very useful for whenever I need to do complex searching driven by user selected criteria.
Generalbrilliant
sampic7
10:40 17 Jun '09  
Ewout, you are a genius. Now my application kicks ass and everyone thinks i am a genius too.

Thanks!

LethalWSDL


GeneralI hope this is not a dumb question...
Steven Jarrell
9:26 7 Jun '09  
I know this may be simple for most, but I would like to know how to randomize my database.

I created a database with prefilled data rows, and when clicking a button, I would like the database to re-order itself and show a random record instead of "next" or "previous" etc. etc. I have searched and searched through visual studio's help and have found nothing on this, and have so far found nothing on Code Project either. So I was just hoping that you may know how I can accomplish this.

Thanks in advance for all your help, as after reading your article, I feel that you most definately know what you are doing and could help me with my "query" Wink Sniff

Steven
QuestionCompare two Database-Fields in WHERE Clause
badelatschenfutzie
7:39 7 Apr '09  
Hello,

first of all, I have to say thanks for you work. It is amazing what you've done for us.

I'm using it not for complex joins or stuff like this. I do the joins in a db-view. So far, so good. I'm using this SelectQueryBuilder for more or less complex where clause. But what I want is to add a where clause to compare two db-fields. How do I do?

For example:
In db I have two fields; a and b.
I want in plain SQL
<code>WHERE A = B</code>

But when I do <code>AddWhere("a", Comparison.Equals, "b")</code> I get <code>WHERE A = 'B'</code>

How can I get what I want?
Thanks.
AnswerRe: Compare two Database-Fields in WHERE Clause
badelatschenfutzie
7:44 7 Apr '09  
Okay,

I found it.

AddWhere(A, Comparison.Equals, new SqlLiteral("B"))

Thanks!
GeneralAdd an ESCAPE statement to the LIKE comparison
philippe dykmans
13:24 16 Dec '08  
For those interested;

The LIKE clause in SQL can be accompanied by an ESCAPE statement. The character following the ESCAPE tells the interpreter which character is used to escape the wildcard characters '_' and '%'. It is very easy to add this to the SelectQueryBuilder code. That is, if you don't mind using the same escape character (like \ in the example below) always. It's a bit of a hardcoded hack. But for my purpose it was better than nothing. By the way, the SQL interpreter doesn't mind if this statement is always there. Even when there's nothing to escape. Think it would be a nice addition to an otherwise great code.

internal static string CreateComparisonClause(string fieldName, Comparison comparisonOperator, object value)
{
...

case Comparison.Like:
Output = fieldName + " LIKE " + FormatSQLValue(value) + " ESCAPE '\\'"; break;
case Comparison.NotLike:
Output = "NOT " + fieldName + " LIKE " + FormatSQLValue(value) + " ESCAPE '\\'"; break;

...
}

Grtz,
Phil

Philippe Dykmans
Software developpement
Advanced Bionics Corp.

Generalquery builder form in web platform
eferen
0:00 5 Sep '08  
hi i am developing sql query builder form in web platform. i wanna make and show table relations on this form (for example microsoft access design view in querry wizard ). i have to build parent child relationship for my tables on this web form .can you suggest me a component for this
GeneralRe: query builder form in web platform
Member 3888964
6:03 7 Aug '09  
Hi, have you found some solution for your problem?
I also need a component to represent tables relationships.. Sniff
Not for web platform, but for c# .net
If you have the answer for this, can you please refer it to me?

Thanks!
GeneralEasyQuery.NET - user-friendly components for query building
tigerwt
20:38 4 May '08  
Hi,

If you are interesting in this topic you possibly should take a look at EasyQuery.NET component library (http://devtools.korzh.com/eq/dotnet/).
It allows to include user-friendly query builder right into your application (or ASP.NET web-site).
We have been using it for more than 1 year and quite happy with it.
GeneralQuery - Select Into
Lindsay Fisher
6:16 18 Mar '08  
Hi Ewout,

How would I do the following query using the code engine:

Select * From Table A Into Table B

Regards

LF
GeneralHow to download the CodeEngine Framework?
SHYAMRVAIDYA
23:25 4 Mar '08  
It is very useful and interesting topic.Thanks for your sharing.

But how to download the library?

When I starts downloading the DLL,its shows error that file not found.

Could you please send the libarary and coding for Insert,Update n Delete
Query Builder?

My mail id is vaidya_shyamr@yahoo.co.in

Thanks in Advance !!

Smile

SHYAM VAIDYA , SOFTWARE DEVELOPER

GeneralRe: How to download the CodeEngine Framework?
jjjamie
5:50 11 Mar '08  
I'm trying to download it too. I am also getting an error.
GeneralUsage in an refactoring to pattern article
stavinski
0:58 14 Jan '08  
Firstly this a great component and has helped a lot on one of my projects, I was just wondering if I could use some of the code as examples to show how we can refactor the WHERE statement building to use a syntax tree as I think it fits the bill nicely and would show a real world example of it's usage?

Thanks,
Mike
GeneralQuery with function (ex. DATEDIFF)
Elisa1508
0:09 9 Oct '07  
How can I build such a query with your nice functions ?

SELECT * FROM TableName where DATEDIFF(dd,TableName.TableField,'20071001') >= 0

Thanks in advance

Gabriele

General'OR' issue [modified]
Bob454
14:48 17 Sep '07  
Hi,

I read your article, and i wanted to make a search function, searching with one word in different colums (if it appears in column 1, show it, or in column 2, show it)..
If I use:



Query query = CalendarItem.CreateQuery();
query.AddWhere(CalendarItem.Columns.Description, Comparison.Like, "%" + SearchTextBox.Text + "%");
query.AddWhere(CalendarItem.Columns.Location, Comparison.Like, "%" + SearchTextBox.Text + "%");
LoadAndBindQuery(query);



I get the results where the word is in each column, so in Description AND in Location, then I tried:



Query query = CalendarItem.CreateQuery();
query.AddWhere(CalendarItem.Columns.Description, Comparison.Like, "%" + SearchTextBox.Text + "%", 1);
query.AddWhere(CalendarItem.Columns.Location, Comparison.Like, "%" + SearchTextBox.Text + "%", 2);
LoadAndBindQuery(query);



But that doesn't seem to work... What am I doing wrong?

Thank you!


-- modified at 13:46 Thursday 20th September, 2007
GeneralSQL CRUD?
JamieRThomson
6:07 13 Jul '07  

First of all - a very nice and useful class. I hope to banish any manual SQL string building in our application to very deep waters, any plans to extend this to support INSERT, UPDATE and DELETE functions?

Thanks,
Jamie. Smile
MCSD.
GeneralRe: SQL CRUD?
Ewout Stortenbeker
7:17 13 Jul '07  
Hi Jamie,

Thanks. There also are an InsertQueryBuilder, UpdateQueryBuilder and DeleteQueryBuilder available in the CodeEngine framework; you can download the dll at www.code-engine.com

Cheers,
Ewout
GeneralWhere Clause issue
MCAST76
8:02 19 Jun '07  
Greetings,

I need a WhereClause like this:

WhereClause("YEAR(ReleaseDate)", Comparison.Equals, cmbYear.Text)

But I get an error, "Invalid column name 'YEAR(ReleaseDate)'."

Can you help me?

Thanks,


M.Cast
GeneralAwesome!
mrshizeats
12:46 7 May '07  
I haven't really pushed this project to the limit with complex query building, but it sure makes things a lot easier for quick asp.net search/filter input building. Nice use of generics, provider factories, etc (newer 2.0 concepts) too! I had a MUCH slimmer version of this I had written myself, but I just might switch to yours!

Craig
QuestionWhat about this query with NOT
8:34 20 Mar '07  
SmileHow do you write this:

SELECT * FROM Orders, Customer
WHERE NOT (Orders.ID > 1000 OR Customer.Name LIKE '%Soft%')
AnswerRe: What about this query with NOT
Pink Floyd
9:04 2 May '07  
Using Boolean Algebra:

NOT (b1 OR b2) = (NOT b1) AND (NOT b2)

So you can now construct your query that way.


Last Updated 14 Mar 2006 | Advertise | Privacy | Terms of Use | Copyright © CodeProject, 1999-2010