【问题标题】:Returning result even for elements in IN list that don't exist in table即使对于表中不存在的 IN 列表中的元素也返回结果
【发布时间】:2014-01-28 18:30:09
【问题描述】:

我正在尝试找到返回结果集的最简单方法,该结果集指示某些值是否存在于表中。考虑这张表:

ID ------ 1 2 3 7 23

我将收到一个 ID 列表,我需要使用相同的列表进行响应,指明表中存在哪些 ID。如果我得到的列表如下所示:'1','2','3','4','8','23',我需要生成一个如下所示的结果集:

编号 |状态 ------------- 1 |展示 2 |展示 3 |展示 4 |失踪 8 |失踪 23 |展示

到目前为止,我已经设法使用UNPIVOT

select id, 'present' as status
from my_table
where id in ('1','2','3')
union
select subq.v as id, 'missing' as status
from (
        select v
        from
        (
          (
            select '1' v1, '2' v2, '3' v3 from dual
          )
          unpivot
          (
            v
            for x in (v1,v2,v3)
          )
        )
      ) subq
where subq.v not in
(
   select id
   from my_table 
   where id in ('1','2','3')
);

它看起来有点奇怪,但它确实有效。这个问题是select '1' v1, '2' v2, '3' v3 from dual 部分:我不知道如何使用 JDBC 准备语句填充它。 ID 列表不固定,因此对使用此查询的函数的每次调用都可以传递不同的 ID 列表。

还有其他方法可以完成这项工作吗?我想我遗漏了一些明显的东西,但我不确定......

(使用 Oracle 11)

【问题讨论】:

  • 您可以传递一个值数组(基于 UDT),您可以将其视为一个表,然后将其左外连接到您的真实表?目前无法充实这个想法......

标签: sql oracle plsql


【解决方案1】:

从 SQL 方面,您可以定义一个表类型并使用它来连接您的真实数据,例如:

create type my_array_type as table of number
/

create or replace function f42 (in_array my_array_type)
return sys_refcursor as
  rc sys_refcursor;
begin
  open rc for
    select a.column_value as id,
      case when t.id is null then 'missing'
        else 'present' end as status
    from table(in_array) a
    left join t42 t on t.id = a.column_value
    order by id;

  return rc;
end f42;
/

SQL Fiddle demo 带有包装函数,因此您可以直接查询它,它给出:

        ID STATUS             
---------- --------------------
         1 present              
         2 present              
         3 present              
         4 missing              
         8 missing              
        23 present              

在Java中,您可以根据表类型定义ARRAY,从Java数组中填充,并直接调用函数;你的单参数绑定变量是ARRAY,你会得到一个可以正常迭代的结果集。

作为Java端的概述:

int[] ids = { 1, 2, 3, 4, 8, 23 };
ArrayDescriptor aDesc = ArrayDescriptor.createDescriptor("MY_ARRAY_TYPE",
  conn);
oracle.sql.ARRAY ora_ids = new oracle.sql.ARRAY(aDesc, conn, ids);

cStmt = (OracleCallableStatement) conn.prepareCall("{ call ? := f42(?) }");
cStmt.registerOutParameter(1, OracleTypes.CURSOR);
cStmt.setArray(2, ora_ids);
cStmt.execute();
rSet = (OracleResultSet) cStmt.getCursor(1);

while (rSet.next())
{
    System.out.println("id " + rSet.getInt(1) + ": " + rSet.getString(2));
}

这给出了:

id 1: present
id 2: present
id 3: present
id 4: missing
id 8: missing
id 23: present

正如 Maheswaran Ravisankar 所提到的,这允许传递任意数量的元素;您不需要知道编译时有多少元素(或处理理论上的最大值),您不受 IN 中允许的最大表达式数或单个分隔字符串的长度的限制,并且您不必组合和分解一个字符串来传递多个值。


正如 ThinkJet 指出的,如果您不想创建自己的表类型,可以使用预定义的集合,demonstrated here;主函数除了参数的声明是一样的:

create or replace function f42 (in_array sys.odcinumberlist)
return sys_refcursor as
...    

包装函数填充数组的方式略有不同,但在 Java 端您只需要更改此行:

ArrayDescriptor aDesc =
  ArrayDescriptor.createDescriptor("SYS.ODCINUMBERLIST", conn );

使用这也意味着(正如 ThinkJet 也指出的那样!)您可以在不定义函数的情况下运行原始的独立查询:

select a.column_value as id,
case when t.id is null then 'missing'
else 'present' end as status
from table(sys.odcinumberlist(1, 2, 3, 4, 8, 23)) a
left join t42 t on t.id = a.column_value
order by id;

(SQL Fiddle).

这意味着您可以直接从 Java 调用查询:

int[] ids = { 1, 2, 3, 4, 8, 23 };
ArrayDescriptor aDesc = ArrayDescriptor.createDescriptor("SYS.ODCINUMBERLIST", conn );
oracle.sql.ARRAY ora_ids = new oracle.sql.ARRAY(aDesc, conn, ids);

sql = "select a.column_value as id, "
    + "case when t.id is null then 'missing' "
    + "else 'present' end as status "
    + "from table(?) a "
    + "left join t42 t on t.id = a.column_value "
    + "order by id";
pStmt = (OraclePreparedStatement) conn.prepareStatement(sql);
pStmt.setArray(1, ora_ids);
rSet = (OracleResultSet) pStmt.executeQuery();

while (rSet.next())
{
    System.out.println("id " + rSet.getInt(1) + ": " + rSet.getString(2));
}

...您可能更喜欢。

还有一个预定义的 ODCIVARCHAR2LIST 类型,如果您实际上是在传递字符串 - 您的原始代码似乎正在处理字符串,即使它们包含数字,所以不确定您真正需要哪个。

因为这些types are defined as VARRAY(32767) 限制为 32k 值,而定义自己的表消除了该限制;但显然,只有当你传递了很多值时,这才重要。

【讨论】:

  • +1.. 支持任意数量的项目.. NVL2(t.id, 'present','missing'),也可以使用,而不是大小写.. 只是一个想法。!
  • +1 但可以使用预定义的 Oracle 类型作为参数(Sys.ODCINumberList 在数字 Id 的情况下)
  • 好的,我只想指出传递数组参数不需要更改数据库大小的代码。即使是初始目标(针对数据库的单个查询)也可以在根本不声明数据库类型和功能的情况下实现。
  • @ThinkJet - 真;我可能过于关注 JDBC 要求。我已经用独立查询和修改功能的这些要点和演示更新了答案。我将添加一个也不需要该功能的 JDBC 版本。谢谢!
  • 亚历克斯,感谢您的出色回答!你让我不用再解释同样的事情了(已经为 PHPC/C++ 完成了):-)
【解决方案2】:

SQL Fiddle

Oracle 11g R2 架构设置

create table IDs (id) AS
            SELECT 1 FROM DUAL
  UNION ALL SELECT 2 FROM DUAL
  UNION ALL SELECT 3 FROM DUAL
  UNION ALL SELECT 7 FROM DUAL
  UNION ALL SELECT 23 FROM DUAL
/

查询 1

将 ID 输入为包含数字列表的字符串,然后使用分层查询和正则表达式将字符串拆分为行:

WITH input AS (
 SELECT '1,2,3,4,8,23' AS input FROM DUAL
),
split_inputs AS (
 SELECT TO_NUMBER( REGEXP_SUBSTR( input, '\d+', 1, LEVEL ) ) AS id
 FROM input
 CONNECT BY LEVEL <= REGEXP_COUNT( input, '\d+' )
)
SELECT s.id,
 CASE WHEN i.id IS NULL THEN 'Missing' ELSE 'Present' END AS status
FROM split_inputs s
 LEFT OUTER JOIN
 IDs i
 ON ( s.id = i.id )
ORDER BY
 s.id

Results

| ID |  STATUS |
|----|---------|
|  1 | Present |
|  2 | Present |
|  3 | Present |
|  4 | Missing |
|  8 | Missing |
| 23 | Present |

【讨论】:

  • 很好,但是 IN 字符串的长度应该是
猜你喜欢
  • 1970-01-01
  • 2019-08-20
  • 1970-01-01
  • 2021-05-26
  • 2018-11-24
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多