【问题标题】:Oracle 10g - Get list of tables from query i'm executingOracle 10g - 从我正在执行的查询中获取表列表
【发布时间】:2017-02-16 15:27:05
【问题描述】:

我正在开发我组织的内部工具,我目前有自动化需求。

输入查询示例:

Select name,userid,url,address_line_1 from user join address on 
user.user_id = address.user_id where userid = 'xxyy';

我需要的是表格列表

user
address

Oracle 10G 中是否有内置方法可以从此查询中获取表列表?或者是否有 python 解析器可以帮助我处理查询中的表列表?

注意:这是一个基本示例,我的查询分几行运行并且更复杂。

【问题讨论】:

  • 上述查询是视图(还是作为对象存储在oracle中)?如果是这样,您可以使用系统表并查看视图的 all_dependencies Select Referenced_Name from all_Dependencies where name = 'ViewName' and referenced_Type='TABLE'
  • @xQbert 不是视图,是代码中的查询
  • 那么像其他答案一样吗? stackoverflow.com/questions/13366096/…。我认为您真正需要的只是遍历这两件事:EXPLAIN PLAN FOR <<the query>> 然后SELECT DISTINCT object_owner, object_name, object_type FROM plan_table
  • @xQbert 我想它有点接近。我会尝试从 Python 自动化,谢谢你指出我的方向
  • 我倾向于相信数据库引擎解析器比我们自己编写/解析的任何东西都更擅长识别所有涉及的表;这就是我使用系统表或数据库引擎工具的原因。

标签: oracle python-3.x oracle10g


【解决方案1】:

您可以创建一个包过程来接受 SQL 语句作为输入。它会做的是将 SQL 包装在 CREATE VIEW 中,然后分析结果视图的依赖关系。

这里是源代码。

CREATE OR REPLACE PACKAGE matt_analysis_pkg IS
  PROCEDURE analyze_sql ( p_sql CLOB );
END matt_analysis_pkg;
/

CREATE OR REPLACE PACKAGE BODY matt_analysis_pkg AS

  PROCEDURE analyze_sql (p_sql CLOB) IS
    PRAGMA AUTONOMOUS_TRANSACTION;
    l_sql CLOB;    
    l_column_count          INTEGER;
    l_view_name VARCHAR2(30);
    l_view_columns   VARCHAR2(4000);
  BEGIN
    DBMS_OUTPUT.put_line ('Current SQL: ' || p_sql);

    -- Parse the SQL to get the column count
    DECLARE
  l_cursor                INTEGER;
  l_column_descriptions   SYS.DBMS_SQL.desc_tab;

  BEGIN
    l_cursor   := sys.DBMS_SQL.open_cursor;

  -- parse SQL
  sys.DBMS_SQL.parse (c => l_cursor, statement => p_sql, language_flag => sys.DBMS_SQL.native);

  -- Describe columns
  sys.DBMS_SQL.describe_columns (c => l_cursor, col_cnt => l_column_count, desc_t => l_column_descriptions);

  sys.DBMS_SQL.close_cursor (l_cursor);

END;
  DBMS_OUTPUT.PUT_LINE('Column count = ' || l_column_count);

  -- Build view columns.  We need to do this because the column names in the SQL are not necessarily unique.    
  SELECT listagg('C' || lpad(rownum,4,'0'),',') within group ( order by rownum )
  INTO    l_view_columns 
  FROM   dual 
  CONNECT BY rownum <= l_column_count;

  DBMS_OUTPUT.PUT_LINE('l_view_columns = ' || l_view_columns);

  l_view_name := 'matt_analysis_view_' || lpad(matt_analysis_view_s.nextval,6,'0') || '$';
  DBMS_OUTPUT.PUT_LINE('l_view_name = ' || l_view_name);

  l_sql := 'CREATE OR REPLACE FORCE VIEW ' || l_view_name || ' (' || l_view_columns || ') AS ' || p_sql;

  EXECUTE IMMEDIATE l_sql;

  DBMS_OUTPUT.PUT_LINE('Objects referenced by current SQL: ');
  FOR r IN ( select referenced_owner || '.' || referenced_name || ' (' || referenced_type || ')' reference_info 
             from user_dependencies where name = upper(l_view_name)
             AND  referenced_name not like 'MATT_ANALYSIS%' ) LOOP
    DBMS_OUTPUT.PUT_LINE(r.reference_info);
  END LOOP;

  EXECUTE IMMEDIATE 'DROP VIEW ' || l_view_name;

  COMMIT;

  END analyze_sql;
END matt_analysis_pkg;
/

测试人员

BEGIN
   matt_analysis_pkg.analyze_sql(p_sql => 
      'WITH oel AS ( SELECT * 
                     FROM oe_order_lines 
                     WHERE ship_from_org_id = 88 ) 
      SELECT oel.line_id, msi.segment1 
      FROM oel INNER JOIN mtl_system_items msi 
         ON msi.organization_id = 92 and msi.inventory_item_id = oel.inventory_item_id');
END;

Objects referenced by current SQL: 
APPS.MTL_SYSTEM_ITEMS (SYNONYM)
APPS.OE_ORDER_LINES (SYNONYM)

【讨论】:

    【解决方案2】:

    我仍在等待解决方案,但现在,我想出了一个快速而肮脏的方法来使用以下 python 获取表名和别名。

        from copy import deepcopy
    
        import cx_Oracle
    
    
        ############################################################################
        ####################### Database connection instantiation###################
        ############################################################################
    
        #connObj = pyodbc.connect(connString,autocommit=True)
    
        qry = """
                    Select name,userid,url,address_line_1 from user join address on 
    user.user_id = address.user_id where userid = 'xxyy'
                """
    
    
        def getConn():
            connObj = cx_Oracle.connect('asap', 'sdfssa', cx_Oracle.makedsn('DBLDEV03', 1521, '23432'))
            return connObj
    
        def destroy(connObj):
            connObj.commit()
            connObj.close()
    
            del connObj
        #####################       Logic  ########################################
        import datetime
    
        def getTablesFromQuery(qry):
            listTables = []
            listAliases = []
            connObj = getConn()
            cursor = connObj.cursor()
            listSpaceItems = qry.split(" ")
            found =False
            for spaceItem in listSpaceItems:
                if spaceItem != '' and spaceItem!='\n':
                    spaceItem = spaceItem.replace("\n",'')
                    listCommaItems = spaceItem.split(",")
                    if found == True:
                        ##### We are assuming that the next item is always alias, the sql query should follow that rule to
                        ##### get the aliases properly.
                        listAliases.append(spaceItem)
                        found = False
    
                    for commaItem in listCommaItems:
                        if commaItem != '':
                            item = commaItem
    
                            if "." in commaItem:
                                item=commaItem.split(".")[1]
    
    
                            cursor.execute('select * from all_tables where table_name=\''+item.upper()+'\'')
                            res = cursor.fetchall()
                            if res is not None and res.__len__()>0:
                                listTables.append(commaItem)
                                found = True
            destroy(connObj)
            return listTables,listAliases
    
        try:
            listTables, listAliases = getTablesFromQuery(qry)
            for item in listTables:
                print(''+item)
    
        except:
            print('Exception..')
    
    
    ############################################################################
    ####################### close database connection###########################
    ############################################################################
    

    【讨论】:

      【解决方案3】:

      有趣的问题。

      您可以使用DBMS_FGA 在 PL/SQL 中构建一个小型 SQL 分析器。这个想法是:

      • 自动修改输入 SQL 以使用带有 FGA 策略的表
      • 在该 FGA 策略中,您将有权访问当前 SQL(无论如何,它的前 32K。这是一个限制...)
      • 使用当前 SQL 在当前 SQL 上构建一次性视图
      • USER_DEPENDENCIES 读取丢弃视图的依赖项
      • 放弃一次性视图。

      这是一个如何工作的示例:

      (我很抱歉将我的名字放在所有对象中;我与其他人共享这个数据库。)

      -- Tester
      BEGIN
         matt_analysis_pkg.analyze_sql(p_sql => 
            'WITH oel AS ( SELECT * 
                           FROM oe_order_lines 
                           WHERE ship_from_org_id = 88 ) 
            SELECT oel.line_id, msi.segment1 
            FROM oel INNER JOIN mtl_system_items msi 
               ON msi.organization_id = 92 and msi.inventory_item_id = oel.inventory_item_id');
      END;
      /
      
      Objects referenced by current SQL: 
      APPS.MTL_SYSTEM_ITEMS (SYNONYM)
      APPS.OE_ORDER_LINES (SYNONYM)
      

      示例(如下)仅报告第一级依赖关系。您可以递归地使用 DBA_DEPENDENCIES 来获得更多深度。另外,这个版本只写信给DBMS_OUTPUT

      正如其他人所报告的,仅仅因为 SQL 依赖于对象并不意味着 Oracle 会在运行时实际访问该对象。不过,我认为这与您的要求非常接近。

      另外,我注意到您使用 Oracle 10g 标记了您的问题。我认为我的解决方案中唯一在 10g 中不起作用的是我对序列的直接访问。您必须将该部分替换为 SELECT ... INTO 才能获得当前序列值。

      无论如何,这是它的源代码(Oracle 12c):

      -- This table doesn't do anything other than have a FGA policy on it.
      CREATE TABLE matt_analysis_tab ( dummy varchar2(1) );
      
      INSERT INTO matt_analysis_tab (dummy) VALUES ('X');
      
      -- Sequence so we can create unique view names, in case two people analyze at the same time.
      CREATE SEQUENCE matt_analysis_view_s;
      
      -- Package to do the work.
      CREATE OR REPLACE PACKAGE matt_analysis_pkg IS
        PROCEDURE analyze_sql ( p_sql CLOB );
        PROCEDURE analyze_current_sql (schema_name VARCHAR2, table_name VARCHAR2, policy_name VARCHAR2);
      END matt_analysis_pkg;
      /
      
      CREATE OR REPLACE PACKAGE BODY matt_analysis_pkg AS
      
        PROCEDURE analyze_sql (p_sql CLOB) IS
          l_modified_sql CLOB := 'WITH v1$ AS ( SELECT /*+ MATERIALIZE */ dummy FROM matt_analysis_tab ) SELECT v1$.dummy, v2$.* FROM v1$, ( ' || p_sql || ') v2$';
        BEGIN
          DBMS_OUTPUT.PUT_LINE('l_modified_sql := ' || l_modified_sql);
          EXECUTE IMMEDIATE l_modified_sql;
        END analyze_sql;
      
      
        PROCEDURE analyze_current_sql (schema_name VARCHAR2, table_name VARCHAR2, policy_name VARCHAR2) IS
          PRAGMA AUTONOMOUS_TRANSACTION;
          l_sql CLOB;    
          l_column_count          INTEGER;
          l_view_name VARCHAR2(30);
          l_view_columns   VARCHAR2(4000);
        BEGIN
          l_sql := SYS_CONTEXT ('userenv', 'CURRENT_SQL',4000) 
          || SYS_CONTEXT ('userenv', 'CURRENT_SQL1',4000)
          || SYS_CONTEXT ('userenv', 'CURRENT_SQL2',4000)
          || SYS_CONTEXT ('userenv', 'CURRENT_SQL3',4000)
          || SYS_CONTEXT ('userenv', 'CURRENT_SQL4',4000)
          || SYS_CONTEXT ('userenv', 'CURRENT_SQL5',4000)
          || SYS_CONTEXT ('userenv', 'CURRENT_SQL6',4000)
          || SYS_CONTEXT ('userenv', 'CURRENT_SQL7',4000)
          ;
          DBMS_OUTPUT.put_line ('Current SQL: ' || l_sql);
          DBMS_OUTPUT.put_line ('Current SQL length (calc): ' || length(l_sql));
          DBMS_OUTPUT.put_line ('Current SQL length (userenv): ' || SYS_CONTEXT('userenv','CURRENT_SQL_LENGTH'));
      
          -- Parse the SQL to get the column count
          DECLARE
        l_cursor                INTEGER;
        l_column_descriptions   SYS.DBMS_SQL.desc_tab;
      
        BEGIN
          l_cursor   := sys.DBMS_SQL.open_cursor;
      
        -- parse SQL
        sys.DBMS_SQL.parse (c => l_cursor, statement => l_sql, language_flag => sys.DBMS_SQL.native);
      
        -- Describe columns
        sys.DBMS_SQL.describe_columns (c => l_cursor, col_cnt => l_column_count, desc_t => l_column_descriptions);
      
        sys.DBMS_SQL.close_cursor (l_cursor);
      
      END;
        DBMS_OUTPUT.PUT_LINE('Column count = ' || l_column_count);
      
        -- Build view columns.  We need to do this because the column names in the SQL are not necessarily unique.    
        SELECT listagg('C' || lpad(rownum,4,'0'),',') within group ( order by rownum )
        INTO    l_view_columns 
        FROM   dual 
        CONNECT BY rownum <= l_column_count;
      
        DBMS_OUTPUT.PUT_LINE('l_view_columns = ' || l_view_columns);
      
        l_view_name := 'matt_analysis_view_' || lpad(matt_analysis_view_s.nextval,6,'0') || '$';
        DBMS_OUTPUT.PUT_LINE('l_view_name = ' || l_view_name);
      
        l_sql := 'CREATE OR REPLACE FORCE VIEW ' || l_view_name || ' (' || l_view_columns || ') AS ' || l_sql;
      
        EXECUTE IMMEDIATE l_sql;
      
        DBMS_OUTPUT.PUT_LINE('Objects referenced by current SQL: ');
        FOR r IN ( select referenced_owner || '.' || referenced_name || ' (' || referenced_type || ')' reference_info 
                   from user_dependencies where name = upper(l_view_name)
                   AND  referenced_name not like 'MATT_ANALYSIS%' ) LOOP
          DBMS_OUTPUT.PUT_LINE(r.reference_info);
        END LOOP;
      
        EXECUTE IMMEDIATE 'DROP VIEW ' || l_view_name;
      
        COMMIT;
      
        END analyze_current_sql;
      END matt_analysis_pkg;
      /
      
      -- Create the FGA policy 
      BEGIN
        DBMS_FGA.add_policy (
          object_schema     => NULL,  -- My current schema
          object_name       => 'MATT_ANALYSIS_TAB',
          policy_name       => 'MATT_ANALYSIS_POLICY',
          audit_condition   => NULL,
          audit_column      => NULL,
          handler_schema    => NULL,  -- My current schema
          handler_module    => 'matt_analysis_pkg.analyze_current_sql',
          enable            => TRUE);
      END;
      /
      
      -- Script to drop the policy, just in case
      --EXEC  DBMS_FGA.drop_policy (NULL, 'MATT_ANALYSIS_TAB', 'MATT_ANALYSIS_POLICY');
      

      【讨论】:

      • 感谢您的尝试,我会试试这个。
      • 实际上,您可以不用所有 FGAC 的东西。只需让ANALYZE_SQL 构建视图等等。 FGAC 方法来自其他方面。如果你愿意,我可以发布一个简化版。
      猜你喜欢
      • 2015-11-06
      • 2021-06-03
      • 1970-01-01
      • 2019-07-05
      • 2011-03-29
      • 2018-05-22
      • 1970-01-01
      • 1970-01-01
      • 2014-07-29
      相关资源
      最近更新 更多