在SQL Server数据库

时间:2016-06-05 21:01:42

标签: sql-server excel ssis time-series

我已经在堆栈溢出和其他地方寻找解决方案,但是找不到处理我必须使用的卷的示例。如果我错过了已在其他地方发布的解决方案,如果有人能指出我正确的方向,我将非常感激。

我尝试从45个不同的Excel工作表中导入时间序列数据(每个Excel工作簿大约5个)。每个工作表都包含商品价格系列,涵盖每种商品的几年每日价格。

原始Excel数据每天有一行可能存在价格,每个商品合约有一列,通常是每月未来合约。因此,每个合约的点数至少为30(但不是更多),而整个表格有几千行和100多列。 我能够构建一个读取数据并使用unpivot的SSIS包,将矩阵转换为基于行的记录,其中列为:

Date, Price, Contract

问题是,在unpivot转换中,我必须手动为每个转换的输入列指定目标列。所以45个工作表,每个包含100多列(有些甚至几百个),用于合同我最终会硬编码'那些会在接下来的几天内手动转换......最重要的是,这并不像我希望的那样灵活/可重复使用。

附加原始数据的示例(整个Cocoa工作表包含9724行和195列)

Example raw data

这里是如何配置另一种商品的非搬运工。目的地栏目'必须逐行手动填写。

enter image description here

我希望我错过了unpivot配置中的正确步骤,以使这些列动态化。理想情况下,SSIS解决方案可以在以后再次使用格式相同的Excel工作簿。它不需要在服务器上运行它,因为它不是经常发生的事情,而是每年最多一次或两次。所以我可以轻松地从VS中手动启动它。

我试图帮助一位学术研究人员,否则他会花大量时间在Excel中手动清理和分析数据。

2 个答案:

答案 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电子表格(和工作表)的列,以便您可以使用此过程将数据取消转换为规范化形式

Looping through Excel columns in SSIS

答案 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;
}