THE SQL Server Blog Spot on the Web

Welcome to SQLblog.com - The SQL Server blog spot on the web Sign in | |
in Search

Alexander Kuznetsov

In defense of SELECT * in production code, in some limited cases?

It is well known that SELECT * is not acceptable in production code, with the exception of this pattern:

IF EXISTS(SELECT *   

We all know that whenever we see code code like this:

Listing 1. "Bad" SQL

 SELECT  Column1 ,
        
Column2
,
        
Column3 
,
        
Column4 
,
        
Column5 
,
        
Column6

FROM    SELECT    c.* ,
                    
ROW_NUMBER() OVER PARTITION BY Column1 ORDER BY Column2 AS rn
          
FROM      data.SomeTable AS c
        
AS c
WHERE   rn 5
 

we are supposed to automatically replace * with an explicit list of columns, as follows:

Listing 2. "Good" SQL 

SELECT  Column1 ,
            
Column2 ,
            
Column3  ,
            
Column4  ,
            
Column5  ,
            
Column6
    
FROM    
    
SELECT    Column1 ,
                
Column2 ,
                
Column3  ,
                
Column4  ,
                
Column5  ,
                
Column6 ,          
               
ROW_NUMBER() OVER PARTITION BY Column1 
            
ORDER BY Column2 AS rn
        
FROM      data.SomeTable
    
AS c
    
WHERE   rn 5  
 

However, repeating one and the same list of columns twice is not the best practice in object-oriented programming. Why should it be the best practice in database programming?

Of course, as we all know, when we list columns explicitly, we protect our code against changes in the underlying objects. However, I cannot come up with a situation when Listing 2 is any safer than Listing 1. Apparently it is enough to list columns explicitly just once.

For all practical purposes, Listing 1 seems to be just as safe as Listing 2. If this is the case, if I am not missing anything, then SELECT * in this particular case, in Listing 1, is completely acceptable, because replacing it with an explicit list of columns does not improve anything.

More to the point, Listing 1 is shorter and more readable, and as such should be preferable.

What do you think?

 

Published Thursday, June 03, 2010 2:27 PM by Alexander Kuznetsov

Comment Notification

If you would like to receive an email when updates are made to this post, please register here

Subscribe to this post's comments using RSS

Comments

 

dan holmes said:

SELECT * in listing 1 may not be covered whereas listing 2 could be easily made so.  However since the outer SELECT doesn't use the "extra" columns the optimizer may not even get them.  I didn't try it to see.

Other than that i agree.

June 3, 2010 2:55 PM
 

Alexander Kuznetsov said:

Dan,

Typically we get the same execution plan in both cases - the optimizer is smart enough.

June 3, 2010 3:08 PM
 

Adam Machanic said:

Alex, agreed 100%. I use SELECT * like that all the time. See the Who is Active code for two or three examples :-)

June 3, 2010 3:25 PM
 

Bob Pusateri said:

Excellent point, Alex.  In a case like that, it really wouldn't make any difference.  Great tip!

June 3, 2010 4:33 PM
 

Brian Tkatch said:

Clarity is always better. I would use case 2 instead of case 1. It only saves a few keystrokes, and only once, why not spend the extra moment and make the code abundantly clear?

Listing 2 could also cause issues if the outer query gets removed. Imagine the code having an issue, so a developer copies the inner query and sends it in an email to a co-worker. The clarity of the outer query is now gone.

If anything, i'd say specify it in the inner query and use * in the outer query. being the inner query hit the TABLE, it should specify the COLUMN-list. But if the outer query merely pulls in information from the inner query, which is defined right there, the case can be made to use *. (I still wouldn't but because i'd apply it as a blanket rule, but not because it is inherently bad.)

The only time i (almost) used * in production code (outside of EXISTS and COUNT) was on 10 TABLEs with 1,000 COLUMNs each. Specifying the COLUMNs would have hurt more than it helped. In the end, we went with long instead of wide.

June 3, 2010 4:38 PM
 

Eric Humphrey said:

I would prefer explicitly listing the inside columns and using * in the outer select as they would have been previously limited. Basically flip listing 1. Just my preference.

June 3, 2010 4:40 PM
 

Alexander Kuznetsov said:

Hey Brian,

The outer query essentially retrieves top 4 rows for each distinct Column1, it does not "merely pulls in information from the inner query".

June 3, 2010 4:47 PM
 

Piotr Rodak said:

I would still use explicit list of columns. Relying on the optimizer to be 'smart' is to much of a raincheck sometimes.

Also, in case of EXISTS I tend to type (select 1 from... ) rather than a *. Just looks better for me, even though there is no difference.

I see some people type select count(1) from.. in turn. I rather prefer count(*) this time, so I can't be called constant consistent :)

June 3, 2010 5:42 PM
 

Brian Tkatch said:

@Alexander Kuznetsov

I mean COLUMN-wise. There's no join, no manipulation, no changing. The COLUMN list would be right there in the inner query, and cannot be run without it. So, there's no chance of confusion.

June 3, 2010 8:48 PM
 

Cheran S said:

Talking about a situation where Listing 2 is safer than Listing 1, what if someone adds a column named "rn" to "SomeTable"?

That being said, I tend to use Listing 1 more often than not.  I agree that it's shorter and more readable to list the columns once.

June 4, 2010 2:21 AM
 

AgileBI said:

"Rules" are handy for people who don't understand or aren't interested in understanding exactly what they are doing.  

But when you *really* know what you are doing and why you are doing it you can always break the rules.

SQL developers seem to be overly obssessed with rules compared to other colleagues I work with who appear to be much more flexible in their approach.  

I particularly notice an abundance of blog posts labouring best practises.  Just get out there and code.  If it doesn't work re-factor.  Get agile :)

June 4, 2010 4:19 AM
 

Brian Tkatch said:

@AgileBI

>But when you *really* know what you are doing and why you are doing

>it you can always break the rules.

And make it how much harder for the maintenance programmer? Is it really worth saving a minute or two now, when the cost later is so much more?

>SQL developers seem to be overly obssessed with rules compared to

>other colleagues I work with who appear to be much more flexible

>in their approach.  

People who work with caustic acid are so much more paranoid about spills that all the cooks i work with, who are much more easy going.

>I particularly notice an abundance of blog posts labouring best

>practises.  Just get out there and code.  If it doesn't work

>re-factor.  Get agile :)

Remind me never to hire you. Not only are Agile and databases not compatible, it isn't even tempting. Agile is good for UI design. And the fad will pass in a few years like all the other shiny new methodologies.

June 4, 2010 7:43 AM
 

Alexander Kuznetsov said:

Brian,

can you give us an example when this * really makes it "harder for the maintenance programmer? Is it really worth saving a minute or two now, when the cost later is so much more?"

June 4, 2010 10:18 AM
 

Lana Goldenberg said:

Very interesting point.

However sometimes you need to make a functionality change, and the fix has to be made on the back-end without touching the front-end App code.

So the returning result set should keep the same structure.Then you would need to change the inner query select list. Solution in this case would be to replace the '*' with the explicit select from the different set of columns/tables and to alias it with the same names-then you can keep the aliases the same.

Example:

SELECT  Column1 ,

           Column2 ,

           Column3  ,

           Column4  ,

           Column5  ,

           Column6

   FROM    (  

   SELECT    Col1 AS Column1  ,

               Col2 AS Column2 ,

               Col3 AS Column3 ,

               Col4  AS Column4 ,

               Col5  AS Column5 ,

               Col6  AS Column6 ,          

               AS ROW_NUMBER() OVER ( PARTITION BY Column1  

           ORDER BY Column2 ) AS rn

       FROM      data.SomeTable2

   ) AS c

   WHERE   rn < 5

June 4, 2010 11:08 AM
 

Alexander Kuznetsov said:

Lana,

I think the following code is equivalent to yours and is shorter:

SELECT  Col1 AS Column1  ,

              Col2 AS Column2 ,

              Col3 AS Column3 ,

              Col4  AS Column4 ,

              Col5  AS Column5 ,

              Col6  AS Column6

  FROM    (  

  SELECT    * ,          

              AS ROW_NUMBER() OVER ( PARTITION BY Column1  

          ORDER BY Column2 ) AS rn

      FROM      data.SomeTable2

  ) AS c

  WHERE   rn < 5

June 4, 2010 11:43 AM
 

jonmcrawford said:

Not sure that SELECT * is really a good idea here either, because you're assuming in Listing 2 that the columns are the same.

In that case, then I don't suppose it makes much difference, but you did not state that in your post.

If the table in your subquery has many more columns, then you're pulling unnecessary data back, which if I play devil's advocate and say it has 1018 more columns, you're getting a lot of junk that you don't need, and should limit the set.

June 4, 2010 12:01 PM
 

jonmcrawford said:

I think Lana probably meant to change some of the columns in the inner query?

something like:

IF OBJECT_ID('tempdb..#testData') IS NOT NULL

   BEGIN

       DROP TABLE #testData

   END

CREATE TABLE #testData

   (

     Column1 INT ,

     Column2 INT ,

     Column3 INT ,

     Column4 INT ,

     Column5 INT ,

     Column6 INT

   )

INSERT  INTO #testData

       SELECT  1 ,

               2 ,

               3 ,

               4 ,

               5 ,

               6

       UNION ALL

       SELECT  7 ,

               8 ,

               9 ,

               0 ,

               1 ,

               2

       UNION ALL

       SELECT  1 ,

               2 ,

               3 ,

               4 ,

               5 ,

               6

       UNION ALL

       SELECT  7 ,

               8 ,

               9 ,

               0 ,

               1 ,

               2

       UNION ALL

       SELECT  1 ,

               2 ,

               3 ,

               4 ,

               5 ,

               6

       UNION ALL

       SELECT  3 ,

               4 ,

               5 ,

               6 ,

               7 ,

               8

--SELECT * FROM #testData AS td

SELECT  c.rn ,

       Column1 ,

       Column2 ,

       Column3 ,

       Column4 ,

       Column5 ,

       Column6

FROM    (

         SELECT    c.* ,

                   ROW_NUMBER() OVER ( PARTITION BY Column1 ORDER BY Column2 ) AS rn

         FROM      #testData AS c

       ) AS c

WHERE   rn < 5

--Lana's example, swapping columns out in the inner query

SELECT  Column1 ,

       Column2 ,

       Column3 ,

       Column4 ,

       Column5 ,

       Column6

FROM    (

         SELECT    Column6 AS Column1 ,

                   Column5 AS Column2 ,

                   Column4 AS Column3 ,

                   Column3 AS Column4 ,

                   Column2 AS Column5 ,

                   Column1 AS Column6 ,

                   ROW_NUMBER() OVER ( PARTITION BY Column1 ORDER BY Column2 ) AS rn

         FROM      #testData

       ) AS c

WHERE   rn < 5

June 4, 2010 12:06 PM
 

Brian Tkatch said:

@Alexander Kuznetsov

>can you give us an example when this * really makes it "harder for

>the maintenance programmer? Is it really worth saving a minute or two

>now, when the cost later is so much more?"

That didn't mean SELECT * specifically, that was in response to the comment "But when you *really* know what you are doing and why you are doing it you can always break the rules."

SELECT * would be unlikely to cause confusion to the maintenance coder (even though the list is probably clearer).

June 4, 2010 12:12 PM
 

Alexander Kuznetsov said:

@AgileBI:

>"Rules" are handy for people who don't understand or aren't interested >in understanding exactly what they are doing.  

>But when you *really* know what you are doing and why you are doing it >you can always break the rules.

I would put it somewhat differently: whenever we use a rule, we need to be able to prove that this rule makes sense in this particular case. If we cannot prove that, we are free to not follow it - without such a proof the rule simple does not apply.

Illinois' Rules of the Road do not apply in New Zealand.

June 4, 2010 4:17 PM
 

Alexander Kuznetsov said:

@Brian:

>Not only are Agile and databases not compatible, it isn't even tempting. >Agile is good for UI design.

Are you sure? I lead a team of agile developers. Are you suggesting that we should stay away from databases, or what do you mean?

I don't see the reason. Database development is development too. Why shouldn't we use in our database programming the approach that helps us succeed in OO programming?

June 4, 2010 4:22 PM
 

Martin Norgrove said:

>Not only are Agile and databases not compatible, it isn't even tempting. >Agile is good for UI design.

Couldn't disagree more, IMHO if you want to do for example BI/DWh successfully then SDLC just doesn't work you have to do agile short bursts of development... you just do it following an agreed high level design.

I'm in agreement that as db developers we're often too focussed on "rules", often the practical application of sound reasoning is more than enough. How many extra minutes do we invest in code to make it "best practice" that never breaks... ever... Thats also a cost to any business.

June 5, 2010 6:38 PM
 

Mike C said:

Have you tested whether the optimizer is smart enough to exclude columns from the inner query result that aren't used by the outer query?  For instance, if you had a Column7 varchar(max) on the table but it wasn't used by the outer query.

June 5, 2010 11:40 PM
 

Alex K said:

I consistently see exactly the same execution plan, no matter if I use * of if I expand * into the list of columns. Note, however, that I keep my queries short and simple, and I avoid big huge monster queries in my production. So, what I am observing in simple cases might not be true for more complex ones.

Of course, because in SQL Server the optimizer is not open source, we cannot see for ourselves the algorithm which makes such decisions, and all we can do is black box testing.

June 6, 2010 1:34 PM
 

B said:

@Alexander Kuznetsov

>I lead a team of agile developers. Are you suggesting that we should

>stay away from databases

If it works for you, go ahead. Stay away from my databases though. Or, another way, don't ask me to help a team that uses Agile.

>Database development is development too. Why shouldn't we use in

>our database programming the approach that helps us succeed in OO

>programming?

Because programming is a set of rules that works together. Data Modeling is a single image that expresses a single idea. Put another way. Programming can be done in parts and then tied together. Data Modeling is done as a whole, then defines its smaller parts.

At least that's my approach. I'm usually called in to fix the problems caused by piecemeal modeling.

June 7, 2010 7:25 AM
 

Brian Tkatch said:

@Martin Norgrove

>you just do it following an agreed high level design.

There's an agreed design? Please, please convince our BAs of that!

>the practical application of sound reasoning is more than enough

In my experience, "sound reasoning" fails after about three months.

>How many extra minutes do we invest in code to make it "best

>practice" that never breaks... ever...

We must work in different universes. I am called in to other teams to fix problems because of exactly this.

June 7, 2010 7:31 AM
 

Jared Ko said:

Mike C

It's smart enough to only pull the columns you want. You can test this by creating a covering index on only the columns you want and see that it's performing an index scan rather than a table (Clustered Index) scan:

IF OBJECT_ID('tempdb..#so') IS NOT NULL DROP TABLE #so

GO

SELECT * INTO #so FROM sys.sysobjects

CREATE INDEX ix_so1 ON #so(name, id, xtype, uid, info, crdate)

GO

SELECT subQ.name, subq.id, subq.xtype, subq.uid, subQ.info, subQ.crdate, subQ.rn

FROM (SELECT *,

     ROW_NUMBER() OVER (ORDER BY crdate ) AS rn

     FROM #so) subQ

June 7, 2010 12:48 PM
 

Jared Ko said:

The concern is that a column [rn] could be added to [SomeTable] in the future and, withouth a full suite of test casese, we might not encounter it until our code hits production. At that point, we get an error:

Msg 8156, Level 16, State 1, Line 1

The column 'rn' was specified multiple times for 'subQ'.

Couldn't we get the best of both wolds by using an impossible-to-repeat column name such as a GUID?

SELECT  Column1 ,

       Column2 ,

       Column3  ,

       Column4  ,

       Column5  ,

       Column6

FROM    ( SELECT    c.* ,

                   ROW_NUMBER() OVER ( PARTITION BY Column1 ORDER BY Column2 ) AS [76E5FC36-A01E-4AC0-AE55-9C1D9A2C3616]

         FROM      data.SomeTable AS c

       ) AS c

WHERE   [76E5FC36-A01E-4AC0-AE55-9C1D9A2C3616] < 5

June 7, 2010 12:55 PM
 

Brian Tkatch said:

@Jared Ko

Excellent point.

Another solution would be to name the COLUMNs on the inner query and use the * on the outer one.

June 7, 2010 1:40 PM
 

Alex K said:

Jared,

When we use rn as column name for ROW_COUNT() in the inner subquery, we implicitly assume that column rn is not present in whatever we select from.

I would rather write an automated (unit) test to document this assumption, and keep my source code succinct. But maybe that's just me - I have a very powerful and easy-to-use unit testing harness, and I can benefit from it in multiple ways.

June 7, 2010 10:22 PM
 

Jared Ko said:

@Alex

The problem doesn't lie with whether [rn] currnetly exists but its potential to exist during the lifetime of the database. Applications tend to live on beyond the time that we're in charge of the development of said database.

The concern is that a new developer will come in a year from now and decide that all tables need row numbers and add [rn] to your table.

This is the same reason we prefix table names (or aliases) in front of all of our column names any time we do a join. We know that there is a risk of somebody adding a column with the same name to another table giving that dreaded "ambiguous column name" error.

You want to do everything possible (within reason) to future-proof your database. I believe that "SELECT *" can be reasonably expanded to column names (or using an impossible name) to reasonably future-proof your database.

June 8, 2010 3:38 PM
 

Alexander Kuznetsov said:

Jared,

I hear what you are saying, and I am suggesting an alternative solution. Instead of changing the runtime code, we can be more optimistic, we can have a simple unit test which verifies that SELECT * FROM SomeTable does not have a column named rn. If we use this approach, and later on someone adds a column named rn, then the unit test will fail and we shall know that there is a problem before the changes hit production.

Documenting assumptions in unit tests is a very powerful technique.

Of course, it is only useful if you strictly control deployments, which I do.

June 8, 2010 5:57 PM
 

Rob Farley said:

Except for the fact that you don't want 'rn' in the outer query, I'd actually prefer to list the columns in the subquery and use 'select *' in the outer.

Reason being that on a subquery that has joins, I'm more likely to find that a * in there will break the query because someone puts a "modifieddate" on both tables, whereas if I've restricted my columns in a subquery, I'm almost always okay about using "select *" on the outside.

But I think there's a more controversial argument in place for whether you allow "select *" in environments that aren't using subqueries, which could potentially return extra columns if the tables beneath are modified... that's a very different discussion.

Rob

June 8, 2010 9:44 PM
 

Jared Ko said:

Agreed that unit tests should sniff out something like this prior to deployment. However, you'd have to have some pretty high code coverage metrics >80% to ensure that you weren't going to get bitten by this particular scenario. This is fine if you have a tight control over you team but I still believe it to be a bad practice.

I see a funny paradox, though. You're trying to save a little bit of time by using "SELECT *" but you're spending a great deal of time on test cases. At least, you'd have to spend a lot of time to get the adequate coverage levels to catch errors like this.

Would you choose to not put table prefixes in front of your column names if only one table in the JOIN condition had the column name? It seems like this would be doing the same thing.

BTW: Thanks for continuing to engage me on this debate. I'm enjoying the discussion and hope I'm not upsetting/offending you in my responses. It's hard to guage response when speaking with a stranger. :)

June 8, 2010 9:54 PM
 

Alexander Kuznetsov said:

Rob,

I think I do need to explicitly list my columns once, so unprotected SELECT * should be avoided. The whole point of this post is that I do not need more than one layer of this protection.

Whether our code should survive "potentially return extra columns if the tables beneath are modified..." that's indeed a very different discussion.

Maybe next time.

June 9, 2010 10:28 AM
 

Alexander Kuznetsov said:

Jared,

My unit test coverage may be very high, typically 100%, because my customers need very robust long-term solutions.

Once all my code is covered with tests and works, refactoring begins. As I refactor my C# code, it can shrink 2-3 times, still passing all tests, still performing well. .Net is very good at facilitating code reuse. Once I am done with my refactoring, my code is lean, succinct, and better maintainable.

T-SQL is less good at code reuse, and is much much more verbose. Instead of succinct

var results = GetMyResults();

I have to do this:

DECLARE @results TABLE(...)

INSERT INTO @results(...) SELECT ... FROM

Essentially I am repeating myself three times, which is unproductive. As I am refactoring my T-SQL code, I aim to make it more succinct, which usually means better maintainable. This is why I want to shrink a redundant column list back to *, to have leaner, better maintainable, code., to avoid repeating myself over and over again.

I like this discussion, so please do not hesitate to post further comments.

June 9, 2010 1:59 PM
 

Jared Ko said:

@Alexander,

There's a school of thought that says that going beyond a certain level of code coverage has an extremely high cost with a minimum payoff. I would assume that it takes you as much effort to get from 80% to 90% as it would take you to get from 0% to 80%.

I don't understand why you would go to all the trouble of hitting 100% coverage but not want to spend the extra effort to ensure that you're writing bulletproof code.  Surely you're losing less than a minute to actually type the column names (I personally just highlight over the object name, press ALT-F1, and copy the column names or take advantage of Intellisense).

Agreed that typing those column names three times (as you put in your most-recent example) is a very tedious process. Manually lining is up to make it readable (the same way that .net automatically does it) takes even more time.

All that being said, if have 100% coverage then you are truly an edge case and you're probably safe to use "SELECT *" if you're running those test cases with each build.

June 10, 2010 2:37 PM
 

Alexander Kuznetsov said:

>>There's a school of thought that says that going beyond a certain level of code coverage has an extremely high cost with a minimum payoff. I would assume that it takes you as much effort to get from 80% to 90% as it would take you to get from 0% to 80%.<<

My coworker Jay Fields frequently blogs about unit testing:

http://jayfields.com/

A very interesting read.

Yet blanket estimates can be very wrong. It depends on your code. Unit testing error handling code is very difficult, no agrument here:

http://www.simple-talk.com/sql/t-sql-programming/close-these-loopholes---reproduce-database-errors/

>>I don't understand why you would go to all the trouble of hitting 100% coverage<<

We develop as many unit tests as we need, on case by case basis. In some cases it is cheaper to write them up front, rather than troubleshoot and still write them later. In other cases it is cheaper to not write them at all...

>if you're running those test cases with each build.

Yes, CI does it for me.

Overall, deciding how many coverage you need is an entirely different, and a very interesting, topic.

June 10, 2010 3:56 PM
 

Jared Ko said:

Thanks for the link on reproducing DB Errors. The introduction of a temporary constraint is an interesting concept that I never considered.

You didn't properly respond to my comment/question, though. I'm not asking why you have 100% coverage. I agree that that's a different topic.

It seems that if you were to go to such pains to reach high coverage numbers then it would be a no-brainer to expand the column names in your code. Arguably, you're taking about one extra minute of coding your stored procedure to ensure that you'd never have to go in and fix it (the column names) if one of your test cases failed.

June 10, 2010 7:19 PM
 

Alexander Kuznetsov said:

Temporary check constraints are nice, but they are not invoked on deletes. Use temporary triggers if you want to force deletes to break.

Answering your original question one more time:

The motivation for collapsing a column list into a * is this: getting more succinct, more elegant code.

This (collapsing a column list into a *) is typically done after everything works, as refactoring/code reuse.

IMO explicitly listing column names instead of every * does not make code any safer. Just one explicit list is enough. This has nothing to do with test coverage and such, just making code more readable for the typical developer, more maintainable etc.

June 11, 2010 4:09 PM
 

Mike C said:

I'm with Rob F. on this one - if I were going to do this I'd put the * in the outer query and explicitly name the columns in the subquery.  I like Jared's test, but I think I'd have to do more testing to be sure that the subquery was not returning more than it needed to the outer query (using different indexes, different combinations of columns, etc.)

June 11, 2010 8:00 PM
 

René Valencourt said:

Brian said:

>> ...Data Modeling is a single image that expresses a single idea.  Put another way.  Programming can be done in parts and then tied together.  Data Modeling is done as a whole, then defines its smaller parts.  At least that's my approach.  I'm usually called in to fix the problems caused by piecemeal modeling.

As someone with more than three decades training and experience with programming, data modeling, and database administration, I tend to agree with Brian.  I agree with the comment

>>"Rules" are handy for people who don't understand or aren't interested in understanding exactly what they are doing.  But when you *really* know what you are doing and why you are doing it you can always break the rules.

But, I see an incredible ignorance of the principles of data modeling on a lot of database administration resource sites.  Data modeling <> database programming <> database administration.  People tend to think that they know more than they really do...

Too much programming of any kind is done with little thought to maintainability or performance later.  But the principles of ease of maintenance and good performance are often in tension with each other, and not always are both, or sometimes even either, an issue.  However, I would say that to ignore maintainability is to tempt fate...

June 21, 2010 11:30 AM

Leave a Comment

(required) 
(required) 
Submit

About Alexander Kuznetsov

Alex Kuznetsov has been working with object oriented languages, mostly C# and C++, as well as with databases for more than a decade. He has worked with Sybase, SQL Server, Oracle and DB2. He regularly blogs on sqlblog.com, mostly about database unit testing, defensive programming, and query optimization. Alex has written a book entitled "Defensive Database Programming with Transact-SQL" and several articles on simple-talk.com and devx.com. Currently he works at DRW Trading Group in Chicago, where he leads a team of developers, practicing agile development, defensive programming, TDD, and database unit testing.

This Blog

Syndication

Powered by Community Server (Commercial Edition), by Telligent Systems
  Privacy Statement