SQL Server and T-SQL Basics: Build an Invoice Report

T-SQL is SQL Server’s SQL dialect, extending relational queries with variables, batches, identity columns and procedural features.

Lesson 95 of 102Beginner11 min read

This SQL Server lesson builds a small invoice table and selects the largest open invoices. It explains T-SQL’s identity syntax, TOP row limits, schema qualification and batch conventions without assuming previous SQL Server experience. The data includes two invoices for the same customer so ordering and record identity remain visible.

Connect to an existing disposable SQL Server database in a suitable client. Verify the database context and use an account authorised to create the practice table. The schema dbo is used explicitly to show that a table name has a namespace as well as a database.

Database, schema and client batches

SQL Server databases contain schemas such as dbo, which contain tables and other objects. dbo.invoices explicitly identifies the intended namespace. USE can change the database context where the client and permissions allow it, but it is not a PostgreSQL connection-switch instruction. A production connection string should normally identify the target database deliberately.

GO is a batch separator understood by clients and tools such as SQL Server utilities; it is not an ordinary T-SQL statement executed by the server in the same way as SELECT. Do not send GO through a driver that does not process it. Semicolons terminate statements, and ending the statement before a WITH clause avoids a common CTE parsing ambiguity.

Identity, decimal and Unicode fields

IDENTITY(1,1) generates integer values starting at one and increasing by one according to SQL Server’s allocation rules. The primary key still establishes the key constraint. Identity values can have gaps and do not by themselves represent customer-visible invoice numbering. When a statement needs to return an inserted identity, an OUTPUT clause is a direct statement-level tool; other connection or scope functions have their own rules.

The example customer field uses NVARCHAR for Unicode text and its literals use the N prefix. DECIMAL(10,2) gives an exact decimal amount representation. BIT is commonly used for boolean-like saved values in SQL Server, rather than copying PostgreSQL BOOLEAN declarations. Field types should follow the required domain rather than being translated by name alone.

TOP and deterministic result ordering

TOP (2) belongs immediately after SELECT in T-SQL. It restricts the result to two records but does not decide which two unless ORDER BY defines their priority. ORDER BY amount DESC,id ASC requests larger amounts first and resolves ties with the unique key. A default storage order or clustered index should not be treated as a substitute for an output-order clause.

TOP ... WITH TIES can return more records than its nominal count when the boundary ORDER BY values tie. Adding a unique id to that ordering removes complete-row ties, so choose the order according to the intended contract. For offset pagination, SQL Server uses ORDER BY with OFFSET and FETCH rather than a MySQL-style trailing LIMIT.

Aggregates and conversion rules

COUNT(*) counts invoice rows, SUM(amount) totals known values and AVG(amount) measures their arithmetic mean. SQL Server’s AVG result type depends on the input type; integer input can lose fractional intent unless cast appropriately. Decimal monetary input should be used with the required precision and rounding policy, not converted to floating point simply to print a decimal.

CAST(expression AS type) is standard-style, while CONVERT(type,expression,style) is T-SQL’s conversion form. TRY_CAST and TRY_CONVERT can produce NULL for many failed permitted conversions, helping an import identify bad text without treating it as a legitimate zero. Retain the raw import field and validity indicator so originally missing and invalid values remain distinguishable.

Transactions and application parameters

BEGIN TRANSACTION, COMMIT and ROLLBACK group related row changes. If invoice creation and its line-item insertion are one business operation, define how failure prevents a partial saved invoice. SQL Server error handling often uses TRY...CATCH with appropriate transaction-state checks; production handling should be designed for the actual failure cases rather than copied as unexplained boilerplate.

Bind customer names and thresholds through the database driver. T-SQL variables use an at-sign, but driver parameter syntax can differ from handwritten local variables. A query that includes an at-sign placeholder still needs proper value binding; interpolating user text into it does not become safe merely because the tutorial mentions parameters.

Local staging and execution plans

A local temporary table commonly has a single-hash name such as #invoice_stage. Its scope differs from a permanent dbo table and from a global temporary table. Keep multi-statement staging on the intended connection. A CTE lasts only for its statement and should not be treated as a reusable session table.

SQL Server provides estimated and actual execution-plan facilities. Use them to inspect access paths and row estimates after correctness is established. An actual plan accompanies execution, so consider effects for write statements. An index on status and amount might help an open-invoice queue, but its benefit depends on selectivity, ordering and maintenance costs. Measure instead of assuming any indexed field makes a query fast.

Run the complete practice setup

Run this T-SQL setup once. Omitting the identity field from the inserts lets SQL Server allocate it in the fresh practice table. The amounts are required, so every summary value in this specific example has complete monetary input.

SQL
CREATE TABLE dbo.tsql_invoices (
 id INTEGER IDENTITY(1,1) PRIMARY KEY,
 customer NVARCHAR(40) NOT NULL,
 amount DECIMAL(10,2) NOT NULL CHECK(amount>=0),
 status VARCHAR(10) NOT NULL
);
INSERT INTO dbo.tsql_invoices (customer,amount,status) VALUES
 (N'Ana',40,'open'),(N'Ben',20,'paid'),(N'Ana',30,'open'),(N'Cara',30,'open');

Practical exercise and worked solution

Produce one summary row for open invoices with their record count and total amount. Do not include paid invoice 2.

SQL
SELECT COUNT(*) AS open_count,SUM(amount) AS open_total
FROM dbo.tsql_invoices WHERE status='open';
Output
open_count | open_total
3 | 100.00

Open amounts are forty, thirty and thirty, giving a total of one hundred across three records. Both Ana invoices count separately because they are separate liabilities. COUNT(DISTINCT customer) would measure different customers instead and return two, which is not an invoice count. If amounts later become optional, include completeness measures before presenting this as a full outstanding balance.

Official documentation

Consult the T-SQL tutorial, TOP reference and CAST/CONVERT reference for the demonstrated features.

SQL Server and T-SQL Basics: Build an Invoice Report Examples

Each example shows the code, the output it produces and what each part does.

Example 1

Select the largest two open invoices

Three invoices are open. Forty ranks first, while the two thirties tie on amount. The ascending identity key chooses invoice 3 ahead of 4 at the two-record boundary. Ana appears twice because the result lists invoices, not distinct customers.

SQL
SELECT TOP (2) id,customer,amount
FROM dbo.tsql_invoices WHERE status='open'
ORDER BY amount DESC,id ASC;
Output
id | customer | amount
1 | Ana | 40.00
3 | Ana | 30.00

Common SQL Server and T-SQL Basics: Build an Invoice Report Mistakes

Sending client-only GO as SQL

A script that works in an editor can fail when an application sends GO directly to the server through a driver. The client normally splits the script into batches before execution. Keep driver commands as valid server statements, or use an authorised script runner that deliberately processes batch separators. Similarly, do not append LIMIT to this T-SQL query; translate it to TOP or the appropriate OFFSET/FETCH form.

SQL Server and T-SQL Basics: Build an Invoice Report FAQ

Is T-SQL different from SQL?

It is a database-specific dialect within the SQL family, used by Microsoft SQL Server and Azure SQL. It adds features such as TOP, IDENTITY, variables declared with DECLARE and procedural code, alongside standard SELECT, JOIN and GROUP BY.

Does GO work in every driver?

No. GO is a batch separator understood by tools such as SQL Server Management Studio and sqlcmd, a client batch convention, not a shared server statement. Sending GO through an application driver causes a syntax error; send each batch separately instead.

Does IDENTITY promise a consecutive invoice number?

No. Identity values can skip after rollbacks, failed inserts or server restarts. Use a separate numbering policy, such as a dedicated table or a sequence with careful handling, if the business requires consecutive numbers.

How do I limit rows in SQL Server?

Use SELECT TOP (10) ... ORDER BY ..., or for paging, ORDER BY ... OFFSET 20 ROWS FETCH NEXT 10 ROWS ONLY. SQL Server doesn't support LIMIT. Always include ORDER BY, so "top" has a defined meaning.

Is SQL Server free to learn on?

Yes. The Developer edition is free for development and testing, and the Express edition is free for small production use. Azure Data Studio and SQL Server Management Studio are free tools, and SQL Server also runs in Docker on Linux and macOS.

SQL Server and T-SQL Basics: Build an Invoice Report Exercises

Answer each one and check it instantly. If you get stuck, open the hint before the answer.

Multiple choicemedium

Why does invoice 3 appear before invoice 4 at the boundary?

SQL Server and T-SQL Basics: Build an Invoice Report Quiz

2 questions to check what you learned.

  1. Which T-SQL feature generates the sample ids?

    • AUTO_INCREMENT
    • A client GO separator
    • IDENTITY(1,1)
  2. What does GO represent in common SQL Server scripts?

    • A client-interpreted batch separator
    • A WHERE condition
    • A portable SQL command that changes a database