Table Column Attributes  «Prev  Next»

Lesson 9

Table Column Attributes: Module Conclusion

This module turned a bare CREATE TABLE statement into a fully specified one, one attribute at a time. A column isn't just a name; it's a bundle of decisions, datatype, size, nullability, default, and sometimes a value-generation rule or a custom type of your own, and every one of those decisions shapes what the column can hold, how much space it consumes, and what the database engine will and won't let through at insert time. The eight lessons in this module walked through that full set of decisions in order, and this page pulls them back together into one coherent picture before the next module moves on.

None of these attributes work in isolation, either. A column's datatype decides what precision and scale arguments even apply to it; its nullability decides whether a default is doing double duty, filling a gap and guaranteeing a value always exists, or just a convenience; and whether it needs to generate its own values decides whether IDENTITY, ROWGUIDCOL, both, or neither belongs on it. Reviewing all eight lessons together, rather than as eight disconnected facts, is what turns "I remember covering nullability" into being able to look at an unfamiliar table and immediately see which of its columns were designed carefully and which weren't.

Module 4 at a Glance

LessonAttributeKey Idea
1Column attributes, generallyA column's attributes form a contract that SQL Server enforces automatically at insert time
2DatatypeMatch each column to the narrowest type that genuinely fits every value it will hold
3Length, precision, scaleAlways state the size argument explicitly; a bare declaration falls back to a smaller default than expected
4NullabilityState NULL/NOT NULL explicitly; prefer NULL over a sentinel value for genuinely unknown data
5Default valuesA DEFAULT fills in a missing value on INSERT, but never overrides an explicit NULL
6IDENTITYGenerates sequential values automatically; still needs its own uniqueness constraint
7ROWGUIDCOLMarks a table's canonical GUID column for FILESTREAM, replication, and generic tooling
8User-defined typesCREATE TYPE keeps a repeated column declaration consistent across many tables

Lesson 1: What a Column Attribute Is

The module's introduction made the case for treating column attributes as a subject of their own, not an afterthought to table-creation syntax, by contrasting a table with no attributes thought through against one where every column's type, nullability, and default were chosen deliberately. A table built from generic VARCHAR(50) columns with nothing else specified will accept a HireDate of "last Tuesday" without complaint; a table built with the right attributes rejects that kind of bad data the moment it's offered, rather than letting it surface as a mystery three reports downstream:

-- Everything is a string, so nothing stops bad data:
CREATE TABLE Employees (
    EmployeeID VARCHAR(50), EmployeeName VARCHAR(50),
    HireDate   VARCHAR(50), Salary       VARCHAR(50)
);

-- Attributes declared up front reject it instead:
CREATE TABLE Employees (
    EmployeeID   INT IDENTITY(1,1) PRIMARY KEY,
    EmployeeName VARCHAR(100) NOT NULL,
    HireDate     DATE NOT NULL DEFAULT (GETDATE())
);
That contrast is the thread every later lesson in this module pulled on.

Lesson 2: Choosing the Right Datatype

Lesson 2 covered the full catalog SQL Server 2025 makes available: exact numerics (INT, DECIMAL) for values that must add up exactly, approximate numerics (FLOAT, REAL) for scientific data that can tolerate a tiny representational error, character strings in both non-Unicode and Unicode families, binary strings for raw byte data, and SQL Server 2025's newer AI and semi-structured types, native JSON and the embedding-oriented VECTOR(n) type, alongside the long-standing XML type. It also covered the specialized types worth knowing by name, UNIQUEIDENTIFIER, ROWVERSION, HIERARCHYID, GEOGRAPHY/GEOMETRY, and flagged TEXT, NTEXT, and IMAGE as deprecated types to replace with their MAX-length equivalents in any new design.

Lesson 3: Length, Precision, and Scale

Lesson 3 covered the size argument that follows many datatype names, length for character and binary types, precision and scale for DECIMAL/NUMERIC, and fractional-second precision for DATETIME2/TIME, along with the two different ways an oversized value fails: character and binary overflow is rejected outright, while decimal scale overflow is rounded rather than truncated. It also covered a cost many people overlook, oversized key columns can push a composite index past SQL Server's 900-byte key-size limit, and the asymmetry between growing a column's size later, cheap, and shrinking one, which requires checking every existing value against the new limit first.

Lesson 4: Allowing or Disallowing NULL

Lesson 4 covered nullability as its own deliberate attribute, controlled with explicit NULL/NOT NULL keywords rather than left to a session or database default. It made the case for NULL over a sentinel value like -1 or a placeholder date, since a sentinel is indistinguishable from a real value unless every query remembers to filter it out, while NULL has unambiguous, built-in support in IS NULL, aggregate functions, and joins. It also covered the two-step backfill pattern, update existing NULLs, then tighten the column, needed before ALTER COLUMN can enforce NOT NULL on a column that already has rows.

Lesson 5: Specifying Default Column Values

Lesson 5 covered the DEFAULT constraint, the value SQL Server substitutes when an INSERT leaves a column unspecified, from a literal constant to a system function call like SYSDATETIME() or JSON_OBJECT():

OrderStatus VARCHAR(20) NOT NULL
    CONSTRAINT DF_Orders_OrderStatus DEFAULT ('Pending')
It covered explicit constraint naming to keep a default easy to find and change later, the behavioral difference between adding a default to an existing column (future inserts only) versus a brand-new one (WITH VALUES to backfill existing rows), and the four specific circumstances under which a default actually fires, most notably that an explicit NULL in an INSERT ... SELECT bypasses the default rather than triggering it.

Lesson 6: The IDENTITY Property

Lesson 6 covered IDENTITY(seed, increment), the standard way to generate a self-incrementing surrogate key without application code tracking the next available value. It covered the rules (one identity column per table, an integer or exact-numeric type, NOT NULL):

CREATE TABLE dbo.Orders (
    OrderID INT IDENTITY(1, 1) NOT NULL PRIMARY KEY
);
It also covered how to find existing identity columns with sys.identity_columns or the $IDENTITY pseudocolumn, the difference between SCOPE_IDENTITY(), @@IDENTITY, and IDENT_CURRENT() for retrieving a just-generated value, and two points worth remembering together: IDENTITY only generates values, it doesn't by itself guarantee uniqueness, and gaps in the sequence are normal, expected behavior rather than a sign of corruption.

Lesson 7: The ROWGUIDCOL Attribute Flag

Lesson 7 covered ROWGUIDCOL, a marker on a single UNIQUEIDENTIFIER column that designates it as a table's canonical row identifier for SQL Server's own subsystems and generic scripts, mandatory for FILESTREAM storage and replication, recommended for schema-agnostic $ROWGUID queries, and otherwise optional:

StreamGuid UNIQUEIDENTIFIER ROWGUIDCOL NOT NULL
    CONSTRAINT UQ_StreamGuid UNIQUE
    CONSTRAINT DF_StreamGuid DEFAULT (NEWSEQUENTIALID())
Like IDENTITY in the previous lesson, ROWGUIDCOL doesn't generate values or enforce uniqueness on its own; that still takes an explicit DEFAULT and a UNIQUE or PRIMARY KEY constraint. The lesson also weighed a GUID's real cost, 16 bytes per row and weaker clustered-index locality than a compact integer identity column, against its one genuine advantage: uniqueness that holds across independent databases, not just within one table.

Lesson 8: Creating and Using Your Own Datatypes

Lesson 8 covered CREATE TYPE, which builds a named alias type from an existing base type, length, and nullability, so that every column meant to hold the same kind of value, an address, say, stays consistent across every table that uses it. It covered the lack of an ALTER TYPE statement, meaning a definition change requires dropping and recreating the type once nothing references it anymore, the distinct, non-interchangeable purpose of a table type for table-valued parameters, and the simpler CLR user-defined-type alternative for cases that genuinely need custom behavior rather than just a named alias.

Recurring Themes Across This Module

Three ideas kept resurfacing across otherwise unrelated lessons, and it's worth naming them explicitly now that the whole module is in view.

State it explicitly; don't lean on a fallback. Lesson 3 showed that a bare varchar or decimal column silently falls back to a length of 1 or a scale of 0, not the maximum anyone would expect. Lesson 4 showed the same pattern for nullability: a column with neither NULL nor NOT NULL stated falls back to a session or database setting that's invisible at the point you're reading the CREATE TABLE statement. Lesson 5 extended this to defaults: a value left out of an INSERT only gets a sensible default if one was actually declared. In every case, the fix is the same habit: write down what you mean on the column itself, rather than trusting a setting that lives somewhere else and can differ from one environment to the next.

A value-generation feature doesn't automatically guarantee uniqueness. Lesson 6's IDENTITY property and Lesson 7's ROWGUIDCOL flag both generate values for you, and it's tempting to assume that generation implies no two rows can ever collide. Neither one actually enforces that on its own; a PRIMARY KEY or UNIQUE constraint is what does the actual checking, in both cases, on every single insert. Pairing a generation attribute with an explicit constraint, rather than assuming the generation attribute covers it, is the pattern that holds up in both lessons.

Every size and type choice trades storage against capability. Lesson 2's Unicode types cost twice the storage of their non-Unicode counterparts in exchange for the full range of international characters. Lesson 3's oversized columns waste space and can even block the indexing you'd planned. Lesson 7's UNIQUEIDENTIFIER costs 16 bytes per row, four times an INT identity column, in exchange for uniqueness that holds across independent databases rather than just one table. None of these trade-offs has a single universally correct answer; each one depends on what the column actually needs to do, which is exactly why this module spent a full lesson on each attribute rather than treating column design as one-size-fits-all.

Self-Check: Questions Worth Asking About Any Column

Before writing a CREATE TABLE statement for a real table, this module's eight lessons boil down to a short set of questions worth running through for every single column:
  • What's the narrowest datatype that genuinely fits every value this column will ever hold (Lesson 2)?
  • What length, precision, scale, or fractional-second precision does that datatype actually need, stated explicitly rather than left to a default (Lesson 3)?
  • Can this column's value ever genuinely be unknown or not applicable, and if so, is NULL the right way to represent that rather than a sentinel value (Lesson 4)?
  • Does this column need a value supplied automatically when one isn't given, and if so, what should that value be (Lesson 5)?
  • Does this column need to generate its own sequential value, and does it need a UNIQUE or PRIMARY KEY constraint alongside that generation (Lesson 6)?
  • Does this table need a stable, globally unique row identifier for replication, FILESTREAM, or schema-agnostic tooling, beyond what an ordinary primary key already provides (Lesson 7)?
  • Is this exact combination of base type, length, and nullability going to repeat across enough other columns or tables that a user-defined type is worth the consistency it buys (Lesson 8)?
Running through that list for every column, rather than reaching for a generic VARCHAR(50) out of habit, is the single biggest difference between a table that quietly accumulates bad data and one that rejects it at the door.

Putting the Whole Module Together

Every attribute this module covered shows up at once in a single, realistic table definition. The order the attributes appear below matches the order the lessons covered them:

CREATE TYPE address FROM VARCHAR(100) NOT NULL;  -- Lesson 8: user-defined type

CREATE TABLE dbo.Orders
(
    OrderID          INT IDENTITY(1, 1) NOT NULL  -- Lesson 6: IDENTITY
        CONSTRAINT PK_Orders_OrderID PRIMARY KEY CLUSTERED,

    OrderGuid        UNIQUEIDENTIFIER ROWGUIDCOL NOT NULL  -- Lesson 7: ROWGUIDCOL
        CONSTRAINT UQ_Orders_OrderGuid UNIQUE NONCLUSTERED
        CONSTRAINT DF_Orders_OrderGuid DEFAULT (NEWSEQUENTIALID()),  -- Lesson 5: DEFAULT

    ShipToAddress    address NOT NULL,            -- Lesson 8: user-defined type; Lesson 4: NOT NULL

    DiscountPercent  DECIMAL(5, 2) NOT NULL        -- Lesson 2 & 3: datatype, precision, scale
        CONSTRAINT DF_Orders_DiscountPercent DEFAULT (0.00),

    OrderStatus      VARCHAR(20) NOT NULL          -- Lesson 2: datatype; Lesson 3: length
        CONSTRAINT DF_Orders_OrderStatus DEFAULT ('Pending'),

    CreatedAt        DATETIME2(3) NOT NULL         -- Lesson 2 & 3: datatype, fractional-second precision
        CONSTRAINT DF_Orders_CreatedAt DEFAULT (SYSDATETIME())
);
Every column here reflects a choice, not a default left unmade: an integer identity column for a compact, self-generating primary key; a ROWGUIDCOL column, carefully distinguished from that primary key, for a table that might eventually replicate; a user-defined type keeping every address column in the database consistent; precise, deliberately scaled decimal and date types; and every single column with its nullability and default stated explicitly rather than left to chance. None of that is incidental. It's the direct, visible result of working through this module's eight lessons one attribute at a time, and it's the standard this course will hold every table definition to from here forward.

Revisiting Lesson 1's Employees Table

Lesson 1 opened this module with a deliberately bare Employees table, four columns, all VARCHAR(50), nothing else specified, to show what a table looks like when none of this module's lessons have been applied yet. It's worth closing the module by rebuilding that same table with everything covered since then:

CREATE TYPE phoneNumber FROM VARCHAR(20) NULL;  -- Lesson 8

CREATE TABLE dbo.Employees
(
    EmployeeID   INT IDENTITY(1, 1) NOT NULL           -- Lesson 6
        CONSTRAINT PK_Employees_EmployeeID PRIMARY KEY CLUSTERED,

    EmployeeGuid UNIQUEIDENTIFIER ROWGUIDCOL NOT NULL   -- Lesson 7
        CONSTRAINT UQ_Employees_EmployeeGuid UNIQUE NONCLUSTERED
        CONSTRAINT DF_Employees_EmployeeGuid DEFAULT (NEWSEQUENTIALID()),

    EmployeeName NVARCHAR(100) NOT NULL,                -- Lesson 2, 3, 4

    HireDate     DATE NOT NULL                          -- Lesson 2, 4
        CONSTRAINT DF_Employees_HireDate DEFAULT (GETDATE()),  -- Lesson 5

    Phone        phoneNumber NULL,                      -- Lesson 8, 4

    Salary       DECIMAL(9, 2) NOT NULL                 -- Lesson 2, 3
);
Every column in this version reflects a deliberate choice rather than an accident of having typed VARCHAR(50) four times in a row. That's the real payoff of this module: not memorizing eight separate pieces of syntax, but building the habit of asking, for every column in every table from here on, exactly what this page's self-check list asks.

What's Ahead

With every column attribute covered, the next module turns from defining a table's columns to the broader work of creating, managing, and constraining the tables themselves in SQL Server 2025. Before moving on, it's worth revisiting this module's exercises and quiz, linked from their respective lessons, to make sure each attribute covered here feels natural rather than merely familiar.

Sql Server Column Attributes - Quiz

Before moving on to the next module, click the Quiz link below on the left to check your knowledge of the material covered in this module.
SQL-Server Column Attributes - Quiz

SEMrush Software 9 SEMrush Banner 9