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:
- Gerenciamento de Usuários: Registro de membros, login, perfis → tabela de usuários
- Gerenciamento de Artigos: Escrita, publicação, categorização para prateleiras → tabela de artigos + tabela de categorias
- Sistema de Tags: Marcar livros para fácil recuperação → tabela de tags + tabela de junção
- Sistema de Comentários: Discussões e comentários de leitores → tabela de comentários
- Análises: Quais livros são mais populares, quais autores são mais famosos → consultas complexas
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
[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
-- =============================================
-- 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
-- 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
-- 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
-- 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
-- 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
-- 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
-- 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)
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
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)
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)
-- 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
-- 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
-- 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
-- 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
-- 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
-- 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:
- Análise de Requisitos: Esclareceu requisitos funcionais e estimativas de volume de dados
- Design do Banco de Dados: 8 tabelas cobrindo usuários, artigos, categorias, tags, comentários, curtidas e seguimentos
- Restrições e Índices: Chaves primárias, restrições de unicidade, semântica de chaves estrangeiras, otimização de índices compostos
- Operações CRUD: Criação, leitura, atualização e exclusão completas com exclusão lógica e manutenção de campos desnormalizados
- Consultas Complexas: Rankings populares, painel de estatísticas, comentários aninhados, associações de tags, feed de seguindo
- Views: Encapsularam lógica de consulta comum para simplificar SQL da camada de negócio
- Otimização de Índices: Criou índices de cobertura para consultas de alta frequência
📝 Exercícios
- 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."
- 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."
- Implemente pesquisa de texto completo: use
MATCH AGAINSTouLIKEpara pesquisar títulos e conteúdo de artigos, e compare as diferenças de performance entre as duas abordagens. - Escreva um script de verificação de consistência de dados para o sistema de blog: verifique se campos desnormalizados como
article_countecomment_countcorrespondem aos dados reais.
Parabéns por completar todas as lições do tutorial de SQL! Continue praticando e melhorando!



