我已经在堆栈溢出和其他地方寻找解决方案,但是找不到处理我必须使用的卷的示例。如果我错过了已在其他地方发布的解决方案,如果有人能指出我正确的方向,我将非常感激。
我尝试从45个不同的Excel工作表中导入时间序列数据(每个Excel工作簿大约5个)。每个工作表都包含商品价格系列,涵盖每种商品的几年每日价格。
原始Excel数据每天有一行可能存在价格,每个商品合约有一列,通常是每月未来合约。因此,每个合约的点数至少为30(但不是更多),而整个表格有几千行和100多列。 我能够构建一个读取数据并使用unpivot的SSIS包,将矩阵转换为基于行的记录,其中列为:
Date, Price, Contract
问题是,在unpivot转换中,我必须手动为每个转换的输入列指定目标列。所以45个工作表,每个包含100多列(有些甚至几百个),用于合同我最终会硬编码'那些会在接下来的几天内手动转换......最重要的是,这并不像我希望的那样灵活/可重复使用。
附加原始数据的示例(整个Cocoa工作表包含9724行和195列)
这里是如何配置另一种商品的非搬运工。目的地栏目'必须逐行手动填写。
我希望我错过了unpivot配置中的正确步骤,以使这些列动态化。理想情况下,SSIS解决方案可以在以后再次使用格式相同的Excel工作簿。它不需要在服务器上运行它,因为它不是经常发生的事情,而是每年最多一次或两次。所以我可以轻松地从VS中手动启动它。
我试图帮助一位学术研究人员,否则他会花大量时间在Excel中手动清理和分析数据。
答案 0 :(得分:0)
我会这样做是一系列嵌套循环:
循环1 ,遍历文件夹中的所有文件。将文件名传递给下一个循环
循环2 打开文件,遍历工作表
在表X中循环3 ,循环通过列(A)> 1
循环4 - 遍历行:
阅读表格X,第B行,
从(第B行,第1列)获取值作为日期,(第1行,第A列)作为产品。 ( B行,A列)作为价格 - 写入目的地。
结束循环4 (可选,在列的末尾记录一些关于行数的元数据)
循环3结束 (可选,记录一些关于工作表中列数的元数据)
循环2结束 (可选,记录一些关于文件X中页数的元数据)
循环1结束 (强烈建议 - 记录一些关于文件X的元数据和工作表/行/列的数量 - 您可以稍后测试样本以获得自己的信心)
您可能希望修改其中一个文件的副本,以便测试问题。
表空
无效数据
缺少标题
文字而不是价格
这将提供更多信心并缩短发现新边缘案例时所需的返工。
最终输出表应该是3列中的非规范化数据:
Date,
Product,
Price
修改强> 这是一个链接,显示如何动态循环遍历Excel电子表格(和工作表)的列,以便您可以使用此过程将数据取消转换为规范化形式
答案 1 :(得分:0)
首先,您需要设计和创建将接收数据的表格,并尝试使用一些手动数据输入来检查数据模型。
确保每个电子表格都有足够的标题信息,以了解如何处理这些行。
完成后,我会将工作表保存为带有制表符分隔符的文本文件。
接下来我会在Perl中编写一个加载程序。它首先读取标题行,并确定将行插入数据库的规则。然后每行被转换为数据库中的插入。
以下是我拥有的发票加载程序(所有权利)的示例:
if ($first) {
$obj->_hdr2keys(0); # convert spreadhseet header into a lookup
my $hdr = $obj->_copystruct($obj->{ar}[0]);
my @Hhdr = ('invoice header id');
my @Hcols = ('invhid');
my @Htypes = ('serial');
my @Dhdr = ('invoice detail id');
my @Dcols = ('invdid','invhid');
my @Dtypes = ('serial','integer');
for (my $col=0; $col <= $#{$hdr}; $col++) {
my $colname = lc($obj->_pomp($hdr->[$col]));
if ($colname eq 'invoicenumber') {
push @Hhdr, $hdr->[$col];
push @Hcols, $colname;
push @Htypes, 'char(32)';
}
elsif ($colname eq 'buysell') {
push @Hhdr, $hdr->[$col];
push @Hcols, $colname;
push @Htypes, 'boolean';
}
elsif ($colname eq 'suppliercustomer') {
push @Hhdr, $hdr->[$col];
push @Hcols, $colname;
push @Htypes, 'char(64)';
}
elsif ($colname eq 'date') {
push @Hhdr, 'Transaction Date';
push @Hcols, 'transactiondate';
push @Htypes, 'date';
}
elsif ($colname eq 'article') {
push @Dhdr, 'Article id';
push @Dcols, 'artid';
push @Dtypes, 'integer';
push @Dhdr, 'Article Description';
push @Dcols, 'description';
push @Dtypes, 'char(64)';
}
elsif ($colname eq 'qty') {
push @Dhdr, $hdr->[$col];
push @Dcols, $colname;
push @Dtypes, 'integer';
}
elsif ($colname eq 'priceexclbtw') {
push @Dhdr, $hdr->[$col];
push @Dcols, $colname;
push @Dtypes, 'double precision';
}
elsif ($colname eq 'btw') {
push @Dhdr, $hdr->[$col];
push @Dcols, $colname;
push @Dtypes, 'real';
}
}
$obj->_getset('INVHar',
['invoiceheader',
['PK','invhid'],
['__COLUMNS__'],
\@Hcols,
\@Htypes,
\@Hhdr
]
);
$obj->_getset('INVDar',
['invoicedetail',
['PK','invdid'],
['FK','invhid','invoiceheader','invhid'],
['FK','artid','article','artid'],
['__COLUMNS__'],
\@Dcols,
\@Dtypes,
\@Dhdr
]
);
}
$first = 0;
SALESROW: for (my $i=1; $i <= $#{$obj->{ar}}; $i++) {
my @Hrow = ('');
my @Drow = ('');
my $date = $obj->_selectar('', $i, 'Date');
$date =~ s/\-/\//g;
if ($date) {
$obj->_validCSV('date', $date)
or die "CSV format error date |$date| in file $file";
}
my $invtotal = ($obj->_selectar('', $i, 'Invoice Total incl. BTW'));
my $article = $obj->_selectar('', $i, 'Article');
$date or $article or next SALESROW;
if ($date) {
push @Hrow, $obj->_selectar('', $i, 'Invoice Number');
my $buysell = $obj->_selectar('', $i, 'Buy/Sell');
push @Hrow, ($buysell eq 'S') ? 1 : 0;
push @Hrow, $obj->_selectar('', $i, 'Supplier/Customer');
push @Hrow, $date;
push @{$obj->_getset('INVHar')}, \@Hrow;
$invhid++;
}
push @Drow, $invhid;
if ($article eq 'E0154') {
push @Drow, 1;
}
elsif ($article eq 'C0154') {
push @Drow, 2;
}
elsif ($article eq 'C0500') {
push @Drow, 3;
}
elsif ($article eq 'C2000') {
push @Drow, 4;
}
elsif ($article eq 'C5000') {
push @Drow, 5;
}
else {
die "unrecognised sales article $article\n"
. Dumper($obj->{ar}[$i]);
}
push @Drow, undef; # description is in article table
push @Drow, $obj->_selectar('', $i, 'Qty.');
push @Drow, $obj->_selectar('', $i, 'Price excl. BTW');
push @Drow, $obj->_selectar('', $i, 'BTW %');
push @{$obj->_getset('INVDar')}, \@Drow;
}
在从另一个电子表格加载产品表后,这将为发票创建标题和明细记录。
在上面的例子中,创建了两个数组,即INVHar和INVDar。准备就绪后,调用例程将它们加载到数据库中,如下所示。在下一个代码示例中,将创建表以及行,并且还会更新metadb以加载将来的表并管理现有表的外键。在上一个代码段中创建的数组包含创建表和插入行所需的所有信息。还有一个简单的例程_DBdatacnv,它可以在电子表格中的格式和数据库中所需的格式之间进行转换。例如,电子表格中包含需要在插入前删除的货币符号。
sub _arr2db {
my ($obj) = @_;
my $ar = $obj->_copystruct($obj->_getset('ar'));
my $dbh = $obj->_getset('CDBh');
my $mdbh = $obj->_getset('MDBh');
my $table = shift @$ar;
$mdbh->{AutoCommit} = 0;
$dbh->{AutoCommit} = 0;
my @tables = $mdbh->selectrow_array(
"SELECT id FROM mtables
WHERE name = \'$table\'"
);
my $id = $tables[0] || '';
if ($id) {
$mdbh->do("DELETE FROM mcolumns where tblid=$id");
$mdbh->do("DELETE FROM mtables where id=$id");
}
# process constraints
my %constraint;
while ($#{$ar} >= 0
and $ar->[0][0] ne '__COLUMNS__') {
my $cts = shift @$ar;
my $type = shift @$cts;
if ($type eq 'PK') {
my $pk = shift @$cts;
$constraint{$pk} ||= '';
$constraint{$pk} .= ' PRIMARY KEY';
@$cts and die "unsupported compound key for $table";
}
elsif ($type eq 'FK') {
my ($col, $ft, $fk) = @$cts;
$ft && $fk or die "incomplete FK declaration in CSV for $table";
$constraint{$col} ||= '';
$constraint{$col} .=
sprintf( ' REFERENCES %s(%s)', $ft, $fk );
}
elsif ($type eq 'UNIQUE') {
while (my $uk = shift @$cts) {
$constraint{$uk} ||= '';
$constraint{$uk} .= ' UNIQUE';
}
}
elsif ($type eq 'NOT NULL') {
while (my $nk = shift @$cts) {
$constraint{$nk} ||= '';
$constraint{$nk} .= ' NOT NULL';
}
}
else {
die "unrecognised constraint |$type| for table $table";
}
}
shift @$ar;
unless ($mdbh->do("INSERT INTO mtables (name) values (\'$table\')")) {
warn $mdbh->errstr . ": mtables";
$mdbh->rollback;
die;
}
@tables = $mdbh->selectrow_array(
"SELECT id FROM mtables
WHERE name = \'$table\'"
);
$id = shift @tables;
$dbh->do("DROP TABLE IF EXISTS $table CASCADE")
or die $dbh->errstr;
my $create = "CREATE TABLE $table\n";
my $cols = shift @$ar;
my $types = shift @$ar;
my $desc = shift @$ar;
my $first = 1;
my $last = 0;
for (my $i=0; $i<=$#{$cols}; $i++) {
$last = 1;
if ($first) {
$first = 0;
$create .= "( "
}
else {
$create .= ",\n";
}
$create .= $cols->[$i]
. ' ' . $obj->_DBcnvtype($types->[$i]);
$constraint{$cols->[$i]}
and $create .= ' ' . $constraint{$cols->[$i]};
unless ($mdbh->do("INSERT INTO mcolumns (tblid,name,type,description)
values ($id,\'$cols->[$i]\',\'$types->[$i]\',\'$desc->[$i]\')"))
{
warn $mdbh->errstr;
$mdbh->rollback;
die;
}
}
$last and $create .= ')';
unless ($dbh->do($create)) {
warn $dbh->errstr;
$dbh->rollback;
die;
}
my $count = 0;
while (my $row = shift @$ar) {
$count++;
my $insert = "INSERT INTO $table (";
my $values = 'VALUES (';
my $first = 1;
for (my $i=0; $i<=$#{$cols}; $i++) {
my $colname = $cols->[$i];
unless (defined($constraint{$colname})
and $constraint{$colname} =~ /PRIMARY KEY/) {
if ($first) {
$first = 0;
}
else {
$insert .= ', ';
$values .= ', ';
}
$insert .= $colname;
my $val = $obj->_DBdatacnv('CSV', 'DB',
$types->[$i],$row->[$i]);
if ($val eq '%ABORT') {
$mdbh->rollback;
die;
}
$values .= $val;
}
}
$insert .= ')' . $values . ')';
unless ($dbh->do($insert)) {
warn $dbh->errstr;
warn $insert;
$mdbh->rollback;
die;
}
}
NOINSERT: $mdbh->commit;
$dbh->commit;
# warn "inserted $count rows into $table";
}
更新:确定我将添加从CSV转换为数组的通用例程,以便为系统的所有其他情况准备_arr2db:电子表格首先使用PK FK和其他约束后跟标题进行扩充数据库的列名称,一行数据库类型(名义上,实际在_DBcnvdatatype中处理)然后是一行标记进入元数据库,最后一行标记 COLUMNS 就在行之前要插入的数据。
sub _csv2arr {
my ($obj, $csv ) = @_;
my $ar = [];
my $delim = $obj->_getset('csvdelim') || '\,';
my $basename = basename($csv);
$basename =~ s/\.txt$//;
$ar = [$basename];
open my $fh, $csv
or die "$!: $csv";
while (<$fh>) {
chomp;
my $sa = [];
@$sa = split /$delim/;
push @$ar, $sa;
}
close $fh;
$obj->{ar} = $ar;
}