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.

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);
GOWhy 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.

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.

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.

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.





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.
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
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
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
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. :)
What does the ‘State’ keyword do in tsql?