Projeto Final: Sistema de Blog

Projeto Final: Sistema de Blog

Esta lição é o projeto final do tutorial de SQL. Aplicaremos todo o conhecimento aprendido até agora para projetar e implementar o banco de dados completo de um sistema de blog do zero.

🎯 Analogia da Vida Real

Construir um sistema de blog é como abrir uma livraria:

Parte 1: Análise de Requisitos

Requisitos Funcionais

Módulo Funcionalidades
Usuários Registro, login, perfil, seguir outros usuários
Artigos Publicar, editar, excluir (exclusão lógica), rascunho, publicar
Categorias Gerenciamento de categorias de artigos (suporta categorias hierárquicas)
Tags Marcar artigos com tags (muitos-para-muitos)
Comentários Comentários em artigos, respostas aninhadas
Análises Rankings de artigos populares, estatísticas de usuários, estatísticas de tags

Estimativas de Volume de Dados

Tabela Volume Estimado Proporção Leitura/Escrita
users 100K Leitura intensiva
articles 1M Leitura muito superior à escrita
comments 5M Leitura/escrita equilibrada
tags 1K Leitura intensiva
categories 100 Leitura intensiva

Parte 2: Design do Banco de Dados

Diagrama ER

TEXT
[users] 1 ──── N [articles] N ──── N [tags]
   │                  │
   │                  │ 1
   │                  │
   │                  N
   │             [comments]
   │                  │
   N                  │ (autorreferência)
   │                  │
[users] 1 ──── N [comments]

[categories] 1 ──── N [articles]
     │
     └── 1 (autorreferência, parent_id)

Descrições dos Relacionamentos

Relacionamento Tipo Implementação
Usuário → Artigo Um-para-Muitos articles.user_id
Artigo → Tag Muitos-para-Muitos Tabela de junção article_tag
Artigo → Comentário Um-para-Muitos comments.article_id
Usuário → Comentário Um-para-Muitos comments.user_id
Comentário → Comentário Autorreferência (aninhado) comments.parent_id
Categoria → Artigo Um-para-Muitos articles.category_id
Categoria → Categoria Autorreferência (hierárquico) categories.parent_id
Usuário → Usuário Muitos-para-Muitos (seguir) Tabela de junção user_follows

Parte 3: Scripts de Criação de Tabelas

SQL
-- =============================================
-- Schema do Banco de Dados do Sistema de Blog
-- Conjunto de caracteres: utf8mb4 (suporta emoji e multilíngue)
-- Motor de armazenamento: InnoDB (suporta transações e chaves estrangeiras)
-- =============================================

-- Criar banco de dados
CREATE DATABASE IF NOT EXISTS blog_system
    DEFAULT CHARACTER SET utf8mb4
    DEFAULT COLLATE utf8mb4_unicode_ci;

USE blog_system;

-- ----------------------------
-- 1. Tabela de usuários
-- ----------------------------
CREATE TABLE users (
    id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT 'ID do usuário',
    username VARCHAR(50) NOT NULL COMMENT 'Nome de usuário',
    email VARCHAR(100) NOT NULL COMMENT 'E-mail',
    password_hash VARCHAR(255) NOT NULL COMMENT 'Hash da senha',
    nickname VARCHAR(50) COMMENT 'Apelido',
    avatar_url VARCHAR(500) COMMENT 'URL do avatar',
    bio VARCHAR(500) COMMENT 'Biografia',
    website VARCHAR(200) COMMENT 'Site pessoal',
    status TINYINT NOT NULL DEFAULT 1 COMMENT 'Status: 1-ativo 0-desabilitado 2-verificação pendente',
    role VARCHAR(20) NOT NULL DEFAULT 'user' COMMENT 'Papel: user/author/admin',
    article_count INT NOT NULL DEFAULT 0 COMMENT 'Contagem de artigos (desnormalizado)',
    follower_count INT NOT NULL DEFAULT 0 COMMENT 'Contagem de seguidores (desnormalizado)',
    last_login_at DATETIME COMMENT 'Último login',
    created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT 'Data de registro',
    updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT 'Data de atualização',
    is_deleted TINYINT NOT NULL DEFAULT 0 COMMENT 'Exclusão lógica: 0-normal 1-excluído',
    UNIQUE KEY uk_username (username),
    UNIQUE KEY uk_email (email),
    INDEX idx_status (status),
    INDEX idx_created_at (created_at)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='Tabela de usuários';

-- ----------------------------
-- 2. Tabela de relacionamento de seguidores (muitos-para-muitos)
-- ----------------------------
CREATE TABLE user_follows (
    follower_id BIGINT NOT NULL COMMENT 'ID do seguidor',
    following_id BIGINT NOT NULL COMMENT 'ID do seguido',
    created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT 'Data do seguimento',
    PRIMARY KEY (follower_id, following_id),
    INDEX idx_following_id (following_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='Tabela de relacionamento de seguidores';

-- ----------------------------
-- 3. Tabela de categorias (suporta categorias hierárquicas)
-- ----------------------------
CREATE TABLE categories (
    id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT 'ID da categoria',
    name VARCHAR(50) NOT NULL COMMENT 'Nome da categoria',
    slug VARCHAR(100) NOT NULL COMMENT 'Slug da URL',
    description VARCHAR(200) COMMENT 'Descrição da categoria',
    parent_id BIGINT DEFAULT NULL COMMENT 'ID da categoria pai, NULL para nível superior',
    sort_order INT NOT NULL DEFAULT 0 COMMENT 'Ordem de classificação',
    article_count INT NOT NULL DEFAULT 0 COMMENT 'Contagem de artigos (desnormalizado)',
    created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
    UNIQUE KEY uk_slug (slug),
    INDEX idx_parent_id (parent_id),
    INDEX idx_sort (sort_order)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='Tabela de categorias';

-- ----------------------------
-- 4. Tabela de tags
-- ----------------------------
CREATE TABLE tags (
    id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT 'ID da tag',
    name VARCHAR(50) NOT NULL COMMENT 'Nome da tag',
    slug VARCHAR(100) NOT NULL COMMENT 'Slug da URL',
    article_count INT NOT NULL DEFAULT 0 COMMENT 'Contagem de artigos (desnormalizado)',
    created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
    UNIQUE KEY uk_name (name),
    UNIQUE KEY uk_slug (slug)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='Tabela de tags';

-- ----------------------------
-- 5. Tabela de artigos
-- ----------------------------
CREATE TABLE articles (
    id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT 'ID do artigo',
    user_id BIGINT NOT NULL COMMENT 'ID do autor',
    category_id BIGINT COMMENT 'ID da categoria',
    title VARCHAR(200) NOT NULL COMMENT 'Título',
    slug VARCHAR(200) NOT NULL COMMENT 'Slug da URL',
    summary VARCHAR(500) COMMENT 'Resumo',
    content LONGTEXT NOT NULL COMMENT 'Contúdo do corpo',
    cover_image_url VARCHAR(500) COMMENT 'Imagem de capa',
    status TINYINT NOT NULL DEFAULT 0 COMMENT 'Status: 0-rascunho 1-publicado 2-não listado',
    is_pinned TINYINT NOT NULL DEFAULT 0 COMMENT 'Fixado: 0-não 1-sim',
    view_count INT NOT NULL DEFAULT 0 COMMENT 'Contagem de visualizações',
    like_count INT NOT NULL DEFAULT 0 COMMENT 'Contagem de curtidas',
    comment_count INT NOT NULL DEFAULT 0 COMMENT 'Contagem de comentários (desnormalizado)',
    published_at DATETIME COMMENT 'Data de publicação',
    created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT 'Data de criação',
    updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT 'Data de atualização',
    is_deleted TINYINT NOT NULL DEFAULT 0 COMMENT 'Exclusão lógica: 0-normal 1-excluído',
    UNIQUE KEY uk_slug (slug),
    INDEX idx_user_id (user_id),
    INDEX idx_category_id (category_id),
    INDEX idx_status_published (status, published_at DESC),
    INDEX idx_is_pinned (is_pinned),
    INDEX idx_created_at (created_at DESC),
    FULLTEXT INDEX ft_title_content (title, content)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='Tabela de artigos';

-- ----------------------------
-- 6. Tabela de associação artigo-tag (muitos-para-muitos)
-- ----------------------------
CREATE TABLE article_tag (
    article_id BIGINT NOT NULL COMMENT 'ID do artigo',
    tag_id BIGINT NOT NULL COMMENT 'ID da tag',
    PRIMARY KEY (article_id, tag_id),
    INDEX idx_tag_id (tag_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='Tabela de associação artigo-tag';

-- ----------------------------
-- 7. Tabela de comentários (suporta respostas aninhadas)
-- ----------------------------
CREATE TABLE comments (
    id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT 'ID do comentário',
    article_id BIGINT NOT NULL COMMENT 'ID do artigo',
    user_id BIGINT NOT NULL COMMENT 'ID do comentarista',
    parent_id BIGINT DEFAULT NULL COMMENT 'ID do comentário pai, NULL para nível superior',
    reply_to_user_id BIGINT DEFAULT NULL COMMENT 'Usuário sendo respondido',
    content TEXT NOT NULL COMMENT 'Conteúdo do comentário',
    like_count INT NOT NULL DEFAULT 0 COMMENT 'Contagem de curtidas',
    status TINYINT NOT NULL DEFAULT 1 COMMENT 'Status: 0-pendente 1-aprovado 2-rejeitado',
    created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
    is_deleted TINYINT NOT NULL DEFAULT 0 COMMENT 'Exclusão lógica',
    INDEX idx_article_id (article_id),
    INDEX idx_user_id (user_id),
    INDEX idx_parent_id (parent_id),
    INDEX idx_created_at (created_at DESC)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='Tabela de comentários';

-- ----------------------------
-- 8. Tabela de curtidas de artigos
-- ----------------------------
CREATE TABLE article_likes (
    user_id BIGINT NOT NULL COMMENT 'ID do usuário',
    article_id BIGINT NOT NULL COMMENT 'ID do artigo',
    created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
    PRIMARY KEY (user_id, article_id),
    INDEX idx_article_id (article_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='Tabela de curtidas de artigos';

Parte 4: Operações CRUD

Gerenciamento de Usuários

SQL
-- Registrar um novo usuário
INSERT INTO users (username, email, password_hash, nickname)
VALUES ('zhangsan', 'zhangsan@example.com', SHA2('mypassword123', 256), 'Zhang San');

-- Login do usuário (consultar por nome de usuário)
SELECT id, username, nickname, avatar_url, role
FROM users
WHERE username = 'zhangsan' AND password_hash = SHA2('mypassword123', 256)
    AND status = 1 AND is_deleted = 0;

-- Atualizar perfil
UPDATE users
SET nickname = 'Zhang Sanfeng',
    bio = 'Amo programação, amo a vida',
    website = 'https://zhangsan.dev'
WHERE id = 1 AND is_deleted = 0;

-- Exclusão lógica do usuário
UPDATE users SET is_deleted = 1 WHERE id = 1;

-- Consultar detalhes do usuário (excluir deletados)
SELECT id, username, email, nickname, avatar_url, bio, website,
       article_count, follower_count, created_at
FROM users
WHERE id = 1 AND is_deleted = 0;

Gerenciamento de Categorias

SQL
-- Criar categorias de nível superior
INSERT INTO categories (name, slug, description, sort_order)
VALUES ('Tecnologia', 'tech', 'Artigos relacionados à tecnologia', 1),
       ('Vida', 'life', 'Ensaios sobre a vida', 2),
       ('Leitura', 'reading', 'Notas de livros', 3);

-- Criar subcategorias
INSERT INTO categories (name, slug, description, parent_id, sort_order)
VALUES ('Frontend', 'frontend', 'Desenvolvimento frontend', 1, 1),
       ('Backend', 'backend', 'Desenvolvimento backend', 1, 2),
       ('Banco de Dados', 'database', 'Tecnologia de banco de dados', 1, 3),
       ('JavaScript', 'javascript', 'Linguagem JavaScript', 4, 1),
       ('Python', 'python', 'Linguagem Python', 5, 1);

-- Consultar árvore de categorias
SELECT
    c1.id, c1.name AS level1,
    c2.name AS level2,
    c3.name AS level3
FROM categories c1
LEFT JOIN categories c2 ON c2.parent_id = c1.id
LEFT JOIN categories c3 ON c3.parent_id = c2.id
WHERE c1.parent_id IS NULL
ORDER BY c1.sort_order, c2.sort_order, c3.sort_order;

Gerenciamento de Tags

SQL
-- Criar tags
INSERT INTO tags (name, slug) VALUES
    ('MySQL', 'mysql'),
    ('JavaScript', 'javascript'),
    ('Python', 'python'),
    ('Docker', 'docker'),
    ('Redis', 'redis'),
    ('Vue.js', 'vuejs'),
    ('React', 'react'),
    ('Algoritmos', 'algorithms');

-- Consultar tags (ordenadas por contagem de artigos)
SELECT t.id, t.name, t.slug, t.article_count
FROM tags t
ORDER BY t.article_count DESC
LIMIT 20;

Gerenciamento de Artigos

SQL
-- Criar artigo (rascunho)
INSERT INTO articles (user_id, category_id, title, slug, summary, content, status)
VALUES (
    1,
    7,  -- Categoria JavaScript
    'Guia Prático de Otimização de Índices MySQL',
    'mysql-index-optimization-guide',
    'Este artigo detalha as estratégias principais e técnicas práticas de otimização de índices MySQL',
    '# Otimização de Índices MySQL\n\n## O que é um Índice\n\nUm índice é...',
    0  -- Rascunho
);

-- Publicar artigo
UPDATE articles
SET status = 1,
    published_at = NOW()
WHERE id = 1 AND user_id = 1;

-- Marcar o artigo com tags
INSERT INTO article_tag (article_id, tag_id) VALUES
    (1, 1),  -- MySQL
    (1, 8);  -- Algoritmos

-- Atualizar tags do artigo (deletar e depois inserir)
DELETE FROM article_tag WHERE article_id = 1;
INSERT INTO article_tag (article_id, tag_id) VALUES
    (1, 1),
    (1, 5);  -- MySQL + Redis

-- Exclusão lógica do artigo
UPDATE articles SET is_deleted = 1 WHERE id = 1 AND user_id = 1;

-- Atualizar campo desnormalizado: contagem de artigos do usuário
UPDATE users SET article_count = (
    SELECT COUNT(*) FROM articles WHERE user_id = 1 AND is_deleted = 0
) WHERE id = 1;

-- Atualizar campo desnormalizado: contagem de artigos da categoria
UPDATE categories SET article_count = (
    SELECT COUNT(*) FROM articles WHERE category_id = 7 AND status = 1 AND is_deleted = 0
) WHERE id = 7;

-- Atualizar campo desnormalizado: contagem de artigos da tag
UPDATE tags SET article_count = (
    SELECT COUNT(*) FROM article_tag WHERE tag_id = 1
) WHERE id = 1;

Gerenciamento de Comentários

SQL
-- Postar um comentário
INSERT INTO comments (article_id, user_id, content)
VALUES (1, 2, 'Ótimo artigo, aprendi muito!');

-- Responder a um comentário
INSERT INTO comments (article_id, user_id, parent_id, reply_to_user_id, content)
VALUES (1, 3, 1, 2, 'Concordo, especialmente a seção de índices ficou muito clara');

-- Atualizar contagem de comentários do artigo
UPDATE articles SET comment_count = (
    SELECT COUNT(*) FROM comments WHERE article_id = 1 AND status = 1 AND is_deleted = 0
) WHERE id = 1;

-- Exclusão lógica do comentário
UPDATE comments SET is_deleted = 1 WHERE id = 1 AND user_id = 2;

Seguimento de Usuários

SQL
-- Seguir um usuário
INSERT INTO user_follows (follower_id, following_id) VALUES (2, 1);
-- Atualizar contagem desnormalizada de seguidores
UPDATE users SET follower_count = follower_count + 1 WHERE id = 1;

-- Deixar de seguir
DELETE FROM user_follows WHERE follower_id = 2 AND following_id = 1;
UPDATE users SET follower_count = follower_count - 1 WHERE id = 1;

-- Consultar minha lista de seguindo
SELECT u.id, u.username, u.nickname, u.avatar_url, u.bio
FROM user_follows f
JOIN users u ON f.following_id = u.id
WHERE f.follower_id = 2 AND u.is_deleted = 0
ORDER BY f.created_at DESC;

-- Consultar minha lista de seguidores
SELECT u.id, u.username, u.nickname, u.avatar_url, u.bio
FROM user_follows f
JOIN users u ON f.follower_id = u.id
WHERE f.following_id = 1 AND u.is_deleted = 0
ORDER BY f.created_at DESC;

Parte 5: Consultas Complexas

Rankings de Artigos Populares (Últimos 30 Dias, Pontuação Composta de Visualizações e Curtidas)

SQL
SELECT
    a.id,
    a.title,
    a.slug,
    a.view_count,
    a.like_count,
    a.comment_count,
    a.published_at,
    u.username AS author,
    u.avatar_url AS author_avatar,
    c.name AS category_name,
    -- Pontuação de popularidade composta: visualizações×1 + curtidas×5 + comentários×3
    (a.view_count * 1 + a.like_count * 5 + a.comment_count * 3) AS hot_score,
    GROUP_CONCAT(t.name ORDER BY t.name SEPARATOR ', ') AS tags
FROM articles a
JOIN users u ON a.user_id = u.id
LEFT JOIN categories c ON a.category_id = c.id
LEFT JOIN article_tag at2 ON a.id = at2.article_id
LEFT JOIN tags t ON at2.tag_id = t.id
WHERE a.status = 1
    AND a.is_deleted = 0
    AND a.published_at >= DATE_SUB(NOW(), INTERVAL 30 DAY)
GROUP BY a.id
ORDER BY hot_score DESC
LIMIT 20;

Painel de Estatísticas do Usuário

SQL
SELECT
    u.id,
    u.username,
    u.nickname,
    u.article_count,
    u.follower_count,
    -- Total de visualizações
    IFNULL(SUM(a.view_count), 0) AS total_views,
    -- Total de curtidas
    IFNULL(SUM(a.like_count), 0) AS total_likes,
    -- Total de comentários
    IFNULL(SUM(a.comment_count), 0) AS total_comments,
    -- Data do último artigo publicado
    MAX(a.published_at) AS last_published_at,
    -- Média de visualizações por artigo
    IFNULL(ROUND(AVG(a.view_count), 0), 0) AS avg_views_per_article
FROM users u
LEFT JOIN articles a ON u.id = a.user_id AND a.status = 1 AND a.is_deleted = 0
WHERE u.id = 1 AND u.is_deleted = 0
GROUP BY u.id;

Página de Detalhes do Artigo (com Lista de Tags)

SQL
SELECT
    a.id,
    a.title,
    a.content,
    a.cover_image_url,
    a.view_count,
    a.like_count,
    a.comment_count,
    a.published_at,
    a.updated_at,
    u.id AS author_id,
    u.username AS author_username,
    u.nickname AS author_nickname,
    u.avatar_url AS author_avatar,
    u.bio AS author_bio,
    c.name AS category_name,
    c.slug AS category_slug
FROM articles a
JOIN users u ON a.user_id = u.id
LEFT JOIN categories c ON a.category_id = c.id
WHERE a.slug = 'mysql-index-optimization-guide'
    AND a.status = 1
    AND a.is_deleted = 0;

-- Consultar tags do artigo
SELECT t.id, t.name, t.slug
FROM article_tag at2
JOIN tags t ON at2.tag_id = t.id
WHERE at2.article_id = 1;

-- Incrementar contagem de visualizações
UPDATE articles SET view_count = view_count + 1 WHERE id = 1;

Lista de Comentários do Artigo (Estrutura Aninhada)

SQL
-- Consultar comentários de nível superior
SELECT
    c.id,
    c.content,
    c.like_count,
    c.created_at,
    u.id AS user_id,
    u.username,
    u.nickname,
    u.avatar_url
FROM comments c
JOIN users u ON c.user_id = u.id
WHERE c.article_id = 1
    AND c.parent_id IS NULL
    AND c.status = 1
    AND c.is_deleted = 0
ORDER BY c.created_at DESC
LIMIT 20;

-- Consultar respostas a um comentário específico
SELECT
    c.id,
    c.content,
    c.like_count,
    c.created_at,
    u.username,
    u.nickname,
    u.avatar_url,
    ru.username AS reply_to_username,
    ru.nickname AS reply_to_nickname
FROM comments c
JOIN users u ON c.user_id = u.id
LEFT JOIN users ru ON c.reply_to_user_id = ru.id
WHERE c.parent_id = 1
    AND c.status = 1
    AND c.is_deleted = 0
ORDER BY c.created_at ASC;

Lista de Artigos Associados a uma Tag

SQL
-- Consultar todos os artigos de uma tag específica
SELECT
    a.id,
    a.title,
    a.slug,
    a.summary,
    a.view_count,
    a.like_count,
    a.published_at,
    u.username AS author,
    u.avatar_url AS author_avatar
FROM article_tag at2
JOIN articles a ON at2.article_id = a.id
JOIN users u ON a.user_id = u.id
WHERE at2.tag_id = 1
    AND a.status = 1
    AND a.is_deleted = 0
ORDER BY a.published_at DESC
LIMIT 20;

Estatísticas de Artigos por Categoria

SQL
-- Contagem de artigos e média de visualizações por categoria
SELECT
    c.id,
    c.name,
    c.slug,
    c.article_count,
    IFNULL(COUNT(a.id), 0) AS actual_count,
    IFNULL(ROUND(AVG(a.view_count)), 0) AS avg_views,
    IFNULL(MAX(a.published_at), NULL) AS latest_article_at
FROM categories c
LEFT JOIN articles a ON c.id = a.category_id
    AND a.status = 1 AND a.is_deleted = 0
GROUP BY c.id
ORDER BY c.sort_order;

-- Consultar contagem de artigos de uma categoria e todas as suas subcategorias
WITH RECURSIVE category_tree AS (
    SELECT id, name, parent_id
    FROM categories
    WHERE id = 1  -- Categoria Tecnologia
    UNION ALL
    SELECT c.id, c.name, c.parent_id
    FROM categories c
    JOIN category_tree ct ON c.parent_id = ct.id
)
SELECT
    ct.id,
    ct.name,
    COUNT(a.id) AS article_count
FROM category_tree ct
LEFT JOIN articles a ON a.category_id = ct.id AND a.status = 1 AND a.is_deleted = 0
GROUP BY ct.id, ct.name;

Feed de Seguindo

SQL
-- Consultar artigos mais recentes dos usuários que sigo
SELECT
    a.id,
    a.title,
    a.slug,
    a.summary,
    a.view_count,
    a.like_count,
    a.published_at,
    u.username AS author,
    u.nickname AS author_nickname,
    u.avatar_url AS author_avatar
FROM user_follows f
JOIN articles a ON f.following_id = a.user_id
JOIN users u ON a.user_id = u.id
WHERE f.follower_id = 2
    AND a.status = 1
    AND a.is_deleted = 0
ORDER BY a.published_at DESC
LIMIT 20;

Parte 6: Criação de Views

SQL
-- View de artigos publicados (uso comum, simplifica consultas)
CREATE VIEW v_published_articles AS
SELECT
    a.id,
    a.user_id,
    a.category_id,
    a.title,
    a.slug,
    a.summary,
    a.cover_image_url,
    a.status,
    a.is_pinned,
    a.view_count,
    a.like_count,
    a.comment_count,
    a.published_at,
    a.created_at,
    u.username AS author_username,
    u.nickname AS author_nickname,
    u.avatar_url AS author_avatar,
    c.name AS category_name,
    c.slug AS category_slug
FROM articles a
JOIN users u ON a.user_id = u.id
LEFT JOIN categories c ON a.category_id = c.id
WHERE a.status = 1 AND a.is_deleted = 0;

-- Consulta usando a view
SELECT * FROM v_published_articles ORDER BY published_at DESC LIMIT 20;
SELECT * FROM v_published_articles WHERE category_id = 1 ORDER BY view_count DESC;

-- View de resumo de tags dos artigos
CREATE VIEW v_article_tags AS
SELECT
    at2.article_id,
    GROUP_CONCAT(t.name ORDER BY t.name SEPARATOR ', ') AS tag_names,
    GROUP_CONCAT(t.slug ORDER BY t.name SEPARATOR ', ') AS tag_slugs,
    COUNT(t.id) AS tag_count
FROM article_tag at2
JOIN tags t ON at2.tag_id = t.id
GROUP BY at2.article_id;

-- Consulta de artigos com tags
SELECT
    vpa.*,
    vat.tag_names,
    vat.tag_count
FROM v_published_articles vpa
LEFT JOIN v_article_tags vat ON vpa.id = vat.article_id
ORDER BY vpa.published_at DESC
LIMIT 20;

Parte 7: Otimização de Índices

SQL
-- 1. Otimização de consulta de lista de artigos (índice de cobertura)
-- Para WHERE status=1 AND is_deleted=0 ORDER BY published_at DESC
CREATE INDEX idx_articles_list ON articles(status, is_deleted, published_at DESC, id, title, slug, summary);

-- 2. Otimização de ranking de artigos populares
CREATE INDEX idx_articles_hot ON articles(status, is_deleted, published_at, view_count, like_count, comment_count);

-- 3. Otimização de filtro por categoria
CREATE INDEX idx_articles_category ON articles(category_id, status, is_deleted, published_at DESC);

-- 4. Otimização de lista de artigos do usuário
CREATE INDEX idx_articles_user ON articles(user_id, status, is_deleted, published_at DESC);

-- 5. Otimização de lista de comentários
CREATE INDEX idx_comments_article ON comments(article_id, status, is_deleted, parent_id, created_at);

-- 6. Otimização de relacionamento de seguidores
CREATE INDEX idx_follows_following ON user_follows(following_id, follower_id);

-- 7. Ver uso de índices
SELECT
    TABLE_NAME,
    INDEX_NAME,
    COLUMN_NAME,
    CARDINALITY,
    SEQ_IN_INDEX
FROM INFORMATION_SCHEMA.STATISTICS
WHERE TABLE_SCHEMA = 'blog_system'
ORDER BY TABLE_NAME, INDEX_NAME, SEQ_IN_INDEX;

-- 8. Analisar consultas lentas
EXPLAIN SELECT * FROM v_published_articles WHERE category_id = 1 ORDER BY view_count DESC LIMIT 20;

❓ Perguntas Frequentes

P: Como garantir a consistência dos dados para campos desnormalizados (como article_count)? R: Atualizar sincronamente os campos desnormalizados durante operações de criação/exclusão. Você pode usar triggers para manutenção automática, ou atualizar explicitamente no código da camada de aplicação. Periodicamente executar scripts de verificação de consistência para corrigir discrepâncias.

P: Como escolher entre exclusão lógica e exclusão física? R: Usar exclusão lógica para dados principais do negócio (usuários, artigos, comentários) para fácil recuperação e auditoria. Usar exclusão física para logs e dados temporários para economizar espaço de armazenamento.

P: O conteúdo dos artigos deve ser armazenado no banco de dados ou no sistema de arquivos? R: Texto curto (<64KB) em MySQL TEXT/LONGTEXT é suficiente. Para conteúdo muito longo ou quando otimização de pesquisa de texto completo é necessária, considere armazenar em Elasticsearch ou armazenamento de objetos, com o banco de dados armazenando apenas resumos e índices.

P: Como lidar com atualizações de contagem de visualizações de artigos sob alta concorrência? R: Não faça UPDATE a cada visualização. Use um contador Redis, sincronize periodicamente em lote com o banco de dados, ou use uma fila de mensagens para processamento assíncrono.

📖 Resumo

Esta lição aplicou abrangentemente todo o conhecimento de SQL:

📝 Exercícios

  1. Adicione uma funcionalidade de "favoritos" ao sistema de blog: os usuários podem favoritar artigos. Projete a tabela de favoritos e escreva uma consulta SQL para "minha lista de favoritos."
  2. Adicione uma funcionalidade de "histórico de navegação de artigos": registre artigos que um usuário visualizou recentemente. Escreva uma consulta SQL para "10 artigos visualizados mais recentemente."
  3. Implemente pesquisa de texto completo: use MATCH AGAINST ou LIKE para pesquisar títulos e conteúdo de artigos, e compare as diferenças de performance entre as duas abordagens.
  4. Escreva um script de verificação de consistência de dados para o sistema de blog: verifique se campos desnormalizados como article_count e comment_count correspondem aos dados reais.

Parabéns por completar todas as lições do tutorial de SQL! Continue praticando e melhorando!

Web-Tutorial.com

Equipe Técnica Web-Tutorial

Uma plataforma de tutoriais mantida por diversos desenvolvedores. Cada tutorial é escrito e revisado por profissionais da área correspondente. Trabalhamos para manter nosso conteúdo preciso e confiável — se encontrar algum problema, avise-nos.

100%