Oracle 数据库对象导出脚本-含创建语句

object_to_html.py

python 复制代码
import cx_Oracle
import jinja2
import os
import datetime
from datetime import datetime
cx_Oracle.init_oracle_client(lib_dir=r'D:\oracleclient\instantclient_23_8')

def get_oracle_connection(config):
    """建立Oracle数据库连接"""
    try:
        dsn = cx_Oracle.makedsn(
            config['host'], 
            config['port'], 
            service_name=config['service_name']
        )
        connection = cx_Oracle.connect(
            user=config['username'],
            password=config['password'],
            dsn=dsn,
            encoding="UTF-8",
            nencoding="UTF-8"
        )
        return connection
    except cx_Oracle.Error as error:
        print(f"连接数据库时出错: {error}")
        return None

def fetch_user_details(connection):
    """获取所有用户及其默认表空间信息"""
    users = []
    try:
        cursor = connection.cursor()
        cursor.execute("""
            SELECT username, default_tablespace
            FROM dba_users
            WHERE username IN  ('EDA_PROD','PMS_CORE','PMS_PORTAL','PMS_SEATA','PMS_SYSTEM','PMS_USER','RPT_DW','RPT_DW_READ','RPT_ODS','SPC_PROD') 
            ORDER BY username
        """)
        for row in cursor:
            users.append({
                'username': row[0],
                'tablespace_name': row[1],
                'tables': [],
                'views': [],
                'indexes': [],
                'procedures': [],
                'triggers': [],
                'synonyms': [],
                'sequences': [],
                'functions': [],
                'packages': []
            })
        cursor.close()
    except cx_Oracle.Error as error:
        print(f"获取用户信息时出错: {error}")
    return users

def fetch_object_details(connection, users):
    """获取每个用户的对象详细信息及创建语句(从all_objects获取创建时间)"""
    try:
        cursor = connection.cursor()
        
        # 定义对象类型及其查询配置(从all_objects获取创建时间)
        object_types = {
            'tables': {
                'base_query': """
                    SELECT t.table_name, o.created, t.comments,o.owner as zso
                    FROM all_tab_comments t
                    JOIN all_objects o ON t.owner = o.owner AND t.table_name = o.object_name AND o.object_type = 'TABLE'
                    WHERE t.owner = :owner
                """,
                'object_type': 'TABLE',
                'name_field': 'table_name',
                'comment_field': 'comments',
                'date_field': 'created'
            },
            'views': {
                'base_query': """
                    SELECT v.table_name as view_name, o.created, v.comments,o.owner as zso
                    FROM all_tab_comments v
                    JOIN all_objects o ON v.owner = o.owner AND v.table_name = o.object_name AND o.object_type = 'VIEW'
                    WHERE v.owner = :owner
                """,
                'object_type': 'VIEW',
                'name_field': 'view_name',
                'comment_field': 'comments',
                'date_field': 'created'
            },
            'indexes': {
                'base_query': """
                    SELECT i.index_name, o.created, i.uniqueness,o.owner as zso
                    FROM dba_indexes i
                    JOIN all_objects o ON i.owner = o.owner AND i.index_name = o.object_name AND o.object_type = 'INDEX'
                    WHERE i.owner = :owner
                """,
                'object_type': 'INDEX',
                'name_field': 'index_name',
                'comment_field': 'uniqueness',
                'date_field': 'created'
            },
            'procedures': {
                'base_query': """
                    SELECT o.object_name, o.created, p.authid as comments,o.owner as zso
                    FROM all_objects o
                    JOIN dba_procedures p ON o.owner = p.owner AND o.object_name = p.object_name  and o.object_type = 'PROCEDURE'
                    WHERE o.owner = :owner  
                """,
                'object_type': 'PROCEDURE',
                'name_field': 'object_name',
                'comment_field': 'comments',
                'date_field': 'created'
            },
            'triggers': {
                'base_query': """
                    SELECT t.trigger_name, o.created, t.trigger_type,o.owner as zso
                    FROM dba_triggers t
                    JOIN all_objects o ON t.owner = o.owner AND t.trigger_name = o.object_name AND o.object_type = 'TRIGGER'
                    WHERE t.owner = :owner
                """,
                'object_type': 'TRIGGER',
                'name_field': 'trigger_name',
                'comment_field': 'trigger_type',
                'date_field': 'created'
            },
            'synonyms': {
                'base_query': """
                    SELECT s.synonym_name, o.created, s.table_owner || '.' || s.table_name||'-'||s.owner  as comments,o.owner as zso
                    FROM dba_synonyms s
                    JOIN all_objects o ON  s.synonym_name = o.object_name AND o.object_type = 'SYNONYM'
                    WHERE s.table_owner = :owner 
                """,
                'object_type': 'SYNONYM',
                'name_field': 'synonym_name',
                'comment_field': 'comments',
                'date_field': 'created'
            },
            'sequences': {
                'base_query': """
                    SELECT s.sequence_name, o.created, s.increment_by,o.owner as zso
                    FROM dba_sequences s
                    JOIN all_objects o ON s.sequence_owner = o.owner AND s.sequence_name = o.object_name AND o.object_type = 'SEQUENCE'
                    WHERE o.owner = :owner
                """,
                'object_type': 'SEQUENCE',
                'name_field': 'sequence_name',
                'comment_field': 'increment_by',
                'date_field': 'created'
            },
            'functions': {
                'base_query': """
                    SELECT o.object_name, o.created, f.authid as comments ,o.owner as zso
                    FROM all_objects o
                    JOIN dba_procedures f ON o.owner = f.owner AND o.object_name = f.object_name  and o.object_type = 'FUNCTION'
                    WHERE o.owner = :owner  
                """,
                'object_type': 'FUNCTION',
                'name_field': 'object_name',
                'comment_field': 'comments',
                'date_field': 'created'
            },
            'packages': {
                'base_query': """
                    SELECT o.object_name, o.created, p.authid as comments ,o.owner as zso
                    FROM all_objects o
                    JOIN dba_procedures p ON o.owner = p.owner AND o.object_name = p.object_name  and o.object_type = 'PACKAGE'
                    WHERE o.owner = :owner  
                """,
                'object_type': 'PACKAGE',
                'name_field': 'object_name',
                'comment_field': 'comments',
                'date_field': 'created'
            }
        }
        
        # 为每个用户获取每种对象的详细信息及DDL
        for user in users:
            owner = user['username']
            for obj_type, obj_config in object_types.items():
                try:
                    # 先获取对象基本信息(从all_objects关联获取创建时间)
                    cursor.execute(obj_config['base_query'], {'owner': owner})
                    obj_list = []
                    for row in cursor:
                        obj = {
                            'name': row[0],
                            'created_at': row[1].strftime('%Y-%m-%d %H:%M:%S') if row[1] else '未知',
                            'comment': row[2] if row[2] else '无' ,
                            'zso': row[3] 
                        }
                        obj_list.append(obj)
                    # 批量获取DDL以提高性能
                    if obj_list:
                        ddl_cursor = connection.cursor()
                        for obj in obj_list:
                            try:
                                # 使用DBMS_METADATA获取DDL
                                ddl_cursor.execute(
                                    f"SELECT DBMS_METADATA.GET_DDL(:obj_type, :obj_name, :owner) FROM dual",
                                    {
                                        'obj_type': obj_config['object_type'],
                                        'obj_name': obj['name'],
                                        'owner': obj['zso']
                                    }
                                )
                                ddl_row = ddl_cursor.fetchone()
                                obj['create_statement'] = ddl_row[0] if ddl_row and ddl_row[0] else '无法获取DDL'
                            except cx_Oracle.Error as ddl_error:
                                obj['create_statement'] = f"获取DDL失败: {str(ddl_error)}"
                        ddl_cursor.close()
                    
                    user[obj_type] = obj_list
                    
                except cx_Oracle.Error as error:
                    print(f"获取{obj_type}信息时出错 (用户: {owner}): {error}")
        
        cursor.close()
    except cx_Oracle.Error as error:
        print(f"获取对象详细信息时出错: {error}")
    return users

def fetch_object_counts(users):
    """根据对象详细信息计算数量"""
    for user in users:
        user['table_count'] = len(user['tables'])
        user['view_count'] = len(user['views'])
        user['index_count'] = len(user['indexes'])
        user['procedure_count'] = len(user['procedures'])
        user['trigger_count'] = len(user['triggers'])
        user['synonym_count'] = len(user['synonyms'])
        user['sequence_count'] = len(user['sequences'])
        user['function_count'] = len(user['functions'])
        user['package_count'] = len(user['packages'])
    return users

def generate_report(users, template_path, output_path,dbname):
    """使用Jinja2模板生成HTML报告(包含DDL转义处理)"""
    try:
        # 准备模板渲染数据
        report_data = {
            'dbname':dbname,
            'users': users,
            'generate_time': datetime.now().strftime('%Y-%m-%d %H:%M:%S'),
            # 设置has_*标志,控制列显示
            'has_tables': any(len(user['tables']) > 0 for user in users),
            'has_views': any(len(user['views']) > 0 for user in users),
            'has_indexes': any(len(user['indexes']) > 0 for user in users),
            'has_procedures': any(len(user['procedures']) > 0 for user in users),
            'has_triggers': any(len(user['triggers']) > 0 for user in users),
            'has_synonyms': any(len(user['synonyms']) > 0 for user in users),
            'has_sequences': any(len(user['sequences']) > 0 for user in users),
            'has_functions': any(len(user['functions']) > 0 for user in users),
            'has_packages': any(len(user['packages']) > 0 for user in users)
        }
        

        # 加载模板
        env = jinja2.Environment(
            loader=jinja2.FileSystemLoader(os.path.dirname(template_path)),
            # autoescape=True  # 启用自动转义,防止HTML注入
        )
        # 自定义过滤器:转义DDL中的HTML特殊字符
        env.filters['escape_ddl'] = lambda ddl: ddl.replace('<', '&lt;').replace('>', '&gt;').replace('&', '&amp;')
        template = env.get_template(os.path.basename(template_path))
        
        # 渲染模板并保存
        with open(output_path, 'w', encoding='utf-8') as f:
            f.write(template.render(report_data))
        
        print(f"报告已生成: {output_path}")
    except Exception as e:
        print(f"生成报告时出错: {e}")

def main():
    """主函数"""
    # 数据库连接配置(请修改为实际配置)
    db_config = {
        'host': '10.12.8.61',
        'port': 1521,
        'service_name': 'edadb',
        'username': 'system',
        'password': 'Zeta@xxxx#$'
    }
    
    # 模板和输出文件路径
    template_path = 'object_template.html'
    output_path = 'edadb_object.html'
    
    # 连接数据库
    connection = get_oracle_connection(db_config)
    if not connection:
        return
    
    try:
        # 获取用户信息
        users = fetch_user_details(connection)
        if not users:
            print("未找到用户信息")
            return
        
        # 获取对象详细信息及DDL
        users = fetch_object_details(connection, users)
        
        # 计算对象数量
        users = fetch_object_counts(users)
        
        # 生成报告
        generate_report(users, template_path, output_path,dbname='EDADB')
        
    finally:
        # 关闭数据库连接
        if connection:
            connection.close()
            print("数据库连接已关闭")

if __name__ == "__main__":
    main()

手动筛选期望导出的用户

object_template.html

html 复制代码
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=0.8, maximum-scale=1.5">
    <title>{{ dbname }}对象统计报告</title>
    <style>
        /* 响应式核心样式 */
        * {
            box-sizing: border-box;
            margin: 0;
            padding: 0;
        }

        body {
            font-family: "Inter", sans-serif;
            background-color: #f5f7fa;
            line-height: 1.6;
        }

        .container {
            max-width: 1200px;
            margin: 0 auto;
            padding: 20px;
            min-width: 320px;
        }

        .page-title {
            text-align: center;
            margin-bottom: 30px;
        }

        .main-table {
            width: 100%;
            overflow-x: auto; /* 小屏幕显示横向滚动条 */
            margin-bottom: 30px;
            box-shadow: 0 2px 12px rgba(0,0,0,0.05);
            border-radius: 10px;
            background-color: white;
        }

        .user-table {
            min-width: 800px;
            border-collapse: collapse;
        }

        .user-table th,
        .user-table td {
            padding: 12px 15px;
            text-align: left;
            border-bottom: 1px solid #e0e0e0;
        }

        .user-table th {
            background-color: #f8f9fa;
            font-weight: 600;
            white-space: nowrap;
        }

        .user-table tr:hover {
            background-color: #fafafa;
        }

        /* 响应式断点 */
        @media (max-width: 768px) {
            .user-table th:nth-child(5),
            .user-table td:nth-child(5),
            .user-table th:nth-child(6),
            .user-table td:nth-child(6) {
                display: none; /* 手机端隐藏存储过程和触发器列 */
            }
        }

        @media (max-width: 576px) {
            .user-table th:nth-child(7),
            .user-table td:nth-child(7),
            .user-table th:nth-child(8),
            .user-table td:nth-child(8) {
                display: none; /* 超小屏隐藏同义词和序列列 */
            }
        }

        /* 详情模块 */
        .user-details {
            margin-bottom: 30px;
            background-color: white;
            border-radius: 10px;
            box-shadow: 0 2px 12px rgba(0,0,0,0.05);
            padding: 20px;
        }

        .tab-nav {
            display: flex;
            overflow-x: auto; /* 选项卡横向滚动 */
            padding-bottom: 15px;
            border-bottom: 1px solid #e0e0e0;
        }

        .tab {
            padding: 10px 18px;
            cursor: pointer;
            white-space: nowrap;
            margin-right: 15px;
            color: #666;
        }

        .tab.active {
            color: #165dff;
            border-bottom: 2px solid #165dff;
            font-weight: 500;
        }

        .tab-content {
            padding: 20px 0;
            display: none;
        }

        .tab-content.active {
            display: block;
        }

        .object-table {
            width: 100%;
            margin-bottom: 20px;
            border-collapse: collapse;
        }

        .object-table th,
        .object-table td {
            padding: 10px 15px;
            border-bottom: 1px solid #f0f0f0;
        }

        .object-table th {
            background-color: #f8f9fa;
            font-weight: 500;
            white-space: nowrap;
        }

        .code-block {
            background-color: #f5f7fa;
            padding: 15px;
            margin-top: 10px;
            border-radius: 6px;
            font-family: "Consolas", monospace;
            font-size: 0.9em;
            line-height: 1.4;
            overflow-x: auto;
        }

        /* 操作按钮 */
        .action-btn {
            background-color: #165dff;
            color: white;
            border: none;
            padding: 6px 12px;
            border-radius: 4px;
            cursor: pointer;
            transition: opacity 0.3s;
        }

        .action-btn:hover {
            opacity: 0.9;
        }

        /* 隐藏空模块 */
        .hidden {
            display: none !important;
        }
    </style>
</head>
<body>
    <div class="container">
        <div class="page-title">
            <h1 class="text-2xl font-bold">{{ dbname }}对象统计报告</h1>
            <p class="text-gray-500 mt-2">生成时间: {{ generate_time }}</p>
        </div>

        <div class="main-table">
            <h2 class="text-lg font-bold mb-4">用户对象统计信息</h2>
            <table class="user-table">
                <thead>
                    <tr>
                        <th>用户名</th>
                        <th>默认表空间</th>
                        {% if has_tables %}<th>表数量</th>{% endif %}
                        {% if has_views %}<th>视图数量</th>{% endif %}
                        {% if has_indexes %}<th>索引数量</th>{% endif %}
                        {% if has_procedures %}<th>存储过程</th>{% endif %}
                        {% if has_triggers %}<th>触发器数量</th>{% endif %}
                        {% if has_synonyms %}<th>同义词数量</th>{% endif %}
                        {% if has_sequences %}<th>序列数量</th>{% endif %}
                        {% if has_functions %}<th>函数数量</th>{% endif %}
                        {% if has_packages %}<th>包数量</th>{% endif %}
                        <th>操作</th>
                    </tr>
                </thead>
                <tbody>
                    {% for user in users %}
                    <tr>
                        <td>{{ user.username }}</td>
                        <td>{{ user.tablespace_name }}</td>
                        {% if has_tables %}<td>{{ user.table_count }}</td>{% endif %}
                        {% if has_views %}<td>{{ user.view_count }}</td>{% endif %}
                        {% if has_indexes %}<td>{{ user.index_count }}</td>{% endif %}
                        {% if has_procedures %}<td>{{ user.procedure_count }}</td>{% endif %}
                        {% if has_triggers %}<td>{{ user.trigger_count }}</td>{% endif %}
                        {% if has_synonyms %}<td>{{ user.synonym_count }}</td>{% endif %}
                        {% if has_sequences %}<td>{{ user.sequence_count }}</td>{% endif %}
                        {% if has_functions %}<td>{{ user.function_count }}</td>{% endif %}
                        {% if has_packages %}<td>{{ user.package_count }}</td>{% endif %}
                        <td>
                            <button class="action-btn" onclick="toggleDetails('{{ user.username }}')">
                                查看详情
                            </button>
                        </td>
                    </tr>
                    {% endfor %}
                </tbody>
            </table>
        </div>

        {% for user in users %}
        <div id="{{ user.username }}_details" class="user-details hidden">
            <div class="flex justify-between items-center mb-4">
                <h3 class="text-xl font-bold">用户: {{ user.username }}</h3>
                <button class="action-btn" onclick="hideDetails('{{ user.username }}')">
                    收起
                </button>
            </div>

            <div class="tab-nav">
                {% set object_types = ['tables', 'views', 'indexes', 'procedures', 'triggers', 'synonyms', 'sequences', 'functions','packages'] %}
                {% for type in object_types %}
                {% set items = user[type] %}
                {% if items|length > 0 %}
                <div class="tab" data-target="{{ user.username }}_{{ type }}">
                    {{ type|capitalize|replace('_', ' ') }}
                </div>
                {% endif %}
                {% endfor %}
            </div>

            {% for type in object_types %}
            {% set items = user[type] %}
            {% if items|length > 0 %}
            <div id="{{ user.username }}_{{ type }}" class="tab-content">
                <h4 class="text-md font-semibold mb-3">
                    {{ type|capitalize|replace('_', ' ') }} 列表 (共 {{ items|length }} 个)
                </h4>
                <table class="object-table">
                    <thead>
                        <tr>
                            <th>对象名</th>
                            <th>创建时间</th>
                            <th>对象注释</th>
                            <th>操作</th>
                        </tr>
                    </thead>
                    <tbody>
                        {% for item in items %}
                        <tr>
                            <td>{{ item.name }}</td>
                            <td>{{ item.created_at }}</td>
                            <td>{{ item.comment|default('无') }}</td>
                            <td>
                                <button class="action-btn" onclick="toggleCode('{{ user.username }}', '{{ type }}', '{{ item.name }}')">
                                    查看语句
                                </button>
                            </td>
                        </tr>
                        <tr id="{{ user.username }}_{{ type }}_{{ item.name }}_code" style="display: none;">
                            <td colspan="4">
                                <div class="code-block">
                                    {{ item.create_statement|replace('\n', '<br>')}}  
                                </div>
                            </td>
                        </tr>
                        {% endfor %}
                    </tbody>
                </table>
            </div>
            {% endif %}
            {% endfor %}
        </div>
        {% endfor %}

        <script>
            function toggleDetails(username) {
                const details = document.getElementById(`${username}_details`);
                details.classList.toggle('hidden');
                
                if (!details.classList.contains('hidden')) {
                    // 激活第一个存在的选项卡
                    const firstTab = details.querySelector('.tab');
                    if (firstTab) {
                        activateTab(firstTab);
                    }
                }
            }

            function hideDetails(username) {
                const details = document.getElementById(`${username}_details`);
                details.classList.add('hidden');
            }

            function toggleCode(username, type, name) {
                const codeBlock = document.getElementById(`${username}_${type}_${name}_code`);
                if (codeBlock.style.display === 'none') {
                    codeBlock.style.display = 'table-row';
                } else {
                    codeBlock.style.display = 'none';
                }
            }

            function activateTab(tab) {
                const target = tab.getAttribute('data-target');
                
                // 移除其他选项卡的激活状态
                const tabs = tab.closest('.user-details').querySelectorAll('.tab');
                tabs.forEach(t => t.classList.remove('active'));
                tab.classList.add('active');
                
                // 隐藏所有内容
                const contents = tab.closest('.user-details').querySelectorAll('.tab-content');
                contents.forEach(c => c.classList.remove('active'));
                
                // 显示目标内容
                document.getElementById(target).classList.add('active');
            }

            // 选项卡切换
            document.addEventListener('click', (e) => {
                if (e.target.classList.contains('tab')) {
                    activateTab(e.target);
                }
            });
        </script>
    </div>
</body>
</html>
相关推荐
码农阿豪2 小时前
告别兼容焦虑:电科金仓 KES 如何把 Oracle 的 PL/SQL 和 JSON 业务“接住”
数据库·sql·oracle·json·金仓数据库
曹牧2 小时前
Oracle SQL 中,& 字符
数据库·sql·oracle
wdfk_prog2 小时前
[Linux]学习笔记系列 -- [fs]dcache
linux·数据库·笔记·学习·ubuntu
xrl20123 小时前
ruoyi-vue2集成flowable6.7.2后端篇
数据库·ruoyi·flowable·工作流集成
java1234_小锋3 小时前
Redis到底支不支持事务啊?
java·数据库·redis
云和恩墨3 小时前
告别 “事后救火”:7 大前置动作规避 80% 数据库故障
数据库·oracle
STLearner4 小时前
VLDB 2025 | 时间序列(Time Series)论文总结(预测,异常检测,压缩,自动化等)
数据库·人工智能·深度学习·神经网络·机器学习·数据挖掘·时序数据库
4 小时前
TIDB——TIKV——raft
数据库·分布式·tidb
不会c嘎嘎4 小时前
MySQL 指南:全面掌握用户管理与权限控制
数据库·mysql