Drizzle | First Normal Form (1NF)

The First Normal Form (1NF) is the first step in the normalization process of a database. It ensures that every column in a table contains only atomic values, and that the table has unique column names and no repeating groups.

Key Concepts

  1. Atomic Values Only: Each column should contain indivisible values — no lists, arrays, or nested records.
  2. Consistent Data Types: Every value in a column must be of the same type (e.g., all integers or all strings).
  3. Unique Rows: Every row should be uniquely identifiable, typically using a primary key.
  4. Distinct Column Names: No duplicate column names, each column represents a single attribute.
  5. Order Independence: The order of rows or columns does not affect the table’s meaning or function.
  6. No Repeating Groups: Avoid multiple columns storing the same type of data (e.g., skill1, skill2, skill3).

The 1NF Rule

To achieve 1NF, a table must satisfy the following conditions:

Example with Atomicity Violation

The following table violates 1NF because the courses column contains multiple values in a single field (e.g., “Math, Physics”). This breaks the atomicity rule.

schema.ts
schema.sql
schema.txt
import { integer, pgTable, varchar, text } from "drizzle-orm/pg-core";

export const students = pgTable("students", {
  id: integer("id").notNull().primaryKey(),
  name: varchar("name", { length: 255 }).notNull(),
  courses: text("courses").notNull(),
});

To bring the table into 1NF, we separate courses into a standalone table and use a join table (enrollments) to represent the many-to-many relationship between students and courses.

schema.ts
schema.sql
schema.txt
import { integer, pgTable, primaryKey, varchar } from "drizzle-orm/pg-core";

export const students = pgTable("students", {
  id: integer("id").notNull().primaryKey(),
  name: varchar("name", { length: 255 }).notNull(),
});

export const courses = pgTable("courses", {
  id: integer("id").notNull().primaryKey(),
  name: varchar("name", { length: 255 }).notNull().unique(),
});

export const enrollments = pgTable("enrollments", {
  studentId: integer("student_id")
    .notNull()
    .references(() => students.id, { onDelete: "cascade" }),
  courseId: integer("course_id")
    .notNull()
    .references(() => courses.id, { onDelete: "cascade" }),
}, (t) => [
  primaryKey({ columns: [t.studentId, t.courseId] }),
]);

With this structure:

Example with Repeating Groups Violation

This structure violates 1NF because it uses repeating groups — multiple columns (skill1, skill2, skill3) to store the same kind of data: skills.

schema.ts
schema.sql
schema.txt
import { integer, pgTable, varchar } from "drizzle-orm/pg-core";

export const employees = pgTable("employees", {
  id: integer("id").notNull().primaryKey(),
  name: varchar("name", { length: 255 }).notNull(),
  skill1: varchar("skill1", { length: 255 }),
  skill2: varchar("skill2", { length: 255 }),
  skill3: varchar("skill3", { length: 255 })
});

We need to eliminate the repeating skill columns and store each skill as a separate record, using a new table to link employees to their skills.

schema.ts
schema.sql
schema.txt
import { integer, pgTable, primaryKey, varchar } from "drizzle-orm/pg-core";

export const employees = pgTable("employees", {
  id: integer("id").notNull().primaryKey(),
  name: varchar("name", { length: 255 }).notNull(),
});

export const skills = pgTable("skills", {
  id: integer("id").notNull().primaryKey(),
  name: varchar("name", { length: 255 }).notNull().unique(),
});

export const employeeSkills = pgTable("employee_skills", {
  employeeId: integer("employee_id")
    .notNull()
    .references(() => employees.id, { onDelete: "cascade" }),
  skillId: integer("skill_id")
    .notNull()
    .references(() => skills.id, { onDelete: "cascade" }),
}, (table) => [
  primaryKey({ columns: [table.employeeId, table.skillId] }),
]);

With this structure:

In practice, most 1NF rules - like having unique column names, consistent data types, unique rows (via primary keys), and order independence are either enforced automatically by the database system or are built into standard SQL table design. Because of that, we don’t show examples of those rules in this guide.