从 Oracle PL/SQL 匿名块填充 C# 数据表

Posted

技术标签:

【中文标题】从 Oracle PL/SQL 匿名块填充 C# 数据表【英文标题】:Fill C# DataTable from Oracle PL/SQL Anonymous Block 【发布时间】:2017-06-19 14:46:20 【问题描述】:

我正在尝试执行光标的结果并将其返回到我的 C# 程序。以下是我的代码:

            OracleCommand cmd = new OracleCommand();
            cmd.CommandText = @"
              declare
              varEmpID              myTable.emp_id%type; 
              varSupID              myTable.supervisor_id%type;
              varNewSupID           myTable.supervisor_id%type;
              varSupActive          myTable.active_ind%type;  

              Cursor C1 is(
                select emp_id,active_ind,supervisor_id
                from myTable where emp_id in 
                ('1','2')) order by emp_id;

            begin                 
              Open C1;                    
              LOOP
                FETCH C1 INTO varEmpID,varSupActive,varSupID;     
                EXIT WHEN C1%NOTFOUND;                       
                while (varSupActive<>'Y') loop

                  select m1.supervisor_id, m2.Active_Ind 
                  into varNewSupID,varSupActive
                  from myTable m1, myTable m2 
                  where m1.supervisor_id = m2.emp_id and m1.emp_id = varSupID;

                  varSupID := varNewSupID;

                end loop;

                 open :rc1 for select sup.emp_id, sup.FIRST_NAME,sup.LAST_NAME                    
                 into varEmpID,varFirstName,varLastName
                 from myTable sup 
                 where emp_id = varSupID; 

               END LOOP;
               CLOSE C1;
            EXCEPTION
               WHEN OTHERS THEN
                  raise_application_error(-20001,'An error was encountered - '||SQLCODE||' -ERROR- '||SQLERRM);

            END;

    ";
            cmd.BindByName = true;
            cmd.Parameters.Add("rc1", OracleDbType.RefCursor).Direction = ParameterDirection.Output;

            using (cmd.Connection = new OracleConnection(strCon))
            
                cmd.Connection.Open();
                var reader = cmd.ExecuteReader();
                OracleDataAdapter da = new OracleDataAdapter(cmd);
                DataTable dt = new DataTable();
                da.Fill(dt);                                     
            

光标的目的是为给定的一组用户找到一个活跃的主管。如果我的表中用户的主管处于非活动状态,我需要在层次结构中上升,直到找到一个活动的主管。还有一些我在这里删除的条件,但我想说的是,我认为这不能使用单个 SQL 语句来实现。

很明显,我没有让所有新的主管都使用 rc1 游标,因为它在循环中并且被覆盖了。所以我的 C# 输出只给了我最后一个主管。我尝试创建表类型数组,但即使这样我也必须在 for 循环中使用索引来访问。

我只有对数据库的读取权限,所以我不能创建函数、过程等。有没有办法可以将新的主管数据写入该程序范围内的临时表而不是变量,这样我就可以在循环后从该临时表中选择 *。或者任何其他方式我可以得到我需要的东西。谢谢!

【问题讨论】:

从未尝试从 .NET 执行匿名块。通常我会创建一个流水线函数来将数据从 Oracle 传递到 .NET。见psoug.org/reference/pipelined.html @PaulStearns 我不能也不应该在数据库中创建函数。 抱歉错过了只读位。这是您需要的程序,还是已简化?我问的原因是,您可能会使用“连接方式”语法并能够将所有内容放入单个查询中。 docs.oracle.com/cd/B19306_01/server.102/b14200/queries003.htm 【参考方案1】:

我认为您可以使用这样的分层查询来代替 PLSQL 块:

select emp_id supervisor_id, last_name, root emp_id
  from (
    select t.*, 
           min(case when active_ind = 'Y' and root <> emp_id 
                    then lvl end) over (partition by root) mlvl
      from (
        select m.*, level lvl,
               connect_by_root(emp_id) root
          from mytable m
          connect by emp_id = prior supervisor_id 
          start with emp_id in (1, 2) ) t )
  where lvl = mlvl

子查询t 创建主管的树。接下来是min() 函数,我正在搜索这个级别最低(最接近员工)且处于活动状态的人。

测试数据:

create table myTable (emp_id number(3), active_ind varchar2(1),  
                      last_name varchar(10), supervisor_id number(3));

insert into mytable values (1, 'Y', 'Brown', 3);
insert into mytable values (2, 'Y', 'Smith', 4);
insert into mytable values (3, 'Y', 'Adams', 6);
insert into mytable values (4, 'N', 'Novak', 5);
insert into mytable values (5, 'Y', 'King',  null);
insert into mytable values (6, 'Y', 'Jones',  null);

输出:

SUPERVISOR_ID  LAST_NAME  EMP_ID
-------------  ---------  ------
3              Adams           1
5              King            2

编辑:Oracle 11 和递归 CTE 版本:

with t (root, id, act, name, sid, stop) as (
  select emp_id, emp_id, active_ind, last_name, supervisor_id, 0
    from mytable where emp_id in (1, 2)
  union all 
  select t.root, m.emp_id, m.active_ind, m.last_name, m.supervisor_id, 
    case when m.active_ind = 'Y' then 1 else 0 end 
    from t join mytable m on m.emp_id = t.sid and t.stop = 0
  )
select * from t where stop = 1

如果出现“循环”问题,请使用 CYCLE id SET cycle TO 1 DEFAULT 0 之类的内容,如下所述:Recursive Subquery Factoring

【讨论】:

我收到 ORA-01436:在用户数据中循环连接。我所做的只是在您的查询中将 emp_id 替换为 person_id,因为那是实际的列名。 没关系。我添加了 NOCYCLE,它就像一个魅力。从来不知道我们只需使用一个查询就可以实现如此复杂的功能。 但是即使是 5 个用户,这个单一的查询也需要很长时间才能执行。从 sql developer 开始,它首先需要 15 秒,后来 2 分钟,第 3 次尝试,它继续运行。从我的 C# 应用程序所在的 Visual Studio Code 中,它被卡住了,并且结果集永远不会出现,而 pl/sql 块的情况并非如此,尽管 1 次针对 900 个用户运行它.. 在没有数据访问的情况下很难重现这种行为。分层查询比其他查询慢。您可以做的是将connect by 重写为with ... union all ... 在找到第一个主管后“停止”的版本。这种类型的查询称为递归 CTE,需要 Oracle 版本 11。如果您发现当前答案不可用,请不接受并等待其他人。

以上是关于从 Oracle PL/SQL 匿名块填充 C# 数据表的主要内容,如果未能解决你的问题,请参考以下文章

PL/SQL ORA-01422 SELECT INTO 错误,Oracle 匿名块(NOVA 环境)

从 oracle PL/SQL 查看变量的值

PL/SQL语法简介(匿名PL/SQL块)

Oracle 匿名 PL/SQL 块中缺少关键字错误

pl/sql基础练习

无法在 Oracle 匿名块中调用和执行 .sql 脚本文件