Build a Query in Two Phases: Organize First, Itemize Second

Build a query in two phases, and most of your errors disappear before they start. Phase one is to organize: get the tables, joins and filters working with SELECT *. Phase two is to itemize: replace the star with the column list you want.

Two boxes joined by an arrow. Phase one, organize, runs SELECT star in three steps that return 4, 7 and 5 rows. Phase two, itemize, swaps the star for a column list and returns the same 5 rows

The Sample Tables

Four branches and seven staff members give us something to join. Boise has no staff on purpose. Run this in any test database. Every result in this post comes from SQL Server 2025.

CREATE TABLE Branch (BranchID INT PRIMARY KEY, City VARCHAR(20) NOT NULL, State CHAR(2) NOT NULL);
CREATE TABLE Staff (StaffID INT PRIMARY KEY, FirstName VARCHAR(20) NOT NULL, LastName VARCHAR(20) NOT NULL, BranchID INT NOT NULL);
INSERT INTO Branch (BranchID, City, State) VALUES (1, 'Seattle', 'WA'), (2, 'Spokane', 'WA'), (3, 'Portland', 'OR'), (4, 'Boise', 'ID');
INSERT INTO Staff (StaffID, FirstName, LastName, BranchID) VALUES
  (1, 'Avery', 'Lin', 1), (2, 'Casey', 'Moore', 1), (3, 'Jordan', 'Ellis', 1), (4, 'Riley', 'Park', 2),
  (5, 'Morgan', 'Reed', 2), (6, 'Quinn', 'Hayes', 3), (7, 'Sam', 'Ortiz', 3);
GO

Why the Column List Comes Last

The column list is the first thing you type, and it names columns from tables you haven’t joined yet. Write it too early and the first run fails. This query lists staff columns but reads only the Branch table.

SELECT FirstName, LastName, City, State FROM Branch;

SQL Server returns Msg 207 twice, once for FirstName and once for LastName. SELECT * can’t produce that error. It takes whatever columns the tables have. Line one is always safe, so you can focus on the harder part.

Phase One: Organize

Resist the urge to type the final column list early. The star costs nothing now, and it keeps your attention on the joins.

Start with one table and check it works. Add the join and check again. Add the filter last. Each query below builds on the one before it.

SELECT * FROM Branch;

SELECT *
FROM Branch
INNER JOIN Staff ON Branch.BranchID = Staff.BranchID;

SELECT *
FROM Branch
INNER JOIN Staff ON Branch.BranchID = Staff.BranchID
WHERE State = 'WA';

The row counts are 4, 7 and 5. After each step, ask whether the count makes sense. This is what “working” means: no error, and the rows you expect. There are seven staff members, so the join should return seven rows. A count query confirms it.

SELECT (SELECT COUNT(*) FROM Staff) AS StaffRows,
  (SELECT COUNT(*) FROM Branch INNER JOIN Staff ON Branch.BranchID = Staff.BranchID) AS JoinRows;

Both counts are 7. Every staff row found its branch. Then the filter keeps only the five rows for Washington.

Three SQL Server errors with their messages and fixes. Msg 207 is an invalid column name when the column list comes first. Msg 209 is an ambiguous column name when two tables share a column. Msg 156 is incorrect syntax near a keyword used as a name

Phase Two: Itemize

Once the star version runs, change the first line to the columns you need. Nothing else moves.

SELECT FirstName, LastName, City, State
FROM Branch
INNER JOIN Staff ON Branch.BranchID = Staff.BranchID
WHERE State = 'WA';

That returns five rows with four columns. If the list includes a column that exists in both tables, SQL Server asks you to choose.

SELECT BranchID, FirstName, City
FROM Branch
INNER JOIN Staff ON Branch.BranchID = Staff.BranchID
WHERE State = 'WA';

The answer is Msg 209, ambiguous column name BranchID. Give each table an alias and put it in front of the column. This is also the moment to add the alias to every column, which makes the query easier to read.

SELECT b.BranchID, s.FirstName, b.City
FROM Branch AS b INNER JOIN Staff AS s ON b.BranchID = s.BranchID
WHERE b.State = 'WA';

When Square Brackets Are Required

Brackets belong in phase two as well, because the column list is where a keyword shows up as a name. Order is a keyword, so an unbracketed table of that name fails.

CREATE TABLE Order (OrderID INT, Note VARCHAR(10));

Wrap the name in square brackets and it works. Names with spaces need them too.

CREATE TABLE #Brackets ([Order] INT, [Project Name] VARCHAR(10));
SELECT [Order], [Project Name] FROM #Brackets;

Not every keyword needs brackets, though. A reader asked what State does in T-SQL. It’s an ordinary column name here, and the query above uses it without brackets. I tried 14 words as column names. Date, Level, Name, State, Status and Type worked. Order, Group, Key, Percent, Select, Table, User and Index failed. Brackets are safe on any name, so many people use them on every one.

Fourteen words tried as column names without brackets. Date, Level, Name, State, Status and Type work. Index, Group, Key, Order, Percent, Select, Table and User fail with a syntax error

Clean Up

Remove the sample tables when you finish.

DROP TABLE Staff;
DROP TABLE Branch;

What to Remember

Organize first: tables, joins and filter with SELECT *, checking the row count at each step. Itemize last: write the column list and prefix each column with an alias. Don’t leave SELECT * in a finished query that other people will run.

To go further, read SELECT * in Production Code: The Hidden Costs, Replace SELECT * with Column Names in SQL Server: SQL in Sixty Seconds #017 and Introduction to JOINs in SQL Server: INNER, OUTER and CROSS JOIN.

Quick reference card: Two Phases of a Query

A query is not written top to bottom, it is built from the tables outward.

Published by Pinal Dave on SQLAuthority. More of my work at pinaldave.com.


Discover more from SQL Authority with Pinal Dave

Subscribe to get the latest posts sent to your email.

Best Practices, SQL Coding Standards, SQL Joins, SQL Scripts
Previous Post
Finding Apostrophes in SQL Server Text: Straight and Curly
Next Post
Unmatched Records Query: Four Ways to Find Rows With No Match

Related Posts

8 Comments. Leave new

  • Option 1 for keywords, but there actually seems to be two classes of keywords, and some require the brackets and others don’t. Texas, USA. Thanks for doing this Pinal – hope it helps some people out.

    Reply
  • Nakul Vachhrajani
    August 4, 2011 2:39 pm

    While there can be many scenarios to use square brackets (I use them as a standard practice in my production code for all columns), but out of the proivded choices only one scenario is when use of square braces is mandatory:

    Correct Answer: #1. The table name conflicts with a keyword

    Why?
    Database object names are identifiers (literal strings). The rules for an identifier clearly state that:

    The identifier must not be a Transact-SQL reserved word. SQL Server reserves both the uppercase and lowercase versions of reserved words.

    When identifiers are used in Transact-SQL statements, the identifiers that do not comply with these rules must be delimited by double quotation marks or brackets.

    Reference:http://msdn.microsoft.com/en-us/library/ms175874.aspx

    By the way, contrary to popular belief, one can have a table name same as the database name.

    Country of residence: India

    Reply
  • Sudeepta Ganguly
    August 4, 2011 11:55 pm

    I think the answer is.. The “name” conflicts with a “keyword”. It might be the table name, column name, or the database name. For example, if we want to create a database “GRANT”, we need to execute the following command:
    USE [master];
    GO
    CREATE DATABASE [GRANT];
    GO

    However, if we execute the above command without the brackets, then we will receive error:
    USE [master];
    GO
    CREATE DATABASE GRANT;
    GO

    The same error will be return for a table as well.

    Sudeepta,
    India

    Reply
  • The correct answer is #1, but it’s also required when field name contains spaces or other special characters. Check this interesting blog by George Mastros

    Naomi from USA

    Reply
  • I’m sorry I don’t have an answer, but a question. I have yet to fully understand joins. What did you mean specifically by “get all your joins working?” Did you mean syntactically correct and not throwing errors, or did you mean only returning at most the same number of rows as exist in the first listed table, assuming no filtering and a left/inner join? I feel like anyone who can REALLY understand joins and perform them perfectly, will inevitably become a spectacular query writer. :)

    Reply
  • What does the ‘State’ keyword do in tsql?

    Reply

Leave a Reply

Your email address will not be published. Required fields are marked *

Fill out this field
Fill out this field
Please enter a valid email address.