<style> .reveal-up, .reveal-left, .reveal-right, .reveal-stagger > * { opacity: 1 !important; transform: none !important; } </style>
  1. Learn
  2. SQL
  3. Creating and changing data
All lessons in this course

Lesson 7 of 54 15 minBeginner

Creating tables and choosing types

Write CREATE TABLE statements: columns, types, NULL rules and a primary key. Plus computed columns, temporary tables and scripts you can safely run twice.

So far you have read BuildMart’s tables. Now you will build your own. CREATE TABLE names the table and lists its columns, each with a type and a NULL rule. BuildMart is about to start buying from suppliers, so it needs a Suppliers table:

Query.sql
CREATE TABLE dbo.Suppliers (    SupplierId  int           NOT NULL IDENTITY PRIMARY KEY,    Name        nvarchar(60)  NOT NULL,    Phone       varchar(20)   NULL,    CreditLimit decimal(10,2) NOT NULL,    Rating      tinyint       NULL,    CreatedAt   datetime2(0)  NOT NULL); SELECT * FROM dbo.Suppliers;
Output
SupplierId  Name  Phone  CreditLimit  Rating  CreatedAt
----------  ----  -----  -----------  ------  ---------

The table exists and is empty: the SELECT shows its columns and no rows. Line by line:

  • dbo.Suppliers: always include the schema when you create something.
  • NOT NULL / NULL: whether a value is required. If you leave it out, SQL Server usually assumes NULL is allowed, so say it every time.
  • IDENTITY: SQL Server numbers new rows itself (1, 2, 3, …). It has its own lesson later in this module.
  • PRIMARY KEY: the column that identifies each row. It must be unique and can never be NULL.

Design it yourself

The design below starts with several classic mistakes. Fix the types, NULL rules and key until the warnings disappear:

Interactive

Design the Suppliers table

ColumnTypeNULL allowedPrimary key
SupplierId
Name
Phone
CreditLimit
Rating
CreatedAt
CreateSuppliers.sql
CREATE TABLE dbo.Suppliers (    SupplierId  int           NOT NULL,    Name        varchar(60)   NOT NULL,    Phone       int           NULL,    CreditLimit float         NOT NULL,    Rating      tinyint       NOT NULL,    CreatedAt   nvarchar(20)  NOT NULL);
  • No primary key: nothing stops two identical suppliers, and other tables have nothing reliable to point at.
  • varchar silently changes letters outside its code page. Supplier names are typed by people: use nvarchar.
  • Phone numbers are not numbers: int drops the leading 0 of 01904 555 010 and cannot hold spaces or +44.
  • float is approximate: sums of money drift by fractions of a penny. Use decimal.
  • Dates stored as text sort as text, accept 2024-02-30, and need converting before any date maths. Use datetime2 or date.

Choosing a type: a checklist

The data is…UseNot
an id or a countint (or bigint for billions)text, float
money, a price, a measurementdecimal(p, s)float, money (it rounds oddly in division)
text a person typesnvarchar(n) with a realistic nnvarchar(max) “just in case”
a code you control (Shipped, GB)varchar(n) or char(n)nvarchar(max)
a phone number, a postcode, an account numbervarchar(n): it is text that happens to contain digitsint (loses leading zeros)
a datedatetext
a moment in timedatetime2(0) (to the second) or datetimeoffsettext, the older datetime
yes / nobitchar(1) with Y and N

Primary keys: natural or surrogate?

A natural key is a real-world value that is already unique, such as an ISBN or a country code. A surrogate key is a number made up for the purpose, such as SupplierId. Natural keys sound neat but have a habit of changing (people change email addresses, companies change names) or turning out not to be unique. Most tables use a surrogate int IDENTITY key and add a UNIQUE constraint on the natural value; the Constraints lesson shows how.

Computed columns

A column can be defined as an expression of other columns. It is worked out whenever you read it, so it can never disagree with them:

Query.sql
CREATE TABLE dbo.QuoteLines (    QuoteLineId int           NOT NULL IDENTITY PRIMARY KEY,    Product     nvarchar(60)  NOT NULL,    Quantity    int           NOT NULL,    UnitPrice   decimal(10,2) NOT NULL,    LineTotal   AS (Quantity * UnitPrice)); INSERT INTO dbo.QuoteLines (Product, Quantity, UnitPrice)VALUES (N'Cement 25 kg', 20, 7.20), (N'Sand 25 kg', 15, 4.10); SELECT Product, Quantity, UnitPrice, LineTotal FROM dbo.QuoteLines;
Output
Product       Quantity  UnitPrice  LineTotal
------------  --------  ---------  ---------
Cement 25 kg        20       7.20     144.00
Sand 25 kg          15       4.10      61.50

Temporary tables

A table whose name starts with # is temporary: it lives in tempdb, only your connection can see it, and it disappears when you disconnect. It is handy for holding intermediate results while you work something out.

Query.sql
CREATE TABLE #Basket (    ProductId int NOT NULL,    Quantity  int NOT NULL); INSERT INTO #Basket (ProductId, Quantity) VALUES (4, 10), (5, 10); SELECT * FROM #Basket;
Output
ProductId  Quantity
---------  --------
        4        10
        5        10

Scripts you can run twice

Run the same CREATE TABLE twice and the second run fails, because the name is taken:

Query.sql
CREATE TABLE dbo.Products (ProductId int);
Output
Msg 2714, Level 16, State 6, Line 1
There is already an object named 'Products' in the database.

DROP TABLE IF EXISTS removes a table only when it is there, which is how BuildMart’s setup script can be re-run to reset everything. Use it in scripts that build test data; never casually against real data, because the rows go with the table.

Query.sql
DROP TABLE IF EXISTS dbo.Deliveries; CREATE TABLE dbo.Deliveries (    DeliveryId  int          NOT NULL IDENTITY PRIMARY KEY,    OrderId     int          NOT NULL,    DeliveredOn date         NULL); SELECT name FROM sys.tables WHERE name = 'Deliveries';
Output
name
----------
Deliveries
Quick check

Which type suits a UK phone number such as 01904 555 010?

Quick check

What happens to a #Basket table when your SSMS window disconnects?

Practice

Create a table dbo.Vans for BuildMart’s delivery vans: VanId (a whole-number IDENTITY primary key), Registration (up to 8 characters, required), PayloadKg (a whole number, required) and LastServiced (a date, may be unknown). Then list its columns and NULL rules from INFORMATION_SCHEMA.COLUMNS.

Show a hint

Registration plates are codes you control, so varchar(8) fits. Order the column list by ORDINAL_POSITION.

Show the solution
Solution.sql
CREATE TABLE dbo.Vans (    VanId        int        NOT NULL IDENTITY PRIMARY KEY,    Registration varchar(8) NOT NULL,    PayloadKg    int        NOT NULL,    LastServiced date       NULL); SELECT COLUMN_NAME, DATA_TYPE, IS_NULLABLEFROM INFORMATION_SCHEMA.COLUMNSWHERE TABLE_NAME = 'Vans'ORDER BY ORDINAL_POSITION;
Output
COLUMN_NAME   DATA_TYPE  IS_NULLABLE
------------  ---------  -----------
VanId         int        NO
Registration  varchar    NO
PayloadKg     int        NO
LastServiced  date       YES

Remember

  • CREATE TABLE schema.Name (column type NULL|NOT NULL, …); say NULL or NOT NULL for every column.
  • Choose types by what the data means: ids int, money decimal, people’s text nvarchar(n), codes varchar, dates date.
  • Every table needs a primary key: unique, never NULL, ideally a short surrogate int IDENTITY.
  • Computed columns (AS expression) are worked out on read and cannot be written to.
  • #Temp tables live only for your connection; DROP TABLE IF EXISTS makes setup scripts re-runnable.
0 of 54 lessons completed