Stage 3: Backend Essentials

Database · Lesson 01

Database Concepts

Relational versus NoSQL thinking, tables, keys and where SQLite fits.

Database3 min readFree lesson

A JSON file works until two users write at the same time, until you need to filter a million records, or until a half-written file corrupts everything. Databases exist to solve those problems once, properly.

Learning objectives

  • Explain what a relational database stores and how tables relate.
  • Distinguish primary keys, foreign keys and unique constraints.
  • Model one-to-many and many-to-many relationships.
  • Decide when SQLite is enough and when a server database is needed.
  • Compare relational thinking with document databases.

1. Tables, rows, columns

A relational database stores data in tables. Each row is one record; each column has a type and a meaning.

users
id | username | email             | created_at
1  | kim      | [email protected]   | 2026-01-04
2  | lee      | [email protected]   | 2026-01-05

The schema — table names, columns, types, constraints — is the contract. The application writes data that fits it, and the database rejects anything that does not.

2. Keys and constraints

Primary key — uniquely identifies a row.

id INTEGER PRIMARY KEY AUTOINCREMENT

Foreign key — a column that points at another table's primary key. It is how relationships are enforced.

user_id INTEGER NOT NULL,
FOREIGN KEY (user_id) REFERENCES users(id)

Other constraints:

Constraint Meaning
NOT NULL value is required
UNIQUE no duplicate values (emails, usernames)
DEFAULT value used when none is supplied
CHECK value must satisfy an expression (price >= 0)

Constraints are cheaper than application bugs. A duplicate email rejected by the database cannot slip through a race condition in your code.

3. Relationships

One-to-many — one user writes many posts. The foreign key lives on the "many" side:

users 1 ─── N posts (posts.user_id → users.id)

Many-to-many — an order contains many products, a product appears in many orders. SQL cannot store a list in a column, so you add a join table:

orders 1 ─── N order_items N ─── 1 products

order_items holds order_id, product_id, quantity and the price at the time of purchase. Copying the price is deliberate: product prices change, order history must not.

One-to-one — a user has one profile. Use a separate table with a unique foreign key, or merge the tables if the data is always present.

4. SQLite

SQLite is a complete relational database stored in a single file. No server, no accounts, no ports.

Good fit: learning, prototypes, desktop and mobile apps, small sites, tests, embedded tools.

Not the right fit: many concurrent writers, multi-server deployments, datasets that need replicas and failover. For those, use PostgreSQL or MySQL — the SQL you learn here transfers almost unchanged.

5. Relational vs NoSQL

Relational (SQL) Document (MongoDB)
Tables with fixed columns Collections of flexible documents
Relations via foreign keys Embedding or references
Strong constraints Validation in the application or schema
JOINs across tables Usually data shaped for one query

Neither is "better". Relational suits data with clear relationships and integrity requirements — orders, payments, enrolment. Document stores suit nested, variable-shaped content — logs, catalogues, CMS pages. Pick based on how the data is read and how strictly it must be consistent.

6. Normalisation in one paragraph

Store each fact once. If a customer's address lives in every order row, updating it means updating hundreds of rows and missing one creates contradictory data. Move the address to customers and reference it. The exception is deliberate denormalisation for read performance — but make that a decision, not an accident.

Practice

Design the shop schema in the assignment: users, products, orders and order items. Draw the relationships before writing any SQL.

Example files

Complete, runnable files used in this lesson. Copy them into your project and experiment.

schema.sqlSQL
-- ============================================
-- 강의01 예제: 학생 테이블 스키마
-- ============================================

-- 기존 테이블이 있으면 삭제 (초기화용)
DROP TABLE IF EXISTS students;

-- 학생 테이블 생성
CREATE TABLE students (
    id      INTEGER PRIMARY KEY AUTOINCREMENT,  -- 자동 증가 기본키
    name    TEXT    NOT NULL,                   -- 학생 이름 (필수)
    age     INTEGER,                            -- 나이
    grade   TEXT    NOT NULL,                   -- 학년 (예: '1학년')
    email   TEXT    UNIQUE                      -- 이메일 (중복 불가)
);

-- 샘플 데이터 삽입
INSERT INTO students (name, age, grade, email) VALUES
    ('김철수', 20, '1학년', '[email protected]'),
    ('이영희', 21, '2학년', '[email protected]'),
    ('박민준', 22, '3학년', '[email protected]'),
    ('최수진', 20, '1학년', '[email protected]');

-- 전체 학생 조회
SELECT * FROM students;

-- 1학년만 조회
SELECT name, email FROM students WHERE grade = '1학년';

Assignment

Assignment 01 — Design a shop schema

Create schema.sql for a small shop. It must run from top to bottom in SQLite.

Tables

users

Column Type Rules
id INTEGER primary key, autoincrement
username TEXT required, unique
password TEXT required (store a hash, never plain text)
email TEXT required, unique
created_at TEXT default to datetime('now')

products — id, name (required), price (required, integer paise/rupees), stock (default 0), category, description.

orders — id, user_id (required, references users), status (default 'pending'), total_price (required), ordered_at (default now).

order_items — id, order_id and product_id (both required, both references), quantity (default 1), unit_price (required).

Requirements

  1. Every table has a primary key.
  2. Foreign keys are declared, not just implied by column names.
  3. order_items is the join table between orders and products.
  4. Add at least two sample users, three products and one order with two items.
  5. Finish with a SELECT that joins all four tables and shows username, order_id, product_name, quantity and subtotal.

Hints

CREATE TABLE orders (
    id          INTEGER PRIMARY KEY AUTOINCREMENT,
    user_id     INTEGER NOT NULL,
    status      TEXT    DEFAULT 'pending',
    total_price INTEGER NOT NULL,
    ordered_at  TEXT    DEFAULT (datetime('now')),
    FOREIGN KEY (user_id) REFERENCES users(id)
);
(oi.quantity * oi.unit_price) AS subtotal

Done when

  • sqlite3 shop.db < schema.sql runs without errors.
  • Deleting a user with orders is rejected (or handled explicitly with ON DELETE).
  • The final join returns one row per order item.
Show the reference solution

Try the assignment yourself first — comparing your attempt with a working solution is where most of the learning happens.

schema.sqlSQL
-- ============================================
-- 과제01 정답: 쇼핑몰 DB 스키마 설계
-- ============================================

-- ─────────────────────────────────────────
-- 1. 회원 테이블
--    쇼핑몰에 가입한 사용자 정보를 저장
-- ─────────────────────────────────────────
CREATE TABLE users (
    id         INTEGER PRIMARY KEY AUTOINCREMENT, -- 회원 고유 번호 (자동 증가)
    username   TEXT    NOT NULL UNIQUE,           -- 로그인 아이디 (중복 불가)
    password   TEXT    NOT NULL,                  -- 비밀번호 (실제로는 해시 저장)
    email      TEXT    NOT NULL UNIQUE,           -- 이메일 (중복 불가)
    created_at TEXT    DEFAULT (datetime('now'))  -- 가입일시
);

-- ─────────────────────────────────────────
-- 2. 상품 테이블
--    판매할 상품 정보를 저장
-- ─────────────────────────────────────────
CREATE TABLE products (
    id          INTEGER PRIMARY KEY AUTOINCREMENT, -- 상품 고유 번호
    name        TEXT    NOT NULL,                  -- 상품명
    price       INTEGER NOT NULL,                  -- 가격 (원 단위)
    stock       INTEGER DEFAULT 0,                 -- 재고 수량
    category    TEXT,                              -- 카테고리 (예: '전자제품', '의류')
    description TEXT                               -- 상품 설명
);

-- ─────────────────────────────────────────
-- 3. 주문 테이블
--    회원이 생성한 주문 하나를 나타냄
--    (여러 상품을 담은 장바구니 전체)
-- ─────────────────────────────────────────
CREATE TABLE orders (
    id          INTEGER PRIMARY KEY AUTOINCREMENT, -- 주문 고유 번호
    user_id     INTEGER NOT NULL,                  -- 주문한 회원 (FK → users.id)
    status      TEXT    DEFAULT 'pending',         -- 주문 상태 (pending/paid/shipped/done)
    total_price INTEGER NOT NULL,                  -- 주문 총 금액
    ordered_at  TEXT    DEFAULT (datetime('now')), -- 주문 일시
    FOREIGN KEY (user_id) REFERENCES users(id)    -- 외래키: users 테이블 참조
);

-- ─────────────────────────────────────────
-- 4. 주문 상품 테이블 (중간 테이블)
--    주문 하나에 여러 상품이 포함될 수 있으므로
--    orders와 products를 연결하는 중간 테이블
--    → N:M 관계를 1:N + N:1 로 분리
-- ─────────────────────────────────────────
CREATE TABLE order_items (
    id          INTEGER PRIMARY KEY AUTOINCREMENT, -- 주문상품 고유 번호
    order_id    INTEGER NOT NULL,                  -- 어느 주문 (FK → orders.id)
    product_id  INTEGER NOT NULL,                  -- 어느 상품 (FK → products.id)
    quantity    INTEGER NOT NULL DEFAULT 1,        -- 수량
    unit_price  INTEGER NOT NULL,                  -- 주문 시점 단가 (상품 가격이 나중에 바뀔 수 있으므로 별도 저장)
    FOREIGN KEY (order_id)   REFERENCES orders(id),   -- 외래키: orders 테이블 참조
    FOREIGN KEY (product_id) REFERENCES products(id)  -- 외래키: products 테이블 참조
);

-- ─────────────────────────────────────────
-- 텍스트 ERD
-- ─────────────────────────────────────────
--
--  users (1) ──── (N) orders (1) ──── (N) order_items (N) ──── (1) products
--
--  설명:
--  - 한 회원(users)은 여러 주문(orders)을 할 수 있다 → 1:N
--  - 한 주문(orders)에 여러 주문상품(order_items)이 있다 → 1:N
--  - 한 상품(products)이 여러 주문상품(order_items)에 포함될 수 있다 → 1:N
--  - orders와 products는 order_items를 통해 N:M 관계
-- ─────────────────────────────────────────

-- ─────────────────────────────────────────
-- 샘플 데이터
-- ─────────────────────────────────────────
INSERT INTO users (username, password, email) VALUES
    ('kim', 'hashed_pw_1', '[email protected]'),
    ('lee', 'hashed_pw_2', '[email protected]');

INSERT INTO products (name, price, stock, category) VALUES
    ('노트북',    1500000, 10, '전자제품'),
    ('마우스',      30000, 50, '전자제품'),
    ('키보드',      80000, 30, '전자제품');

-- 김철수의 주문 (노트북 1개 + 마우스 2개)
INSERT INTO orders (user_id, total_price) VALUES (1, 1560000);

INSERT INTO order_items (order_id, product_id, quantity, unit_price) VALUES
    (1, 1, 1, 1500000),  -- 노트북 1개
    (1, 2, 2,   30000);  -- 마우스 2개

-- 주문 내역 조회 (JOIN 예시)
SELECT
    u.username,
    o.id        AS order_id,
    p.name      AS product_name,
    oi.quantity,
    oi.unit_price,
    (oi.quantity * oi.unit_price) AS subtotal
FROM order_items oi
JOIN orders   o ON oi.order_id   = o.id
JOIN users    u ON o.user_id     = u.id
JOIN products p ON oi.product_id = p.id;

Learn this lesson with a mentor

Work through the Build Your Homepage series on your own, or join a guided cohort at our Pitampura campus and online.

Call NowEnroll Now