个人主页:Guiat
归属专栏:Oracle
正文
DDL(Data Definition Language)是Oracle数据库的"建筑师",负责设计和构建数据库的结构。如果说数据库是一座大厦,那DDL就是绘制蓝图、搭建框架的工具。它不仅能创建表、索引、视图等对象,还能修改和删除它们,就像装修房子一样灵活!
DDL就像是数据库的"工程师",专门负责数据库结构的设计和管理。它定义了数据如何存储、如何组织,以及各种数据库对象之间的关系。在Oracle这个数据王国里,DDL就是制定规则和建造基础设施的核心工具。
Oracle DDL的功能体系就像一个完整的建筑工具箱:
创建表就像设计房间的格局,需要考虑每个字段的类型、大小和约束:
-- 基础表创建
CREATE TABLE employees (
employee_id NUMBER(6) PRIMARY KEY,
first_name VARCHAR2(20) NOT NULL,
last_name VARCHAR2(25) NOT NULL,
email VARCHAR2(25) UNIQUE,
phone_number VARCHAR2(20),
hire_date DATE DEFAULT SYSDATE,
job_id VARCHAR2(10) NOT NULL,
salary NUMBER(8,2) CHECK (salary > 0),
commission_pct NUMBER(2,2),
manager_id NUMBER(6),
department_id NUMBER(4)
);
-- 带详细约束的表创建
CREATE TABLE products (
product_id NUMBER(10) GENERATED ALWAYS AS IDENTITY,
product_name VARCHAR2(100) NOT NULL,
description CLOB,
category_id NUMBER(6) NOT NULL,
supplier_id NUMBER(6),
unit_price NUMBER(10,2) DEFAULT 0 CHECK (unit_price >= 0),
units_in_stock NUMBER(5) DEFAULT 0 CHECK (units_in_stock >= 0),
reorder_level NUMBER(5) DEFAULT 0,
discontinued CHAR(1) DEFAULT 'N' CHECK (discontinued IN ('Y', 'N')),
created_date TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
modified_date TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT pk_products PRIMARY KEY (product_id),
CONSTRAINT fk_products_category
FOREIGN KEY (category_id) REFERENCES categories(category_id),
CONSTRAINT fk_products_supplier
FOREIGN KEY (supplier_id) REFERENCES suppliers(supplier_id)
);
-- 分区表创建
CREATE TABLE sales_data (
sale_id NUMBER(12),
sale_date DATE NOT NULL,
customer_id NUMBER(8) NOT NULL,
product_id NUMBER(10) NOT NULL,
quantity NUMBER(6) NOT NULL,
unit_price NUMBER(10,2) NOT NULL,
total_amount NUMBER(12,2) NOT NULL,
sales_rep_id NUMBER(6),
region_id NUMBER(4)
)
PARTITION BY RANGE (sale_date) (
PARTITION sales_2023_q1 VALUES LESS THAN (DATE '2023-04-01'),
PARTITION sales_2023_q2 VALUES LESS THAN (DATE '2023-07-01'),
PARTITION sales_2023_q3 VALUES LESS THAN (DATE '2023-10-01'),
PARTITION sales_2023_q4 VALUES LESS THAN (DATE '2024-01-01'),
PARTITION sales_future VALUES LESS THAN (MAXVALUE)
);
-- 临时表创建
CREATE GLOBAL TEMPORARY TABLE temp_calculations (
session_id NUMBER,
calculation_id NUMBER,
input_value NUMBER,
result_value NUMBER,
created_time TIMESTAMP
) ON COMMIT DELETE ROWS;
-- 外部表创建
CREATE TABLE external_sales_data (
sale_date DATE,
customer_name VARCHAR2(100),
product_name VARCHAR2(100),
quantity NUMBER,
amount NUMBER(10,2)
)
ORGANIZATION EXTERNAL (
TYPE ORACLE_LOADER
DEFAULT DIRECTORY data_dir
ACCESS PARAMETERS (
RECORDS DELIMITED BY NEWLINE
FIELDS TERMINATED BY ','
OPTIONALLY ENCLOSED BY '"'
)
LOCATION ('sales_data.csv')
);
修改表就像装修房子,可以加房间、改格局、换装饰:
-- 添加新列
ALTER TABLE employees ADD (
middle_name VARCHAR2(20),
birth_date DATE,
emergency_contact VARCHAR2(100)
);
-- 修改列定义
ALTER TABLE employees MODIFY (
phone_number VARCHAR2(30),
salary NUMBER(10,2)
);
-- 重命名列
ALTER TABLE employees RENAME COLUMN phone_number TO contact_phone;
-- 删除列
ALTER TABLE employees DROP COLUMN middle_name;
-- 添加约束
ALTER TABLE employees ADD CONSTRAINT chk_hire_date
CHECK (hire_date >= DATE '1980-01-01');
-- 删除约束
ALTER TABLE employees DROP CONSTRAINT chk_hire_date;
-- 启用/禁用约束
ALTER TABLE employees DISABLE CONSTRAINT fk_emp_dept;
ALTER TABLE employees ENABLE CONSTRAINT fk_emp_dept;
-- 重命名表
ALTER TABLE employees RENAME TO staff_members;
-- 移动表到不同表空间
ALTER TABLE employees MOVE TABLESPACE new_tablespace;
-- 添加分区
ALTER TABLE sales_data ADD PARTITION sales_2024_q1
VALUES LESS THAN (DATE '2024-04-01');
-- 分割分区
ALTER TABLE sales_data SPLIT PARTITION sales_future
AT (DATE '2024-07-01')
INTO (PARTITION sales_2024_q2, PARTITION sales_future);
-- 客户表
CREATE TABLE customers (
customer_id NUMBER(10) GENERATED ALWAYS AS IDENTITY,
customer_code VARCHAR2(20) NOT NULL UNIQUE,
company_name VARCHAR2(100),
contact_name VARCHAR2(50) NOT NULL,
contact_title VARCHAR2(30),
address VARCHAR2(200),
city VARCHAR2(50),
region VARCHAR2(50),
postal_code VARCHAR2(20),
country VARCHAR2(50) DEFAULT 'China',
phone VARCHAR2(30),
email VARCHAR2(100),
credit_limit NUMBER(12,2) DEFAULT 0,
account_status VARCHAR2(20) DEFAULT 'Active'
CHECK (account_status IN ('Active', 'Inactive', 'Suspended')),
created_date TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
modified_date TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT pk_customers PRIMARY KEY (customer_id),
CONSTRAINT uk_customers_code UNIQUE (customer_code),
CONSTRAINT uk_customers_email UNIQUE (email)
);
-- 订单表(按年分区)
CREATE TABLE orders (
order_id NUMBER(12) GENERATED ALWAYS AS IDENTITY,
order_number VARCHAR2(20) NOT NULL,
customer_id NUMBER(10) NOT NULL,
employee_id NUMBER(6),
order_date DATE DEFAULT SYSDATE,
required_date DATE,
shipped_date DATE,
ship_via NUMBER(4),
freight NUMBER(10,2) DEFAULT 0,
ship_name VARCHAR2(100),
ship_address VARCHAR2(200),
ship_city VARCHAR2(50),
ship_region VARCHAR2(50),
ship_postal_code VARCHAR2(20),
ship_country VARCHAR2(50),
order_status VARCHAR2(20) DEFAULT 'Pending'
CHECK (order_status IN ('Pending', 'Processing', 'Shipped', 'Delivered', 'Cancelled')),
total_amount NUMBER(12,2) DEFAULT 0,
CONSTRAINT pk_orders PRIMARY KEY (order_id, order_date),
CONSTRAINT fk_orders_customer
FOREIGN KEY (customer_id) REFERENCES customers(customer_id),
CONSTRAINT uk_orders_number UNIQUE (order_number),
CONSTRAINT chk_orders_dates CHECK (required_date >= order_date)
)
PARTITION BY RANGE (order_date) (
PARTITION orders_2022 VALUES LESS THAN (DATE '2023-01-01'),
PARTITION orders_2023 VALUES LESS THAN (DATE '2024-01-01'),
PARTITION orders_2024 VALUES LESS THAN (DATE '2025-01-01'),
PARTITION orders_future VALUES LESS THAN (MAXVALUE)
);
-- 订单明细表
CREATE TABLE order_details (
order_id NUMBER(12),
order_date DATE,
product_id NUMBER(10),
unit_price NUMBER(10,2) NOT NULL,
quantity NUMBER(6) NOT NULL CHECK (quantity > 0),
discount NUMBER(4,3) DEFAULT 0 CHECK (discount BETWEEN 0 AND 1),
line_total NUMBER(12,2) GENERATED ALWAYS AS (
ROUND(unit_price * quantity * (1 - discount), 2)
),
CONSTRAINT pk_order_details PRIMARY KEY (order_id, order_date, product_id),
CONSTRAINT fk_order_details_order
FOREIGN KEY (order_id, order_date) REFERENCES orders(order_id, order_date),
CONSTRAINT fk_order_details_product
FOREIGN KEY (product_id) REFERENCES products(product_id)
)
PARTITION BY RANGE (order_date) (
PARTITION order_details_2022 VALUES LESS THAN (DATE '2023-01-01'),
PARTITION order_details_2023 VALUES LESS THAN (DATE '2024-01-01'),
PARTITION order_details_2024 VALUES LESS THAN (DATE '2025-01-01'),
PARTITION order_details_future VALUES LESS THAN (MAXVALUE)
);
-- 账户表
CREATE TABLE accounts (
account_id NUMBER(12) GENERATED ALWAYS AS IDENTITY,
account_number VARCHAR2(20) NOT NULL UNIQUE,
customer_id NUMBER(10) NOT NULL,
account_type VARCHAR2(20) NOT NULL
CHECK (account_type IN ('Savings', 'Checking', 'Credit', 'Investment')),
currency_code CHAR(3) DEFAULT 'CNY',
balance NUMBER(15,2) DEFAULT 0,
available_balance NUMBER(15,2) DEFAULT 0,
credit_limit NUMBER(15,2) DEFAULT 0,
interest_rate NUMBER(5,4) DEFAULT 0,
account_status VARCHAR2(20) DEFAULT 'Active'
CHECK (account_status IN ('Active', 'Inactive', 'Frozen', 'Closed')),
opened_date DATE DEFAULT SYSDATE,
closed_date DATE,
last_transaction_date DATE,
CONSTRAINT pk_accounts PRIMARY KEY (account_id),
CONSTRAINT fk_accounts_customer
FOREIGN KEY (customer_id) REFERENCES customers(customer_id),
CONSTRAINT chk_accounts_balance CHECK (
CASE account_type
WHEN 'Credit' THEN balance >= -credit_limit
ELSE balance >= 0
END = 1
),
CONSTRAINT chk_accounts_dates CHECK (closed_date >= opened_date)
);
-- 交易表(按月分区,包含子分区)
CREATE TABLE transactions (
transaction_id NUMBER(15) GENERATED ALWAYS AS IDENTITY,
account_id NUMBER(12) NOT NULL,
transaction_date TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
transaction_type VARCHAR2(20) NOT NULL
CHECK (transaction_type IN ('Deposit', 'Withdrawal', 'Transfer', 'Payment', 'Fee')),
amount NUMBER(15,2) NOT NULL CHECK (amount != 0),
balance_after NUMBER(15,2) NOT NULL,
description VARCHAR2(200),
reference_number VARCHAR2(50),
related_account_id NUMBER(12),
channel VARCHAR2(20) DEFAULT 'Branch'
CHECK (channel IN ('Branch', 'ATM', 'Online', 'Mobile', 'Phone')),
status VARCHAR2(20) DEFAULT 'Completed'
CHECK (status IN ('Pending', 'Completed', 'Failed', 'Reversed')),
created_by VARCHAR2(30) DEFAULT USER,
CONSTRAINT pk_transactions PRIMARY KEY (transaction_id, transaction_date),
CONSTRAINT fk_transactions_account
FOREIGN KEY (account_id) REFERENCES accounts(account_id),
CONSTRAINT fk_transactions_related
FOREIGN KEY (related_account_id) REFERENCES accounts(account_id)
)
PARTITION BY RANGE (transaction_date)
SUBPARTITION BY HASH (account_id) SUBPARTITIONS 4 (
PARTITION trans_2024_01 VALUES LESS THAN (TIMESTAMP '2024-02-01 00:00:00'),
PARTITION trans_2024_02 VALUES LESS THAN (TIMESTAMP '2024-03-01 00:00:00'),
PARTITION trans_2024_03 VALUES LESS THAN (TIMESTAMP '2024-04-01 00:00:00'),
PARTITION trans_future VALUES LESS THAN (MAXVALUE)
);
索引就像书的目录,帮助快速找到需要的信息:
-- 基本B-Tree索引
CREATE INDEX idx_employees_last_name ON employees(last_name);
-- 复合索引
CREATE INDEX idx_employees_dept_job ON employees(department_id, job_id);
-- 唯一索引
CREATE UNIQUE INDEX idx_employees_email ON employees(email);
-- 函数索引
CREATE INDEX idx_employees_upper_name ON employees(UPPER(last_name));
-- 部分索引(带WHERE条件)
CREATE INDEX idx_active_employees ON employees(employee_id)
WHERE status = 'ACTIVE';
-- 降序索引
CREATE INDEX idx_employees_salary_desc ON employees(salary DESC);
-- 位图索引(适合数据仓库)
CREATE BITMAP INDEX idx_products_category ON products(category_id);
-- 分区索引
CREATE INDEX idx_sales_date ON sales_data(sale_date) LOCAL;
-- 全局分区索引
CREATE INDEX idx_sales_customer ON sales_data(customer_id)
GLOBAL PARTITION BY RANGE (customer_id) (
PARTITION idx_cust_1_1000 VALUES LESS THAN (1001),
PARTITION idx_cust_1001_2000 VALUES LESS THAN (2001),
PARTITION idx_cust_others VALUES LESS THAN (MAXVALUE)
);
-- 反向键索引(避免热块)
CREATE INDEX idx_orders_id_reverse ON orders(order_id) REVERSE;
-- 压缩索引
CREATE INDEX idx_employees_dept_job_comp ON employees(department_id, job_id)
COMPRESS 1;
-- 重建索引
ALTER INDEX idx_employees_last_name REBUILD;
-- 重建索引到新表空间
ALTER INDEX idx_employees_last_name REBUILD TABLESPACE new_index_ts;
-- 在线重建索引
ALTER INDEX idx_employees_last_name REBUILD ONLINE;
-- 收集索引统计信息
ANALYZE INDEX idx_employees_last_name COMPUTE STATISTICS;
-- 或使用DBMS_STATS
EXEC DBMS_STATS.GATHER_INDEX_STATS('HR', 'IDX_EMPLOYEES_LAST_NAME');
-- 监控索引使用情况
ALTER INDEX idx_employees_last_name MONITORING USAGE;
-- 查看索引使用情况
SELECT * FROM v$object_usage WHERE index_name = 'IDX_EMPLOYEES_LAST_NAME';
-- 检查索引碎片
SELECT index_name, blevel, leaf_blocks, distinct_keys, clustering_factor
FROM user_indexes
WHERE table_name = 'EMPLOYEES';
-- 删除未使用的索引
DROP INDEX idx_unused_index;
-- 使索引不可见(测试性能影响)
ALTER INDEX idx_employees_last_name INVISIBLE;
ALTER INDEX idx_employees_last_name VISIBLE;
-- 虚拟列索引
ALTER TABLE products ADD (
price_category VARCHAR2(10) GENERATED ALWAYS AS (
CASE
WHEN unit_price < 10 THEN 'Low'
WHEN unit_price < 100 THEN 'Medium'
ELSE 'High'
END
)
);
CREATE INDEX idx_products_price_cat ON products(price_category);
-- JSON数据索引
CREATE TABLE json_documents (
id NUMBER PRIMARY KEY,
doc_data CLOB CHECK (doc_data IS JSON)
);
CREATE INDEX idx_json_name ON json_documents(
JSON_VALUE(doc_data, '$.name' RETURNING VARCHAR2(100))
);
-- 全文索引
CREATE INDEX idx_products_text ON products(description)
INDEXTYPE IS CTXSYS.CONTEXT;
-- 空间索引
CREATE INDEX idx_locations_spatial ON locations(geometry)
INDEXTYPE IS MDSYS.SPATIAL_INDEX;
视图就像是数据的"窗口",提供不同的观察角度:
-- 简单视图
CREATE VIEW employee_summary AS
SELECT employee_id, first_name, last_name, department_id, salary
FROM employees
WHERE status = 'ACTIVE';
-- 复杂视图(多表连接)
CREATE VIEW employee_details AS
SELECT
e.employee_id,
e.first_name || ' ' || e.last_name AS full_name,
e.email,
e.hire_date,
d.department_name,
j.job_title,
e.salary,
m.first_name || ' ' || m.last_name AS manager_name
FROM employees e
LEFT JOIN departments d ON e.department_id = d.department_id
LEFT JOIN jobs j ON e.job_id = j.job_id
LEFT JOIN employees m ON e.manager_id = m.employee_id
WHERE e.status = 'ACTIVE';
-- 聚合视图
CREATE VIEW department_statistics AS
SELECT
d.department_id,
d.department_name,
COUNT(e.employee_id) AS employee_count,
AVG(e.salary) AS avg_salary,
MIN(e.salary) AS min_salary,
MAX(e.salary) AS max_salary,
SUM(e.salary) AS total_salary
FROM departments d
LEFT JOIN employees e ON d.department_id = e.department_id
GROUP BY d.department_id, d.department_name;
-- 可更新视图
CREATE VIEW active_employees AS
SELECT employee_id, first_name, last_name, email, salary, department_id
FROM employees
WHERE status = 'ACTIVE'
WITH CHECK OPTION;
-- 只读视图
CREATE VIEW salary_report AS
SELECT
department_id,
job_id,
COUNT(*) AS emp_count,
AVG(salary) AS avg_salary
FROM employees
GROUP BY department_id, job_id
WITH READ ONLY;
-- 物化视图
CREATE MATERIALIZED VIEW mv_monthly_sales
BUILD IMMEDIATE
REFRESH COMPLETE ON DEMAND
AS
SELECT
EXTRACT(YEAR FROM order_date) AS year,
EXTRACT(MONTH FROM order_date) AS month,
customer_id,
SUM(total_amount) AS monthly_total,
COUNT(*) AS order_count
FROM orders
WHERE order_date >= ADD_MONTHS(SYSDATE, -24)
GROUP BY EXTRACT(YEAR FROM order_date), EXTRACT(MONTH FROM order_date), customer_id;
-- 分层查询视图
CREATE VIEW employee_hierarchy AS
SELECT
employee_id,
first_name || ' ' || last_name AS full_name,
manager_id,
LEVEL AS hierarchy_level,
SYS_CONNECT_BY_PATH(first_name || ' ' || last_name, ' -> ') AS path
FROM employees
START WITH manager_id IS NULL
CONNECT BY PRIOR employee_id = manager_id
ORDER SIBLINGS BY last_name;
-- 分析函数视图
CREATE VIEW employee_rankings AS
SELECT
employee_id,
first_name,
last_name,
department_id,
salary,
RANK() OVER (PARTITION BY department_id ORDER BY salary DESC) AS dept_salary_rank,
DENSE_RANK() OVER (ORDER BY salary DESC) AS overall_salary_rank,
NTILE(4) OVER (ORDER BY salary) AS salary_quartile,
LAG(salary) OVER (PARTITION BY department_id ORDER BY hire_date) AS prev_salary,
salary - LAG(salary) OVER (PARTITION BY department_id ORDER BY hire_date) AS salary_diff
FROM employees;
-- 透视视图
CREATE VIEW sales_pivot AS
SELECT *
FROM (
SELECT
EXTRACT(YEAR FROM order_date) AS year,
EXTRACT(MONTH FROM order_date) AS month,
total_amount
FROM orders
)
PIVOT (
SUM(total_amount)
FOR month IN (1 AS Jan, 2 AS Feb, 3 AS Mar, 4 AS Apr, 5 AS May, 6 AS Jun,
7 AS Jul, 8 AS Aug, 9 AS Sep, 10 AS Oct, 11 AS Nov, 12 AS Dec)
);
-- 递归视图(Oracle 11g R2+)
CREATE VIEW category_tree AS
WITH category_hierarchy (category_id, category_name, parent_id, level_num, path) AS (
-- 根节点
SELECT category_id, category_name, parent_id, 1, category_name
FROM categories
WHERE parent_id IS NULL
UNION ALL
-- 递归部分
SELECT c.category_id, c.category_name, c.parent_id, ch.level_num + 1,
ch.path || ' -> ' || c.category_name
FROM categories c
JOIN category_hierarchy ch ON c.parent_id = ch.category_id
)
SELECT * FROM category_hierarchy;
序列就像是自动编号机,为表提供唯一的数字:
-- 基本序列创建
CREATE SEQUENCE emp_seq
START WITH 1000
INCREMENT BY 1
MAXVALUE 999999
MINVALUE 1
NOCYCLE
CACHE 20;
-- 复杂序列配置
CREATE SEQUENCE order_seq
START WITH 100000
INCREMENT BY 1
MAXVALUE 9999999999
MINVALUE 100000
CYCLE
CACHE 100
ORDER;
-- 使用序列
INSERT INTO employees (employee_id, first_name, last_name, hire_date)
VALUES (emp_seq.NEXTVAL, 'John', 'Doe', SYSDATE);
-- 查看序列当前值
SELECT emp_seq.CURRVAL FROM dual;
-- 修改序列
ALTER SEQUENCE emp_seq
INCREMENT BY 5
MAXVALUE 9999999
CACHE 50;
-- 重置序列(通过修改实现)
ALTER SEQUENCE emp_seq RESTART START WITH 1;
-- 删除序列
DROP SEQUENCE emp_seq;
-- 为不同表创建专用序列
CREATE SEQUENCE customer_id_seq START WITH 10000 INCREMENT BY 1;
CREATE SEQUENCE order_id_seq START WITH 100000 INCREMENT BY 1;
CREATE SEQUENCE product_id_seq START WITH 1000 INCREMENT BY 1;
-- 创建带前缀的序列号生成函数
CREATE OR REPLACE FUNCTION generate_order_number
RETURN VARCHAR2
IS
seq_val NUMBER;
BEGIN
SELECT order_seq.NEXTVAL INTO seq_val FROM dual;
RETURN 'ORD' || TO_CHAR(SYSDATE, 'YYYYMMDD') || LPAD(seq_val, 6, '0');
END;
/
-- 使用函数生成订单号
INSERT INTO orders (order_id, order_number, customer_id, order_date)
VALUES (order_id_seq.NEXTVAL, generate_order_number(), 12345, SYSDATE);
-- 查看序列信息
SELECT sequence_name, min_value, max_value, increment_by,
cycle_flag, cache_size, last_number
FROM user_sequences;
存储过程就像是预先写好的"脚本",可以重复执行复杂的业务逻辑:
-- 简单存储过程
CREATE OR REPLACE PROCEDURE update_employee_salary(
p_employee_id IN NUMBER,
p_new_salary IN NUMBER
)
IS
BEGIN
UPDATE employees
SET salary = p_new_salary,
modified_date = SYSDATE
WHERE employee_id = p_employee_id;
IF SQL%ROWCOUNT = 0 THEN
RAISE_APPLICATION_ERROR(-20001, 'Employee not found');
END IF;
COMMIT;
END;
/
-- 复杂存储过程(带异常处理)
CREATE OR REPLACE PROCEDURE process_monthly_payroll(
p_department_id IN NUMBER DEFAULT NULL,
p_process_date IN DATE DEFAULT SYSDATE
)
IS
v_total_processed NUMBER := 0;
v_total_amount NUMBER := 0;
v_error_count NUMBER := 0;
CURSOR emp_cursor IS
SELECT employee_id, salary, commission_pct
FROM employees
WHERE (p_department_id IS NULL OR department_id = p_department_id)
AND status = 'ACTIVE';
TYPE emp_array IS TABLE OF emp_cursor%ROWTYPE;
emp_list emp_array;
BEGIN
-- 批量获取员工数据
OPEN emp_cursor;
FETCH emp_cursor BULK COLLECT INTO emp_list;
CLOSE emp_cursor;
-- 处理每个员工
FOR i IN 1..emp_list.COUNT LOOP
BEGIN
-- 计算薪资
DECLARE
v_gross_pay NUMBER;
v_net_pay NUMBER;
BEGIN
v_gross_pay := emp_list(i).salary +
NVL(emp_list(i).commission_pct * emp_list(i).salary, 0);
v_net_pay := v_gross_pay * 0.8; -- 简化的税收计算
-- 插入薪资记录
INSERT INTO payroll_history (
employee_id, pay_date, gross_pay, net_pay, created_date
) VALUES (
emp_list(i).employee_id, p_process_date,
v_gross_pay, v_net_pay, SYSDATE
);
v_total_processed := v_total_processed + 1;
v_total_amount := v_total_amount + v_net_pay;
END;
EXCEPTION
WHEN OTHERS THEN
v_error_count := v_error_count + 1;
-- 记录错误日志
INSERT INTO error_log (
error_date, error_message, employee_id
) VALUES (
SYSDATE, SQLERRM, emp_list(i).employee_id
);
END;
END LOOP;
-- 记录处理摘要
INSERT INTO payroll_summary (
process_date, employees_processed, total_amount, error_count
) VALUES (
p_process_date, v_total_processed, v_total_amount, v_error_count
);
COMMIT;
DBMS_OUTPUT.PUT_LINE('Payroll processed: ' || v_total_processed || ' employees');
DBMS_OUTPUT.PUT_LINE('Total amount: ' || v_total_amount);
DBMS_OUTPUT.PUT_LINE('Errors: ' || v_error_count);
EXCEPTION
WHEN OTHERS THEN
ROLLBACK;
RAISE_APPLICATION_ERROR(-20002, 'Payroll processing failed: ' || SQLERRM);
END;
/
-- 简单函数
CREATE OR REPLACE FUNCTION get_employee_age(
p_employee_id IN NUMBER
) RETURN NUMBER
IS
v_birth_date DATE;
v_age NUMBER;
BEGIN
SELECT birth_date INTO v_birth_date
FROM employees
WHERE employee_id = p_employee_id;
v_age := FLOOR(MONTHS_BETWEEN(SYSDATE, v_birth_date) / 12);
RETURN v_age;
EXCEPTION
WHEN NO_DATA_FOUND THEN
RETURN NULL;
END;
/
-- 表函数(返回结果集)
CREATE OR REPLACE FUNCTION get_department_employees(
p_department_id IN NUMBER
) RETURN SYS_REFCURSOR
IS
emp_cursor SYS_REFCURSOR;
BEGIN
OPEN emp_cursor FOR
SELECT employee_id, first_name, last_name, salary
FROM employees
WHERE department_id = p_department_id
ORDER BY last_name;
RETURN emp_cursor;
END;
/
-- 管道表函数
CREATE OR REPLACE TYPE emp_summary_type AS OBJECT (
department_id NUMBER,
department_name VARCHAR2(100),
employee_count NUMBER,
avg_salary NUMBER
);
/
CREATE OR REPLACE TYPE emp_summary_table AS TABLE OF emp_summary_type;
/
CREATE OR REPLACE FUNCTION get_department_summary
RETURN emp_summary_table PIPELINED
IS
v_summary emp_summary_type;
BEGIN
FOR rec IN (
SELECT d.department_id, d.department_name,
COUNT(e.employee_id) as emp_count,
AVG(e.salary) as avg_sal
FROM departments d
LEFT JOIN employees e ON d.department_id = e.department_id
GROUP BY d.department_id, d.department_name
) LOOP
v_summary := emp_summary_type(
rec.department_id, rec.department_name,
rec.emp_count, rec.avg_sal
);
PIPE ROW(v_summary);
END LOOP;
RETURN;
END;
/
-- 使用管道表函数
SELECT * FROM TABLE(get_department_summary());
触发器就像是数据库的"守卫",在特定事件发生时自动执行:
-- 审计触发器
CREATE OR REPLACE TRIGGER trg_employees_audit
AFTER INSERT OR UPDATE OR DELETE ON employees
FOR EACH ROW
DECLARE
v_action VARCHAR2(10);
BEGIN
CASE
WHEN INSERTING THEN v_action := 'INSERT';
WHEN UPDATING THEN v_action := 'UPDATE';
WHEN DELETING THEN v_action := 'DELETE';
END CASE;
INSERT INTO audit_log (
table_name, action_type, record_id,
old_values, new_values, action_date, action_user
) VALUES (
'EMPLOYEES', v_action,
COALESCE(:NEW.employee_id, :OLD.employee_id),
CASE WHEN DELETING THEN
:OLD.first_name || '|' || :OLD.last_name || '|' || :OLD.salary
END,
CASE WHEN NOT DELETING THEN
:NEW.first_name || '|' || :NEW.last_name || '|' || :NEW.salary
END,
SYSDATE, USER
);
END;
/
-- 自动更新时间戳触发器
CREATE OR REPLACE TRIGGER trg_employees_timestamp
BEFORE UPDATE ON employees
FOR EACH ROW
BEGIN
:NEW.modified_date := SYSDATE;
:NEW.modified_by := USER;
END;
/
-- 业务规则触发器
CREATE OR REPLACE TRIGGER trg_salary_validation
BEFORE INSERT OR UPDATE OF salary ON employees
FOR EACH ROW
DECLARE
v_max_salary NUMBER;
v_min_salary NUMBER;
BEGIN
-- 获取职位的薪资范围
SELECT min_salary, max_salary
INTO v_min_salary, v_max_salary
FROM jobs
WHERE job_id = :NEW.job_id;
-- 验证薪资范围
IF :NEW.salary < v_min_salary OR :NEW.salary > v_max_salary THEN
RAISE_APPLICATION_ERROR(-20003,
'Salary must be between ' || v_min_salary || ' and ' || v_max_salary);
END IF;
-- 验证薪资增长不超过20%
IF UPDATING AND :NEW.salary > :OLD.salary * 1.2 THEN
RAISE_APPLICATION_ERROR(-20004,
'Salary increase cannot exceed 20%');
END IF;
END;
/
-- 复合触发器(Oracle 11g+)
CREATE OR REPLACE TRIGGER trg_order_processing
FOR INSERT OR UPDATE OR DELETE ON orders
COMPOUND TRIGGER
-- 声明变量
TYPE order_id_array IS TABLE OF NUMBER;
g_order_ids order_id_array := order_id_array();
-- BEFORE STATEMENT
BEFORE STATEMENT IS
BEGIN
DBMS_OUTPUT.PUT_LINE('Starting order processing...');
END BEFORE STATEMENT;
-- BEFORE EACH ROW
BEFORE EACH ROW IS
BEGIN
IF INSERTING THEN
:NEW.order_number := 'ORD' || TO_CHAR(SYSDATE, 'YYYYMMDD') ||
LPAD(order_seq.NEXTVAL, 6, '0');
:NEW.created_date := SYSDATE;
ELSIF UPDATING THEN
:NEW.modified_date := SYSDATE;
END IF;
END BEFORE EACH ROW;
-- AFTER EACH ROW
AFTER EACH ROW IS
BEGIN
g_order_ids.EXTEND;
g_order_ids(g_order_ids.COUNT) :=
COALESCE(:NEW.order_id, :OLD.order_id);
END AFTER EACH ROW;
-- AFTER STATEMENT
AFTER STATEMENT IS
BEGIN
-- 批量更新相关统计
FOR i IN 1..g_order_ids.COUNT LOOP
update_customer_statistics(g_order_ids(i));
END LOOP;
DBMS_OUTPUT.PUT_LINE('Processed ' || g_order_ids.COUNT || ' orders');
END AFTER STATEMENT;
END trg_order_processing;
/
-- DDL触发器
CREATE OR REPLACE TRIGGER trg_ddl_audit
AFTER CREATE OR ALTER OR DROP ON SCHEMA
BEGIN
INSERT INTO ddl_audit_log (
event_type, object_type, object_name,
sql_text, event_date, username
) VALUES (
SYS.SYSEVENT, SYS.DICTIONARY_OBJ_TYPE, SYS.DICTIONARY_OBJ_NAME,
SYS.SQL_TEXT, SYSDATE, USER
);
END;
/
-- 禁用触发器
ALTER TRIGGER trg_employees_audit DISABLE;
-- 启用触发器
ALTER TRIGGER trg_employees_audit ENABLE;
-- 禁用表上所有触发器
ALTER TABLE employees DISABLE ALL TRIGGERS;
-- 启用表上所有触发器
ALTER TABLE employees ENABLE ALL TRIGGERS;
-- 删除触发器
DROP TRIGGER trg_employees_audit;
-- 查看触发器信息
SELECT trigger_name, trigger_type, triggering_event, status
FROM user_triggers
WHERE table_name = 'EMPLOYEES';
-- 查看触发器源码
SELECT trigger_body
FROM user_triggers
WHERE trigger_name = 'TRG_EMPLOYEES_AUDIT';
表空间就像是数据库的"仓库",存储各种数据库对象:
-- 创建永久表空间
CREATE TABLESPACE sales_data
DATAFILE '/u01/app/oracle/oradata/orcl/sales_data01.dbf' SIZE 100M
AUTOEXTEND ON NEXT 10M MAXSIZE 1G
EXTENT MANAGEMENT LOCAL
SEGMENT SPACE MANAGEMENT AUTO;
-- 创建临时表空间
CREATE TEMPORARY TABLESPACE temp_large
TEMPFILE '/u01/app/oracle/oradata/orcl/temp_large01.dbf' SIZE 500M
AUTOEXTEND ON NEXT 50M MAXSIZE 2G;
-- 创建撤销表空间
CREATE UNDO TABLESPACE undo_large
DATAFILE '/u01/app/oracle/oradata/orcl/undo_large01.dbf' SIZE 200M
AUTOEXTEND ON NEXT 20M MAXSIZE 1G;
-- 添加数据文件
ALTER TABLESPACE sales_data
ADD DATAFILE '/u01/app/oracle/oradata/orcl/sales_data02.dbf' SIZE 100M;
-- 调整数据文件大小
ALTER DATABASE DATAFILE '/u01/app/oracle/oradata/orcl/sales_data01.dbf'
RESIZE 200M;
-- 设置自动扩展
ALTER DATABASE DATAFILE '/u01/app/oracle/oradata/orcl/sales_data01.dbf'
AUTOEXTEND ON NEXT 10M MAXSIZE 500M;
-- 表空间离线/在线
ALTER TABLESPACE sales_data OFFLINE;
ALTER TABLESPACE sales_data ONLINE;
-- 表空间只读/读写
ALTER TABLESPACE sales_data READ ONLY;
ALTER TABLESPACE sales_data READ WRITE;
-- 删除表空间
DROP TABLESPACE old_tablespace INCLUDING CONTENTS AND DATAFILES;
-- 查看表空间使用情况
SELECT
ts.tablespace_name,
ROUND(ts.total_mb, 2) AS total_mb,
ROUND(ts.used_mb, 2) AS used_mb,
ROUND(ts.free_mb, 2) AS free_mb,
ROUND((ts.used_mb / ts.total_mb) * 100, 2) AS used_percent
FROM (
SELECT
tablespace_name,
SUM(bytes) / 1024 / 1024 AS total_mb,
SUM(bytes) / 1024 / 1024 - NVL(free.free_mb, 0) AS used_mb,
NVL(free.free_mb, 0) AS free_mb
FROM dba_data_files df
LEFT JOIN (
SELECT
tablespace_name,
SUM(bytes) / 1024 / 1024 AS free_mb
FROM dba_free_space
GROUP BY tablespace_name
) free ON df.tablespace_name = free.tablespace_name
GROUP BY df.tablespace_name, free.free_mb
) ts
ORDER BY used_percent DESC;
-- 查看数据文件信息
SELECT
file_name,
tablespace_name,
ROUND(bytes / 1024 / 1024, 2) AS size_mb,
ROUND(maxbytes / 1024 / 1024, 2) AS max_size_mb,
autoextensible,
status
FROM dba_data_files
ORDER BY tablespace_name, file_id;
结语
感谢您的阅读!期待您的一键三连!欢迎指正!