【问题标题】:only taking certain values from a list in perl仅从 perl 中的列表中获取某些值
【发布时间】:2015-10-28 18:16:46
【问题描述】:

首先我将描述我所拥有的,然后是问题。

我有一个这样结构的文本文件

----------- Start of file-----
<!-->
name,name2,ignore,name4,jojobjim,name3,name6,name9,pop
-->
<csv counter="1">
1,2,3,1,6,8,2,8,2,
2,6,5,1,5,8,7,7,9,
1,4,3,1,2,8,9,3,4,
4,1,6,1,5,6,5,2,9
</csv>
-------- END OF FILE-----------

我还有一个带有映射的 perl 程序:

 my %column_mapping = (
"name" => 'name',
"name1" => 'name_1',
"name2" => 'name_2',
"name3" => 'name_3',
"name4" => 'name_4',
"name5" => 'name_5',
"name6" => 'name_6',
"name7" => 'name_7',
"name9" => 'name_9',
)

我的动态插入语句(假设我正确连接到数据库,并且标题是我的标题名称数组,例如 test1、test2 等)

my $sql = sprintf 'INSERT INTO tablename ( %s ) VALUES ( %s )',
    join( ',', map { $column_mapping{$_} } @headers ),
    join( ',', ('?') x scalar @headers ); 

my $sth = $dbh->prepare($sql);

现在我实际遇到的问题是: 我需要一种方法来只对标题和地图中的值进行插入。 在作为示例给出的数据文件中,有几个名称不在地图中,有没有办法可以忽略它们以及在 csv 部分中与它们关联的数字?

基本上是做一个子集csv,把它变成:

name,name2,name4,name3,name6,name9,
 1,2,1,8,2,8,
 2,6,1,8,7,7,
 1,4,1,8,9,3,
 4,1,1,6,5,2,

这样我的插入语句只会在地图中插入那些。数据文件总是不同的,而且顺序也不一样,地图中会出现未知的数量。

这是执行此操作的理想方法,因为此脚本将遍历数千个文件,并且每个文件都位于 csv 的数百万行和数百列之后。

它只是一个正在读取的文本文件,而不是 csv,不确定 csv 库是否可以在这种情况下工作。

【问题讨论】:

  • @ThisSuitIsBlack 不太确定这只是用于标题,但是否确定要在 csv 部分跳过哪些数据?我不相信它确实如此
  • 啊,我明白你在问什么。你是对的,它没有。
  • @ThisSuitIsBlackNot 在他更新后我意识到这不会解决它。我认为这是我将遇到的最后一个与 perl 相关的问题。这个脚本对我来说相当复杂,因为我昨天还不知道 perl,哈哈。所以不幸的是,在一些地方被难住了......
  • 你问了一个新问题是对的,我很抱歉没有仔细阅读。这是一个相当复杂的任务,在 Perl 学习了一天之后就必须完成!
  • 您在使用未转义的@headers 时有潜在的SQL 注入攻击。它们至少应该通过DBI-&gt;quote_identifier 运行。最好使用SQL::Abstract 来构造您的SQL 语句或ORM 或类似DBIx::Class

标签: perl csv dictionary subset sql-insert


【解决方案1】:

您通常会将一组有效索引放在一个列表中,然后使用array slices

@valid = grep { defined($column_mapping{ $headers[$_] }) } 0 .. $#headers;

...

my $sql = sprintf 'INSERT INTO tablename ( %s ) VALUES ( %s )',
  join( ',', map { $column_mapping{$_} } @headers[@valid] ),
  join( ',', ('?') x scalar @valid);
my $sth = $dbh->prepare($sql);

...

my @row = split /,/, <INPUT>; 
$sth->execute( @row[@valid] );

...

【讨论】:

  • 说到切片,你可以说@column_mappings{@headers[@valid]}而不是map { $column_mappings{$_} } @headers[@valid]
  • 您可以编辑最后两行并发表评论吗?抱歉,昨天才学习 perl,有点困惑最后两行如何确定要抓取或跳过的 csv 数据:S
  • 它正在将整行数据读入数组@row(例如,来自OP的"1,2,3,1,6,8,2,8,2,")。它将@row 的有效部分(对应于具有有效标题的列)传递给$sql-&gt;execute()
  • 任何想法,当我尝试使用此代码时,我得到“无法找到通过包执行的对象方法”INSERT INTO(然后是我的插入语句的其余部分)。我错过了准备声明还是什么?
  • @Lain - 已编辑。像在 OP 中一样使用 $sth 语句对象
【解决方案2】:

因为这是四个不同的问题,所以我将对广泛的问题采取更高层次的方法,并将编程细节留给你(或者你可以就细节提出新的问题)。

我会尽快更改数据格式。将 CSV 列混合到 XML 文件中既奇怪又低效,我相信您知道。对批量数据使用 CSV 文件。对复杂的元数据使用 XML 文件。

标题是 XML 注释更糟糕,现在您正在解析 cmets; cmets 应该被忽略。如果您必须保留混合 XML/CSV 格式,请将标头放入适当的 XML 标记中。否则使用 XML 有什么意义?

由于您要解析一个大文件,因此请使用 XML SAX 解析器。与在执行任何操作之前必须解析整个文档的更传统的 DOM 解析器不同,SAX 解析器将在读取文件时对其进行处理。这将节省大量内存。我将 SAX 处理留作练习,从 XML::SAX::Intro 开始。

在 SAX 解析器中,从 &lt;csv&gt; 中提取数据并在其上使用 CSV 解析器。 Text::CSV_XS 是个不错的选择。它效率高,解决了解析 CSV 数据时可能遇到的所有问题。

当您最终将其归结为 Text::CSV_XS 对象时,在循环中调用 getline_hr 以获取行作为哈希值,应用您的映射,然后插入到您的数据库中。 @mob's solution is fine,但我会使用 SQL::Abstract 来生成 SQL,而不是手动生成。这将防止 SQL 注入攻击以及更普通的事情,例如包含 SQL 元字符和保留字的标头。

将解析数据的处理与数据的解析分开是很重要的。我很确定可怕的数据格式会改变,无论是变坏还是变好,你不想将代码与它绑定。

【讨论】:

  • 如果我能改变这些文件的给出方式,我会心跳加速。我们公司唯一的其他程序员编写了要这样存储的文件。他的文件存在的问题比我发布的要多得多。我不能将标题放入标签中,不幸的是,我只是按原样获得文件,编写了自己的迷你解析器来确定哪个注释块是标题,这并不有趣。数据格式不会变,4年了;(
  • @Lain 如果是更改旧格式的技术问题,您能否制作两份数据文件?一个是现有解析器的旧版本,一个是新的、健全的格式?然后,您可以逐步将解析器更改为更有效的格式。或者这是一个其他程序员产生废话并且不会改变的社会问题?
  • 它是程序员生产的,不会改变。他的代码存在比解析 cmets 更多的问题。他也是专业程序员,我是大学二年级暑期学生:D 有这么多愚蠢的代码要处理。感谢您提供详细的答案,不过今天会尝试暴民的答案,看看我是否可以让它像那样工作。
猜你喜欢
  • 2021-02-23
  • 1970-01-01
  • 2013-01-21
  • 2023-02-09
  • 2016-01-14
  • 1970-01-01
  • 1970-01-01
  • 2019-04-18
  • 1970-01-01
相关资源
最近更新 更多