【问题标题】:How to sort huge CSV file?如何对巨大的 CSV 文件进行排序?
【发布时间】:2021-02-15 00:48:28
【问题描述】:

我需要使用 Powershell 或 Perl(公司请求)对一些巨大的(2GB 或更多)CSV 文件进行排序。 我需要从任何列中对它们进行排序,具体取决于文件。

对于某些人来说,我的 CSV 文件看起来像这样,使用 双引号

Column1;Column2;Column3;Column4
1234;1234;ABCD;"1234;ABCD"
5678;5678;ABCD;"5678;ABCD"
9012;5678;ABCD;"9012;ABCD"
...

在 Powershell 中,我已经测试了解决方案 Import-CSV,但我遇到了 OutOfMemory Exception 问题。 我还尝试使用 OleDb Connection 使用以下代码将我的 CSV 文件加载到 SQL 表中:

$provider = (New-Object System.Data.OleDb.OleDbEnumerator).GetElements() | Where-Object { $_.SOURCES_NAME -like "Microsoft.ACE.OLEDB.*" }

if ($provider -is [system.array]) { $provider = $provider[0].SOURCES_NAME } else {  $provider = $provider.SOURCES_NAME }

$csv = "PathToCSV\file.csv"

$firstRowColumnNames = "Yes"

$connstring = "Provider=$provider;Data Source=$(Split-Path $csv);Extended Properties='text;HDR=$firstRowColumnNames;';"

$tablename = (Split-Path $csv -leaf).Replace(".","#")

$sql = "SELECT * from [$tablename] ORDER BY Column3"

# Setup connection and command
$conn = New-Object System.Data.OleDb.OleDbconnection
$conn.ConnectionString = $connstring
$conn.Open()
$cmd = New-Object System.Data.OleDB.OleDBCommand
$cmd.Connection = $conn
$cmd.CommandText = $sql

$cmd.ExecuteReader()

# Clean up
$cmd.dispose 
$conn.dispose

但这会给我返回错误: Exception calling "ExecuteReader" with "1" argument(s): "No value given for one or more required parameters." 我不明白为什么。我试过修改代码,SQL代码,还是不行。

我认为这是做到这一点的最佳解决方案,但我暂时无法让它发挥作用,我对所有其他可行的解决方案持开放态度......

我是 Perl 的初学者,所以我只是想了解它的工作原理以及如何使用它,但暂时没有编写任何代码。

编辑

我刚刚测试了您提出的所有解决方案,非常感谢您的帮助。

两种解决方案均无效,因为您告诉我使用的模块(Text::CSV、File::Sort 或 Data::Dumper)未安装在我使用的 Perl 版本中,我无法安装他们(公司限制......)。

我尝试的是对列进行简单排序,而不考虑 双引号 问题:

use CGI qw(:standard);
use strict;
use warnings;

my $file = 'path\to\my\file.csv';

open (my $csv, '<', $file) || die "cant open";
foreach (<$csv>) {
   chomp;
   my @fields = split(/\;/);
}

@sorted = sort { $a->[1] cmp $b->[1] } @fields;

我在想这应该可行,在我的数组中排序@sorted 我在第二列的数据,但它不起作用,我不明白为什么......

【问题讨论】:

  • @A Le Drian 推荐在 Perl 中读取 CSV 文件的模块是 Text::CSV_XS。读取 CSV 数据后,将其存储在具有唯一值作为键的哈希中,然后使用 sort 函数轻松打印。
  • @vkk05 读取CSV数据会不会有内存问题?
  • 它是用python编写的,但我会先从csvkit尝试csvsort
  • 如果内存有问题,您可以将整个 CSV 文件加载到 SQLite 中,然后编写一个生成排序输出的查询。引号并不重要。正确实现的消费者会在需要的地方用引号和不带引号来理解它。
  • 您考虑过使用 Excel 吗?您似乎在 Windows 上,这可能是最好的选择。

标签: powershell csv perl sorting


【解决方案1】:

使用 *NIX sort(或您正在使用的操作系统中的等效项)和分隔符选项(例如,-t';' - 确保引用分号)对文件进行排序。如果公司要求您使用 Perl,请将系统调用包装到 Perl 中的sort,如下所示:

system "sort -t';' [options] in_file > out_file" and die "cannot sort: $?"

例子:

按第 1 列数字排序:

sort -k1,1g -t';' in_file.txt > out_file.txt

请注意,需要( head -n1 in_file.txt ; tail -n+2 in_file.txt | sort ... ) 才能将标题保持在顶部并仅对数据行进行排序。

按第 1 列排序,数字降序:

( head -n1 in_file.txt ; tail -n+2 in_file.txt | sort -k1,1gr -t';' ) > out_file.txt

按第 4 列按 ASCII 顺序排序,然后按第 1 列按数字降序排序:

( head -n1 in_file.txt ; tail -n+2 in_file.txt | sort -k4,4 -k1,1gr -t';' ) > out_file.txt

【讨论】:

  • *NIX core sort 通常采用-t 选项作为分隔符,那么为什么要先转换呢?
  • @simbabque 感谢您建议使用-t 选项作为分隔符,它允许按原样使用文件。
【解决方案2】:

可以使用 DBD::CSV,在这种情况下,这样的事情可能会起作用:

use Data::Dumper;
    $Data::Dumper::Deepcopy=1;
    $Data::Dumper::Indent=1;
    $Data::Dumper::Sortkeys=1;
use DBI;
use Getopt::Long::Descriptive ('describe_options');
use Text::CSV_XS ('csv');
use Try::Tiny;
use 5.01800;
use warnings;

try {
    push @ARGV,'--help'
        unless (@ARGV);
    my ($opts,$usage)=describe_options(
            'my-program %o <some-arg>',
            ,['field|f=s'     ,'the order by field']
            ,['table|t=s'     ,'The table (file) to be sorted']
            ,['new|n=s'       ,'The sorted table (file)']
            ,[]
            ,['verbose|v'      ,'print extra stuff'              ,{ default => !!0 }]
            ,['help'           ,'print usage message and exit'   ,{ shortcircuit => !1 }]
            );

    if ($opts->help()) { # MAN! MAN!
        say <<"_HELP_";
        @{[$usage->text]}

_HELP_
        exit;
        }
    else { # No MAN required.
        };

    my $dbh=DBI->connect("dbi:CSV:",undef,undef,{
        f_ext        => ".csv/r",
        csv_sep_char => ";",
        RaiseError   => 1,
        }) or die "Cannot connect: $DBI::errstr";

    my $sth=$dbh->prepare(
        "select * from @{[$opts->table()]} order by @{[$opts->field()]}"
        );

    # New table
    my $csv=Text::CSV_XS->new({ sep_char => ";" });
    open my $fh,">:encoding(utf8)","@{[$opts->new()]}.csv"
        or die "@{[$opts->new()]}.csv: $!";

    # fields
    $sth->execute();
    my $fields_aref=$sth->{NAME};
    $csv->say($fh,$fields_aref);

    # the sorted rows
    my $max_rows=5_000;
    while (my $aref=$sth->fetchall_arrayref(undef,$max_rows)) {
        $csv->say($fh,$_)
            for (@$aref);
        };
    close $fh
        or die "@{[$opts->new()]}.csv: $!";
    $dbh->disconnect();
    }
catch {
    Carp::confess $_;
    };
__END__

(在窗口下)调用它

perl CSV_01.t -t data -f "column2 DESC" -n newest

不带参数调用获得帮助或

    my-program [-fntv] [long options...] <some-arg>
    -f STR --field STR  the order by field
    -t STR --table STR  The table (file) to be sorted
    -n STR --new STR    The sorted table (file)

    -v --verbose        print extra stuff
    --help              print usage message and exit

(遗憾的是,冗长并没有做任何额外的事情。)

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2013-11-09
    • 2011-11-28
    • 2015-09-03
    • 2019-09-06
    • 2012-02-08
    • 2017-06-23
    • 1970-01-01
    相关资源
    最近更新 更多