如何将属性 - 值对列表转换为列为属性的平面表

时间:2013-07-11 17:54:25

标签: sql database perl csv python-3.x

我正在尝试将包含3列(ATTRIBUTE_NAME,ATTRIBUTE_VALUE,ID)的csv文件转换为每行为(ID,Attribute1,Attribute2,Attribute3,....)的平面表。最后提供了这些表格的样本。

Python,Perl或SQL都可以。非常感谢,非常感谢您的时间和精力!

事实上,我的问题与此post非常相似,只是在我的情况下,属性的数量非常大(~300)并且在每个ID之间不一致,因此对每个属性进行硬编码可能不是一个切实可行的解决方案。

对我来说,挑战/困难的部分是:

  • 大约有2.7亿行输入,输入表的总大小约为60 GB。
  • 一些单个值(字符串)在其中包含逗号(,),整个字符串将用双引号(")括起来,以使读者意识到这一点。例如,ID = 53的"JPMORGAN CHASE BANK, NA, TX"
  • ID的属性集不一样。例如,总属性的数量是8,但ID = 53,17和23分别只有7,6和5。 ID = 17没有属性string_countrystring_address,因此请在逗号后输出blank/nothing

输入属性值表如下所示。在此示例输入和输出中,我们有3个ID,其属性数可以不同,具体取决于我们是否可以从服务器获取此类属性。

ATTRIBUTE_NAME,ATTRIBUTE_VALUE,ID
num_integer,100,53
string_country,US (United States),53
string_address,FORT WORTH,53
num_double2,546.0,53
string_acc,My BankAcc,53
string_award,SILVER,53
string_bankname,"JPMORGAN CHASE BANK, NA, TX",53
num_integer,61,17
num_double,34.32,17
num_double2,200.541,17
string_acc,Your BankAcc,17
string_award,GOLD,17
string_bankname,CHASE BANK,17
num_integer,36,23
num_double,78.0,23
string_country,CA (Canada),23
string_address,VAN COUVER,23
string_acc,Her BankAcc,23

输出表应该如下所示。 (列中属性的顺序不固定。可以按字母顺序或按顺序排序。)

ID,num_integer,num_double,string_country,string_address,num_double2,string_acc,string_award,string_bankname
53,100,,US (United States),FORT WORTH,546.0,My BankAcc,SILVER,"JPMORGAN CHASE BANK, NA, TX"
17,61,34.32,,,200.541,Your BankAcc,GOLD,CHASE BANK
23,36,78.0,CA (Canada),VAN COUVER,,Her BankAcc,,

2 个答案:

答案 0 :(得分:1)

这个程序可以按照你的要求进行。它期望输入文件的名称作为命令行上的参数。

更新仔细查看数据,我发现并非所有数据字段都可用于每个ID。如果字段的保存顺序与文件中出现的顺序相同,那么事情会变得更加复杂。

此程序通过扫描文件并将所有数据累积到散列%data来工作。同时,它构建一个哈希%headers,保持每个标题的位置出现在每个ID值的数据中。

扫描文件后,通过查找包含两个标头信息的每对的第一个ID对收集的标头进行排序。整个集合中该对的排序顺序必须与它们在该ID的数据中出现的顺序相同,因此只需使用<=>比较两个位置值即可。

一旦创建了一组已排序的标头,就会转储%data哈希,使用哈希切片访问每个ID的完整值列表。

更新2 现在,我意识到数据的庞大规模,我可以看到我的第二次尝试也存在缺陷,因为它试图将所有信息读入输出前的内存。除非你有一个大约1TB内存的怪物机器,否则这不会起作用!

您可能会从此版本中获得一些里程数。它在文件中扫描两次,第一次读取数据,以便可以创建和排序完整的标题名称集,然后再次读取每个ID的数据并输出。

让我知道它是否不适合你,因为我仍然可以采取一些措施来提高内存效率。

use strict;
use warnings;
use 5.010;

use Text::CSV;
use Fcntl 'SEEK_SET';

my $csv = Text::CSV->new;

open my $fh, '<', $ARGV[0] or die qq{Unable to open "$ARGV[0]" for input: $!};

my %headers = ();
my $last_id;
my $header_num;
my $num_ids;

while (my $row = $csv->getline($fh)) {
  next if $. == 1;

  my ($key, $val, $id) = @$row;

  unless (defined $last_id and $id eq $last_id) {
    ++$num_ids;
    $header_num = 0;
    $last_id = $id;
    print STDERR "Processing ID $id\n";
  }

  $headers{$key}[$num_ids-1] = ++$header_num;
}

sub by_position {
  for my $id (0 .. $num_ids-1) {
    my ($posa, $posb) = map $headers{$_}[$id], our $a, our $b;
    return $posa <=> $posb if $posa and $posb;
  }
  0;
}

my @headers = sort by_position keys %headers;
%headers = ();
print STDERR "List of headers complete\n";

seek $fh, 0, SEEK_SET;
$. = 0;

$csv->combine('ID', @headers);
print $csv->string, "\n";

my %data = ();
$last_id = undef;

while () {
  my $row = $csv->getline($fh);
  next if $. == 1;

  if (not defined $row or defined $last_id and $last_id ne $row->[2]) {
    $csv->combine($last_id, @data{@headers});
    print $csv->string, "\n";
    %data = ();
  }

  last unless defined $row;
  my ($key, $val, $id) = @$row;
  $data{$key} = $val;
  $last_id = $id;
}

<强>输出

ID,num_integer,num_double,string_country,string_address,num_double2,string_acc,string_award,string_bankname
53,100,,"US (United States)","FORT WORTH",546.0,"My BankAcc",SILVER,"JPMORGAN CHASE BANK, NA, TX"
17,61,34.32,,,200.541,"Your BankAcc",GOLD,"CHASE BANK"
23,36,78.0,"CA (Canada)","VAN COUVER",,"Her BankAcc",,

答案 1 :(得分:0)

使用Text::CSV from CPAN

#!/usr/bin/env perl

use strict;
use warnings;

# --------------------------------------

use charnames qw( :full :short   );
use English   qw( -no_match_vars );  # Avoids regex performance penalty

use Text::CSV;

my $col_csv     = Text::CSV->new();
my $id_attr_csv = Text::CSV->new({ eol=>"\n", });

$col_csv->column_names( $col_csv->getline( *DATA ));
while( my $row = $col_csv->getline_hr( *DATA )){

  # do all the keys but skip if ID
  for my $attribute ( keys %$row ){
    next if $attribute eq 'ID';

    $id_attr_csv->print( *STDOUT, [ $attribute, $row->{$attribute}, $row->{ID}, ]);

  }
}

__DATA__
ID,num_integer,num_double,string_country,string_address,num_double2,string_acc,string_award,string_bankname
53,100,,US (United States),FORT WORTH,546.0,My BankAcc,SILVER,"JPMORGAN CHASE BANK, NA, TX"
17,61,34.32,,,200.541,Your BankAcc,GOLD,CHASE BANK
23,36,78.0,CA (Canada),VAN COUVER,,Her BankAcc,,