我正在从事一项日常自动化工作,该工作针对oracle数据库检查文本文件中的记录。
我们每天都会收到来自外部团队的文本文件,其中包含大约100,000条记录。文本文件将为unix格式,具有6列,由|分隔。符号。
例如,
HDR 1
home/sample/file|testvalue1|testvalue2|testval3|testval4|testval5
TRL
我需要检查oracle数据库中我的表中是否存在testval3和testval5中的值。该表有大约一千万条记录。
我目前正在通过shellscript处理它。在shellscript中,我正在读取文本文件并循环遍历每一行。在循环内部,我从每一行传递值,并对DB运行查询。如果记录在数据库中不存在,则必须将它们输出到一个csv文件中。使用以下查询:
select ‘testval3’,’testval5’ from dual
where not exists (select primarykeycolumn
from mytable where mycolumn=testval3 and mycolumn2=testval5)
由于输入文件有100000个条目,因此我的循环将运行查询100000次,并且每次它将检查具有1000万条记录的表。这使我的批处理作业要运行多个小时,我必须终止它。有没有更好的方法来处理这种情况?如果没有更好的方法可以通过shellscript进行操作,我也可以使用Java。
您正在使用UTL_FILE吗?您正在使用哪个Oracle版本?
看来您已经选择了效率最低的方法。您是否曾听说过SQL * Loader,外部表等?我的建议是首先在数据库中创建一个包含两列的表,然后使用SQL * Loader将文件加载到该表中。然后,使用单个查询,您可以提取所有需要的内容。与加载程序不同,外部表和UTL_FILE需要目录权限。如果可以轻松获得它,那就去吧。
@KaushikNayak他非常清楚自己的方式效率低下,这就是为什么他首先在这里提出问题。您只需省略前两个短语即可给出相同的值。
@Busybee:我发布了一个答案,可能是您正在寻找的东西。
@VinkoVrsalovic:您怎么知道它的"他"? 。此外,我不知道在Java标记下是否很少出现这样的问题,但是在Oracle中,SQL * Loader之类的东西是人们希望OP意识到的基本问题。我们需要连接一百万次的逻辑并没有给我留下深刻的印象。因此,问题是,为什么要使用它。
@KaushikNayak我不知道他们的性别,以他为默认。无论如何,我们都曾经是新手,面对新手实际上没有什么价值,他们可能很了解新手,他们很可能知道这一点以及寻求帮助的好处。我们需要更加欢迎,特别是关于网站上的新人。我就是这么说。如果您被不了解基本知识的人所冒犯,请跳过它!您不了解他们的情况,例如,这个人可能根本没有DBA或程序员的职责。
@VinkoVrsalovic:我通常想结束徒劳的论点,就是这样。但是,在结束之前,我只能说,如果我不欢迎新手,我什至不会发表评论或免费提供所有解决方案。
@kaushiknayak免费做事与欢迎无关。
@KaushikNayak对不起,我没有在原始问题中提及更多细节。我从事shellscript工作的原因是,最初的要求是大约1000条记录,并且从开发到生产之后,记录开始于100000条。创建临时表不是一开始就给我的选择,因为它具有还要经过一组额外的批准,这需要时间。
似乎最好的方法应该是通过创建登台表..因此,我将尝试这种方法。如果没有得到批准,我可以采用Vinko Vrsalovics的方法。感谢您的建议..!
Busybee:我认为您应该真正说服批准者/管理层使它为您工作,因为我认为,由于模块所有者之间的不合作,不应在效率和性能上做出妥协。您可能最终会找到解决方法,这些解决方法将来必定会引起麻烦。祝一切顺利!
以下是一种简单的解决方案,它可以确保不会出现超时,甚至您也不需要扫描数百万条记录以进行10万次。
一次设置:
创建临时临时表:
create table a_staging_table(
testvalue1 varchar2(255),
testvalue2 varchar2(255),
testval3 varchar2(255),
testval4 varchar2(255),
testval5 varchar2(255)
);
----重复过程
将您的" CSV / TEXT"数据加载到登台表中:
some_file_name.ctl:该文件包含以下加载数据命令。
load data
INFILE 'home/sample/file.csv'
INTO TABLE a_staging_table
APPEND FIELDS TERMINATED BY '|'
(testvalue1,testvalue2,testval3,testval4,testval5);
现在,运行SQL加载程序以将数据加载到暂存表形式。
sqlldr userid=dbUserName/dbUserPassword control=some_file_name.ctl log=some_file_name.log
您的数据已加载到登台表中。现在,将登台表和your_original_table结合起来以标识不存在的记录。
第一种方式:
使用SQL * PLUS从以下SQL假脱机输出:
select s.testval3,testval5
from (select distinct testval3,testval5
from a_staging_table) s
where not exists
(select 1
from your_original_table
where mycolumn1=s.testval3
and mycolumn2=s.testval5);
第二种方式:
Begin
for x in (
select s.testval3,testval5
from (select distinct testval3,testval5
from a_staging_table) s
where not exists
(select 1
from your_original_table
where mycolumn1=s.testval3
and mycolumn2=s.testval5)
) loop
DBMS_OUTPUT.put_line('testval3: '||x.testval3 || ' ------ '||'testval5: '||x.testval5);
--write all these values into another file saying that these are not matching values, using UTL_FILE.
--Then finally truncate the table"a_staging_table"
--so that this data will not available next time, and next time again process will run with different file
end loop;
无需再次建议循环。 OP可以将输出后台处理到csv。另外,group by可以替换为distinct
@KaushikNayak:感谢您指出,我用DISTINCT替换了GROUP BY
别客气。但是,我主要关心的是loop,这不是必需的。如果OP可以使用sqlloader,那么他们也可以使用shell脚本中的sqlplus命令行来简单地假脱机输出。
好的,我还将对此进行更新。
@KKK,感谢您的详细解释。抱歉,我没有首先提到这一点,testval5值将是一些用逗号分隔的值。例如,home / sample / file | testvalue1 | testvalue2 | testval3 | testval4 | test1,test2
例如,home / sample / file | testvalue1 | testvalue2 | testval3 | testval4 | test1,test2,test3。 test1,test 2,test 3的值是varchar2,我需要对照查询中提到的mycolumn2来检查它们。 original_table中的mycolumn2始终具有单个值,如test1或test2。有没有一种方法可以将值传递到登台表,例如将testvalue1,testvalue2,tesval3,testval4,test1作为1行,将testvalue1,testvalue2,tesval3,testval4,test2作为另一行,等等?
some_file_name.ctl中的@KKK,有没有办法从UNIX作业中单独传递文件名。每天会有一个文件名不同的文件,例如,以下内容位于文件ABCD_xx_xxdatetimestamp HDR 1 home / sample / file | testvalue1 | testvalue2 | testval3 | testval4 | test1,test2 TRL中。第二天,我们得到了另一个文件ABCD_x1x2_x3x4datetimestamp
对于动态文件名:stackoverflow.com/questions/27250002/
对于您在"注释"中提到的第一种情况,将其用作:将" and and mycolumn2 = s.testval5"都替换为" and mycolumn2 in"(选择regexp_substr(s.testval5,[^,] +,1,level )通过regexp_substr(s.testval5,[^,] +,1,level)的双连接不为空"
如果信息对您有用,则您可以接受答案并支持:)
一种快速的方法是在脚本开始时从表中收集所有可用的testval3和testval5组合,将它们存储在哈希表或类似的结构中,以便在读取每一行时都可以轻松地查询本地内存数据结构。
当然,它将使用更多的内存,但是它将运行单个验证查询并多次加速程序。
要运行的查询为select distinct mycolumn,mycolumn2 from mytable或等效查询。
看到
如何(或可以)在多列上选择DISTINCT?和
ORACLE Select Distinct返回许多列,其中
因此,总而言之,我提出的机制是:
运行查询以从表中选择所有不同的testval3和testval5对
创建一个哈希表和存储在其上的特定对结构。例如,在Java中,您可以使用Java值对集合? (元组?),然后,如果列的类型是字符串,则使用类似
HashMap, boolean> pairMap
确保实现hashCode和equals方法,就像在其他示例答案中一样,这样您就可以正确地将其用作地图上的键(如果使用Java或类似方法)
将查询结果存储在哈希表中,将testval3和testval5对作为表上的键,将true作为值(对结果集进行迭代的方法留给读者练习):
pairMap.put(new Pair(testval3,testval5),true)
逐行读取文件
在Foreach行中,查找哈希表上是否存在一对testval3和testval5,如果不存在则将其输出到CSV。为此,您只需查询地图并检查是否为空(HashMap中的键存在检查)
例如:
if (pairMap.get(new Pair(testval3,testval5)) == null) {
//output to CSV
}
最后,@ Kaushik Nayak所说的@Vivek暗示着另一种选择是,使用其数据加载工具将文件加载到Oracle上,然后对不存在的值运行单个查询。
谢谢你的建议。如果第一种方法行不通,将尝试此操作。