PostgreSQL 练习题集

基础练习

练习1:数据库和表操作

-- 1. 创建一个名为 company 的数据库
CREATE DATABASE company;

-- 2. 连接到 company 数据库
\c company

-- 3. 创建员工表
CREATE TABLE employees (
    id SERIAL PRIMARY KEY,
    name VARCHAR(100) NOT NULL,
    email VARCHAR(100) UNIQUE,
    department VARCHAR(50),
    salary DECIMAL(10,2),
    hire_date DATE DEFAULT CURRENT_DATE,
    is_active BOOLEAN DEFAULT true
);

-- 4. 创建部门表
CREATE TABLE departments (
    id SERIAL PRIMARY KEY,
    name VARCHAR(50) UNIQUE NOT NULL,
    manager_id INTEGER,
    budget DECIMAL(12,2)
);

-- 5. 添加外键约束
ALTER TABLE employees ADD COLUMN dept_id INTEGER;
ALTER TABLE employees ADD CONSTRAINT fk_employee_dept 
    FOREIGN KEY (dept_id) REFERENCES departments(id);

练习2:数据插入和查询

-- 插入部门数据
INSERT INTO departments (name, budget) VALUES
('技术部', 1000000.00),
('销售部', 800000.00),
('人事部', 500000.00),
('财务部', 600000.00);

-- 插入员工数据
INSERT INTO employees (name, email, department, salary, dept_id) VALUES
('张三', 'zhangsan@company.com', '技术部', 8000.00, 1),
('李四', 'lisi@company.com', '技术部', 9000.00, 1),
('王五', 'wangwu@company.com', '销售部', 7000.00, 2),
('赵六', 'zhaoliu@company.com', '销售部', 7500.00, 2),
('钱七', 'qianqi@company.com', '人事部', 6000.00, 3);

-- 练习查询
-- 1. 查询所有员工信息
SELECT * FROM employees;

-- 2. 查询技术部员工
SELECT * FROM employees WHERE department = '技术部';

-- 3. 查询薪资大于7000的员工
SELECT name, salary FROM employees WHERE salary > 7000;

-- 4. 查询员工总数
SELECT COUNT(*) as total_employees FROM employees;

-- 5. 查询各部门平均薪资
SELECT department, AVG(salary) as avg_salary 
FROM employees 
GROUP BY department;

练习3:连接查询

-- 1. 内连接查询员工和部门信息
SELECT e.name, e.salary, d.name as department_name, d.budget
FROM employees e
INNER JOIN departments d ON e.dept_id = d.id;

-- 2. 左连接查询所有部门及其员工数量
SELECT d.name as department, COUNT(e.id) as employee_count
FROM departments d
LEFT JOIN employees e ON d.id = e.dept_id
GROUP BY d.id, d.name;

-- 3. 查询每个部门的最高薪资员工
SELECT d.name as department, e.name as employee, e.salary
FROM employees e
INNER JOIN departments d ON e.dept_id = d.id
WHERE e.salary = (
    SELECT MAX(salary) 
    FROM employees e2 
    WHERE e2.dept_id = e.dept_id
);

中级练习

练习4:窗口函数

-- 1. 为每个员工添加部门内薪资排名
SELECT 
    name,
    department,
    salary,
    RANK() OVER (PARTITION BY department ORDER BY salary DESC) as dept_rank,
    ROW_NUMBER() OVER (ORDER BY salary DESC) as overall_rank
FROM employees;

-- 2. 计算累计薪资
SELECT 
    name,
    salary,
    SUM(salary) OVER (ORDER BY hire_date) as cumulative_salary
FROM employees
ORDER BY hire_date;

-- 3. 计算移动平均薪资(前3个员工)
SELECT 
    name,
    salary,
    AVG(salary) OVER (
        ORDER BY hire_date 
        ROWS BETWEEN 2 PRECEDING AND CURRENT ROW
    ) as moving_avg
FROM employees
ORDER BY hire_date;

练习5:JSON 数据处理

-- 创建包含 JSON 的表
CREATE TABLE employee_profiles (
    id SERIAL PRIMARY KEY,
    employee_id INTEGER REFERENCES employees(id),
    profile JSONB
);

-- 插入 JSON 数据
INSERT INTO employee_profiles (employee_id, profile) VALUES
(1, '{"skills": ["Java", "Python", "SQL"], "experience": 5, "certifications": ["AWS", "Oracle"]}'),
(2, '{"skills": ["JavaScript", "React", "Node.js"], "experience": 3, "certifications": ["Google Cloud"]}'),
(3, '{"skills": ["Sales", "CRM", "Negotiation"], "experience": 7, "certifications": ["Salesforce"]}');

-- JSON 查询练习
-- 1. 查询具有特定技能的员工
SELECT e.name, ep.profile->'skills' as skills
FROM employees e
JOIN employee_profiles ep ON e.id = ep.employee_id
WHERE ep.profile->'skills' ? 'Python';

-- 2. 查询经验超过4年的员工
SELECT e.name, (ep.profile->>'experience')::int as experience
FROM employees e
JOIN employee_profiles ep ON e.id = ep.employee_id
WHERE (ep.profile->>'experience')::int > 4;

-- 3. 更新 JSON 数据
UPDATE employee_profiles 
SET profile = profile || '{"last_review": "2024-01-15"}'
WHERE employee_id = 1;

练习6:数组操作

-- 创建项目表
CREATE TABLE projects (
    id SERIAL PRIMARY KEY,
    name VARCHAR(100),
    team_members INTEGER[],
    technologies TEXT[],
    start_date DATE,
    end_date DATE
);

-- 插入数组数据
INSERT INTO projects (name, team_members, technologies, start_date) VALUES
('电商平台', ARRAY[1, 2, 3], ARRAY['Java', 'Spring', 'MySQL', 'Redis'], '2024-01-01'),
('移动应用', ARRAY[2, 4], ARRAY['React Native', 'Node.js', 'MongoDB'], '2024-02-01'),
('数据分析', ARRAY[1, 5], ARRAY['Python', 'Pandas', 'PostgreSQL'], '2024-03-01');

-- 数组查询练习
-- 1. 查询使用特定技术的项目
SELECT name, technologies 
FROM projects 
WHERE 'Java' = ANY(technologies);

-- 2. 查询包含特定员工的项目
SELECT name, team_members 
FROM projects 
WHERE 1 = ANY(team_members);

-- 3. 统计每种技术被使用的次数
SELECT 
    unnest(technologies) as technology,
    COUNT(*) as usage_count
FROM projects
GROUP BY unnest(technologies)
ORDER BY usage_count DESC;

高级练习

练习7:存储过程和函数

-- 创建计算奖金的函数
CREATE OR REPLACE FUNCTION calculate_bonus(emp_salary DECIMAL, performance_rating INTEGER)
RETURNS DECIMAL AS $$
BEGIN
    CASE performance_rating
        WHEN 5 THEN RETURN emp_salary * 0.20;  -- 优秀
        WHEN 4 THEN RETURN emp_salary * 0.15;  -- 良好
        WHEN 3 THEN RETURN emp_salary * 0.10;  -- 一般
        WHEN 2 THEN RETURN emp_salary * 0.05;  -- 需改进
        ELSE RETURN 0;                          -- 不合格
    END CASE;
END;
$$ LANGUAGE plpgsql;

-- 使用函数
SELECT 
    name,
    salary,
    calculate_bonus(salary, 4) as bonus
FROM employees;

-- 创建存储过程更新薪资
CREATE OR REPLACE PROCEDURE update_salary(
    emp_id INTEGER,
    increase_percentage DECIMAL
)
LANGUAGE plpgsql AS $$
BEGIN
    UPDATE employees 
    SET salary = salary * (1 + increase_percentage / 100)
    WHERE id = emp_id;
    
    -- 记录日志
    INSERT INTO salary_history (employee_id, old_salary, new_salary, change_date)
    SELECT id, salary / (1 + increase_percentage / 100), salary, NOW()
    FROM employees WHERE id = emp_id;
END;
$$;

练习8:触发器

-- 创建薪资历史表
CREATE TABLE salary_history (
    id SERIAL PRIMARY KEY,
    employee_id INTEGER REFERENCES employees(id),
    old_salary DECIMAL(10,2),
    new_salary DECIMAL(10,2),
    change_date TIMESTAMP DEFAULT NOW()
);

-- 创建触发器函数
CREATE OR REPLACE FUNCTION log_salary_change()
RETURNS TRIGGER AS $$
BEGIN
    IF OLD.salary != NEW.salary THEN
        INSERT INTO salary_history (employee_id, old_salary, new_salary)
        VALUES (NEW.id, OLD.salary, NEW.salary);
    END IF;
    RETURN NEW;
END;
$$ LANGUAGE plpgsql;

-- 创建触发器
CREATE TRIGGER salary_change_trigger
    AFTER UPDATE ON employees
    FOR EACH ROW
    EXECUTE FUNCTION log_salary_change();

-- 测试触发器
UPDATE employees SET salary = 8500 WHERE id = 1;

-- 查看薪资变更历史
SELECT * FROM salary_history;

练习9:性能优化

-- 创建大表进行性能测试
CREATE TABLE large_table (
    id SERIAL PRIMARY KEY,
    user_id INTEGER,
    category VARCHAR(50),
    amount DECIMAL(10,2),
    created_at TIMESTAMP DEFAULT NOW(),
    data TEXT
);

-- 插入大量数据
INSERT INTO large_table (user_id, category, amount, data)
SELECT 
    (random() * 10000)::int,
    (ARRAY['A', 'B', 'C', 'D', 'E'])[ceil(random() * 5)],
    (random() * 1000)::decimal(10,2),
    'Sample data ' || generate_series
FROM generate_series(1, 1000000);

-- 性能测试查询
EXPLAIN ANALYZE
SELECT category, COUNT(*), AVG(amount)
FROM large_table
WHERE created_at >= NOW() - INTERVAL '7 days'
GROUP BY category;

-- 创建索引优化
CREATE INDEX idx_large_table_created_at ON large_table(created_at);
CREATE INDEX idx_large_table_category ON large_table(category);

-- 再次测试性能
EXPLAIN ANALYZE
SELECT category, COUNT(*), AVG(amount)
FROM large_table
WHERE created_at >= NOW() - INTERVAL '7 days'
GROUP BY category;

答案和解析

基础练习答案要点

  1. 表设计: 注意主键、外键、约束的使用
  2. 数据类型: 选择合适的数据类型,如 SERIAL、DECIMAL、BOOLEAN
  3. 查询优化: 使用适当的 WHERE 条件和索引

中级练习答案要点

  1. 窗口函数: 理解 PARTITION BY 和 ORDER BY 的作用
  2. JSON 操作: 掌握 -> 和 ->> 操作符的区别
  3. 数组处理: 熟练使用 ANY、unnest 等数组函数

高级练习答案要点

  1. 函数编写: 注意 PL/pgSQL 语法和返回类型
  2. 触发器: 理解 BEFORE/AFTER 和 FOR EACH ROW 的区别
  3. 性能优化: 学会使用 EXPLAIN ANALYZE 分析查询计划

建议按顺序完成练习,每个练习都要实际动手操作,遇到问题及时查阅文档。