匹配两个巨大的csv文件之间的公共ID

时间:2015-07-06 11:53:45

标签: bash csv replace awk

我需要比较两个巨大的csv文件和一千个条目,如bellow:

id;val

1;a
2;b 
3;c

Ans第二个文件具有以下结构

id1;entry    
1;002
2;x90 
5;d07

所需的结果是匹配并组合id / id1的相同值的行,并创建第三个csv文件,其中只有匹配的条目显示如下:

idR;valR;entryR
1;a;002
2;b;x90 

为了实现这一点,我可以在不同的数据库表中加载每个文件并执行这样的选择以检索所有匹配的值:

select tb1.id, tb1.val tb2.entry 
  from tb1, tb2
 where tb1.id = tb2.1   

我可以使用这种方法检索所需的所有值。

但是,假设这些文件可以进行排序,并且通过这种方式使用awk可以使用awk来打印具有相同id和id1值的条目的结果。我能做的最好的事情是为每个值创建两个关联数组,并使用awk和sed / cut执行二进制搜索?

可以加载这两个文件并立即合并它们以生成带有结果的最终csv文件吗?

或者我可以使用带有标准库的perl进行此操作吗?

3 个答案:

答案 0 :(得分:5)

使用awk将信息加载到内存中,然后在id匹配时打印该行:

$ awk 'FNR==NR {a[$1]=$2; next} ($1 in a) {print $1, $2, a[$1]}' f2 f1
1 a 002
2 b x90

解释

基本想法是在阅读thingsfile1other_things阅读file2时执行awk 'FNR==NR {things; next} {other_things}' file1 file2

things

在我们的例子中,file2是将file1的内容存储在内存中,将每个id映射到其值。

然后,如果有共同的id,它会通过httpClient.execute(httpPost); 并打印行的内容以及映射的值。

答案 1 :(得分:4)

可以使用标准join实用程序

执行此操作

<强> FILE1.TXT

1 a
2 b
3 c

<强> FILE2.TXT

1 002
2 x90
5 d07

加入示例

join -1 1 -2 1 -o 1.1,1.2,2.2 file1.txt file2.txt

此处join是从file1.field1加入到file2.field2并输出使用-o标志指定的字段

<强>输出

1 a 002
2 b x90

答案 2 :(得分:2)

大小是困难的部分,因为合并您可能需要在整个批次中阅读的文件。

然而,对于一般解决方案perl中的问题:

#!/usr/bin/env perl
use strict;
use warnings;

use Text::CSV;

my %count_of;
my @field_order;

foreach my $file (@ARGV) {
    my $csv = Text::CSV->new( { binary => 1 } );
    open( my $input, "<", $file ) or die $!;
    my $header_row = $csv->getline($input);
    foreach my $header (@$header_row) {
        if ( not $count_of{$header} ) {
            push( @field_order, $header );
        }
        $count_of{$header}++;
    }
}

print "Common headers:\n";
my @common_headers = grep { $count_of{$_} >= @ARGV } keys %count_of;
print join( "\n", @common_headers );

my %lookup_row;
my $key_field;
if (@common_headers) { $key_field = shift @common_headers };

foreach my $file (@ARGV) {
    my $csv = Text::CSV->new( { binary => 1 } );
    open( my $input, "<", $file ) or die $!;
    my @headers = @{ $csv->getline($input) };
    $csv->column_names(@headers);
    while ( my $row_hr = $csv->getline_hr($input) ) {
        my $key = $.;
        if ($key_field) {
            $key = $row_hr->{$key_field};
        }
        $lookup_row{$key}{$file} = $row_hr;
    }
    close($input);
}

my $csv_out = Text::CSV->new( { binary => 1 } );
my $header_row = \@field_order;
$csv_out->print( \*STDOUT, $header_row );
print "\n";

foreach my $key ( sort keys %lookup_row ) {
    my %combined_row;
    foreach my $file ( sort keys %{ $lookup_row{$key} } ) {
        foreach my $header (@field_order) {
            if ( $lookup_row{$key}{$file}{$header} ) {
                if (   not defined $combined_row{$header}
                    or not $combined_row{$header} eq
                    $lookup_row{$key}{$file}{$header} )
                {
                    $combined_row{$header}
                        .= $lookup_row{$key}{$file}{$header};
                }
            }
        }
    }
    my @row = @combined_row{@field_order};
    $csv_out->print( \*STDOUT, \@row );
    print "\n";
}

请注意,Text::CSV可以更改为将输出重定向到文件句柄而不是STDOUT,这可能不是您想要的大文件(或者只知道> output.csv }。)

您还可以通过Text::CSV配置sep_char的分隔符:

my $csv = Text::CSV -> new ( { binary => 1, sep_char => "\t" } ); 

我不清楚你的分隔符是什么,所以假设逗号(正如你引用csv)。

上面的脚本将选择一个公共字段并在其上合并,如果不存在,则为行号。

注意:

此脚本将文件读入内存并将其合并到那里,对公共密钥进行排序和连接。它将根据此输出进行排序。因此,记忆贪婪,但应该只是工作&#39;在很多情况下。只需指定文件名splice.pl file1.csv file2.csv file3.csv

即可

如果这些文件中有一个公共字段,它将按顺序连接这些文件并输出。如果没有,它将使用行号。