【问题标题】:Splitting two large CSV files preserving relations between file A and B across the resulting files拆分两个大型 CSV 文件,在结果文件中保留文件 A 和 B 之间的关系
【发布时间】:2014-08-10 00:19:30
【问题描述】:

我有两个大的 CSV 文件(大约 1GB)。他们都共享彼此之间的关系(ID就像外键一样)。结构简单,逐行但可以出现值字符串中带有换行符的CSV单元格

37373;"SOMETXT-CRCF 或其他换行符-";3838383;"sasa ssss"

一个文件是P文件,另一个是T文件。 T 相当于 P 文件的 70% 大小(P > T)。我必须将它们切割成更小的部分,因为它们对于我必须导入它们的程序来说太大了......我不能简单地使用split -l 100000,因为我将失去必须保留的 ID=ID 关系!关系可以是 1:1、2:3、4:6 或 1:5。所以愚蠢的文件分割是没有选择的,我们必须检查我们创建新文件的地方。这是一个简化 CSV 结构的示例,也是我希望剪切文件的地方(上面的行转到单独的 P|T__00x 文件,我们继续直到 P 或 T 结束)。行在两个文件中都进行了排序,因此无需在整个文件中搜索 ID!

文件“P”(为清晰起见空行):

CSV_FILE_P;HEADER;GOES;HERE
564788402;1;1;"^";"01"
564788402;2;1;"^";"01"
564788402;3;1;"^";"01"

575438286;1;1;"^";"01"
575438286;1;1;"^";"01"
575438286;2;1;"37145859"
575438286;2;1;"37145859"
575438286;3;1;"37145859"
575438286;3;1;"37145859"

575439636;1;1;"^"
575439636;1;1;"^"

# lets say ~100k line limit of file P is somewhere here and no more 575439636 ID lines , so we cut.

575440718;1;1;"^"
575440718;1;1;"^"
575440718;2;1;"10943890"
575440718;2;1;"10943890"
575440718;3;1;"10943890"
575440718;3;1;"10943890"

575441229;1;1;"^";"01"
575441229;1;1;"^";"01"
575441229;2;1;"4146986"
575441229;2;1;"4146986"
575441229;3;1;"4146986"
575441229;3;1;"4146986"

文件 T(为清晰起见空行)

CSV_FILE_T;HEADER;GOES;HERE
564788402;4030000;1;"0204"

575438286;6102000;1;"0408"
575438286;6102000;0;"0408"
575439636;7044010;1;"0408"
575439636;7044010;0;"0408"

# we must cut here since bigger file "P" 100k limit has been reached
# and we end here because 575439636 ID lines are over.

575440718;6063000;1;"0408"
575440718;6063000;0;"0408"

575441229;8001001;0;"0408"
575441229;8001001;1;"0408"

您能否帮助将这两个文件拆分为许多 100 000(左右)行,单独的文件 T_001 和相应的 P_001 文件等等?所以文件部分之间的 ID 匹配。我相信 awk 将是最好的工具,但我在这个领域没有太多经验。最后一件事 - CSV 标头应该保留在每个文件中。 我有强大的 AIX 机器来解决这个问题(linux 也可以,因为 AIX 命令有时是有限的)

【问题讨论】:

  • 为什么功能强大的 AIX 机器会因此而遭到破坏? CSV 显然不是解决方案的正确数据格式。使用数据库
  • 是的,但我不是创建、导入、使用 JOIN 然后再次导出数据库解决方案的数据库管理员。我需要 awk、tail、head 或其他工具解决方案。需要从 CSV 文件导入这些较小文件的程序,这是我的目标 - 获得许多 100 000 行文件并保留关系。
  • man... 我在一家大型 IT 公司工作,我不能自己创建和管理数据库。 DB家伙正在休假,明天必须完成工作。所以 stackoverflow 社区是我最后的希望。
  • 把空行去掉有关系吗?
  • 我们根本不需要then(空行是为了让你第一眼看到关系,我已经输入了)。我们可能在不同的 CSV“单元格”处有一个 CRLF(或其他)换行符,因此在引号之间 - 这有时会在 CSV 中产生一个新的空行......但到目前为止,我只发现一个地方有这样的“坏数据” ”(因为这个文件是一个人名 - 在我们国家的名字中没有白色字符:) ...)。我可以删除这个问题手册。

标签: file csv awk split


【解决方案1】:

您可以使用 awk 解析开始的 ID,然后检查当前 ID 是否与最后一个相同。只有当它不同时,您才允许关闭当前输出文件并打开一个新文件。此时记录 ID 以跟踪下一个文件。您可以在文本文件或内存中跟踪此 ID。我已经在内存中完成了它,但是像这样的大文件可能会遇到麻烦。在内存中跟踪比打开多个文件并从中读取更容易。

那么你只需要区分第一个文件(输出和记录)和第二个文件(输出和使用预先记录的数据)。

代码对字段中是否存在 CRLF 进行了非常强力的检查 - 如果该行不以看起来像 ID 的内容开头,那么它会输出该行并且不对其进行进一步测试。如果 CRLF 后面紧跟一个数字和分号,这就是一个问题!不过这可能不太可能......

运行:gawk -f parser.awk P T

我不保证这会奏效!

BEGIN {
    MAXLINES = 100000
    block = 0
    trackprevious = 0
}


FNR == 1 {
    # First line is CSV header
    csvheader = $0

    if (FILENAME == "-")
    {
        _error = 1
        print "Error: Need filename on command line"
        exit 1
    }

    if (trackprevious)
    {
        _error = 1
        print "Only one file can track another"
        exit 1
    }

    if (block >= 1)
    {
        # New file - track previous output...
        close(outputname)
        Tracking[block] = idval

        print "Output for " FILENAME " tracks previous file"
        trackprevious = 1
    }
    else
    {
        print "Chunking output (" MAXLINES ") for " FILENAME
    }

    linecount = 0
    idval = 0
    block = 1

    outputprefix = FILENAME "_block"
    outputname = sprintf("%s_%03d", outputprefix, block)

    print csvheader > outputname
    next
}


/^[0-9]+;/ {
    linecount++

    newidval = $0
    sub(/;.*$/, "", newidval)
    newidval = newidval + 0     # make a number

    startnewfile = 0

    if (trackprevious && (idval != newidval) && (idval == Tracking[block]))
    {
        startnewfile = 1
    }
    else if (!trackprevious && (idval != newidval) && (linecount > MAXLINES))
    {
        # Last ID value found before new file:
        Tracking[block] = idval
        startnewfile = 1
    }

    if (startnewfile)
    {
        close(outputname)
        block++

        outputname = sprintf("%s_%03d", outputprefix, block)
        print csvheader > outputname
        linecount = 1
    }

    print $0 > outputname
    idval = newidval
    next
}

{
    linecount++
    print $0 > outputname
}

【讨论】:

  • 在第二个 sprintf 中发现一个错误(拼写错误!),但它似乎工作正常!
  • 谢谢,我还没测试。但这看起来像每个文件 hack 的智能 (~100k) 行(不会丢失一个部分文件中的任何记录)但是文件 T 呢?它应该以这样的方式同时拆分,以覆盖 P_block_xxx 文件中的所有 ID=ID 记录(来自 T 的相应 ID 行应放置在 T_block_xxx 以形成可用的文件对)。我们不需要在 T 文件中搜索数据,它是排序的(如示例)。导入程序需要同时导入两个、P 和 T 文件。
  • 是的,我只是在阅读原帖,我明白了。它也不保留标题(很容易)。 P和T匹配吗?即:如果 P 中有 100 *“575438286”,T 中是否也有 100? (编辑:关系可以是 1:1、2:3、4:6 或 1:5。我明白了。更难)
  • 你可以做的是解析P并同时为T生成一个跟踪文件。然后解析T。或者你可以打开P和打开T并同时扫描它们。由于对 ID 进行了排序,这同样容易。我会回来找你的:-)
  • 是的,如果两个文件中有 100 个 ID,简单的 split 就足够了,我不会问任何人如何做到这一点。但是现在我会使用 tail -n 1 和 head -n1 来获取第一个和最后一个 P_block_ ID,然后嗯不知道 grep 来计算大 T 文件中的第一个 ID 出现,然后 grep 来获取最后一个 ID 的最后一次出现,然后 tail + head 结合将想要的部分切割成新的 > T_block_xxx。不是 awk 而是 bash 风格... T 文件看起来像是已排序,因此无需抓取和搜索相应的 ID!只需搜索开头并搜索最后出现的行,然后剪切此部分和中提琴!
猜你喜欢
  • 1970-01-01
  • 2016-09-20
  • 2020-06-22
  • 1970-01-01
  • 1970-01-01
  • 2014-06-06
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多