将列从 Varchar2 转换为 CLOB 后查询无法执行

Posted

技术标签:

【中文标题】将列从 Varchar2 转换为 CLOB 后查询无法执行【英文标题】:Query fails to execute after converting a column from Varchar2 to CLOB 【发布时间】:2016-05-20 08:36:15 【问题描述】:

我有一个 oracle 查询

select id from (
    select ID, ROW_NUMBER() over (partition by LATEST_RECEIPT order by ID) rownumber
    from Table
    where LATEST_RECEIPT in 
    (
        select LATEST_RECEIPT from Table 
        group by LATEST_RECEIPT
        having COUNT(1) > 1
    )
) t
where rownumber <> 1;

LATEST_RECEIPT 的数据类型较早时为 varchar2(4000),此查询运行良好。由于需要扩展列的长度,我将其修改为 CLOB,之后失败。谁能帮我解决这个问题或提供解决方法?

【问题讨论】:

抛出什么错误? ORA00932- 不一致的数据类型- 预期:得到 clob 这是使用 CLOB/BLOB 的缺点之一,它们不能用于索引,也不能用于 GROUP BY 和其他聚合函数。我认为您必须将其转换回 VARCHAR(much-longer)data 类型 我有长度> 4000的数据。但是oracle中varchar2的最大大小是4000。所以,我们切换到CLOB 那么这是您的 DBMS 和您拥有的用例的真正问题。你真的需要按整个LAST_RECEIPT 文本分组吗? (如果是,那么您可能需要添加带有文本哈希键的另一列,然后将其用于按条件分组) 【参考方案1】:

您可以更改内部查询以查找具有相同 last_receipt 值但 ID 不同的其他行(假设 ID 是唯一的);如果存在另一行,则等于您的计数返回大于一。但是你不能简单地测试两个CLOB值是否相等,你需要使用dbms_lob.compare

select ID
from your_table t1
where exists (
    select null from your_table t2
    where dbms_lob.compare(t2.LATEST_RECEIPT, t1.LATEST_RECEIPT) = 0
    and t2.ID != t1.ID
    -- or if ID isn't unique: and t2.ROWID != t1.ROWID
);

应用行号过滤器比较棘手,因为您也不能在分析 partition by 子句中使用 CLOB。正如 André Schild 所建议的,您可以使用哈希;这里传递整数值 3,相当于dbms_crypto.hash_sh1(尽管理论上可能会在未来的版本中改变!):

select id from (
    select ID, ROW_NUMBER() over (partition by dbms_crypto.hash(LATEST_RECEIPT, 3)
        order by ID) rownumber
    from your_table t1
    where exists (
        select null from your_table t2
        where dbms_lob.compare(t2.LATEST_RECEIPT, t1.LATEST_RECEIPT) = 0
        and t2.ID != t1.ID
        -- or if ID isn't unique: and t2.ROWID != t1.ROWID
    )
)
where rownumber > 1;

当然有可能发生哈希冲突,如果发生这种情况 - 您有两个 latest_receipt 值,它们都出现了不止一次并且都哈希到相同的值 - 那么您可能会返回太多行。这似乎不太可能,但值得考虑。

因此,您只能查找具有相同 lastest_receipt 和较低 ID 的行,而不是排序:

select ID
from your_table t1
where exists (
    select null from your_table t2
    where dbms_lob.compare(t2.LATEST_RECEIPT, t1.LATEST_RECEIPT) = 0
    and t2.ID < t1.ID
);

再次假设 ID 是唯一的。如果不是,那么您仍然可以改用rowid,但您对找到哪些行的控制较少 - 最低的rowid 不一定是最低的ID。大概您正在使用它来删除要删除的行。如果您实际上不介意保留哪一行以及删除哪一行,那么您仍然可以这样做:

and t2.ROWID < t1.ROWID

但是由于您当前正在订购,这可能是不可接受的,尽管风险很小,但散列可能更可取。

【讨论】:

以上是关于将列从 Varchar2 转换为 CLOB 后查询无法执行的主要内容,如果未能解决你的问题,请参考以下文章

oracle修改字段类型由varchar2修改为clob类型

将列从日期转换为日期时间

将列从 varchar 转换为 nvarchar 是不是会更改存储在列中的字符串的编码?

Oracle中表列由VARCHAR2类型改成CLOB

bind_rows_(x, .id) 中的错误:无法将列从因子转换为数字

Oracle:将 VARCHAR2 列更改为 CLOB