如何从 CSV 为 PostgreSQL 副本生成模式

Posted

技术标签:

【中文标题】如何从 CSV 为 PostgreSQL 副本生成模式【英文标题】:How to generate a schema from a CSV for a PostgreSQL Copy 【发布时间】:2016-05-16 13:38:31 【问题描述】:

给定一个包含几十列或更多列的 CSV,如何创建可用于 PostgreSQL 中的 CREATE TABLE SQL 表达式以与 COPY 工具一起使用的“模式”?

我看到了很多关于 COPY 工具的示例和基本的 CREATE TABLE 表达式,但是对于手动创建模式的列数可能过高的情况,没有任何详细说明。

【问题讨论】:

【参考方案1】:

如果 CSV 不是太大并且在您的本地计算机上可用,那么csvkit 是最简单的解决方案。它还包含许多其他用于处理 CSV 的实用程序,因此一般来说要知道 usefull tool。

在 shell 中最简单的输入:

$ csvsql myfile.csv

将打印出所需的CREATE TABLE SQL 命令,可以使用输出重定向将其保存到文件中。

如果您还提供连接字符串csvsql 将创建表并一次性上传文件:

$ csvsql --db "$MY_DB_URI" --insert myfile.csv

还有一些选项可以指定您正在使用的 SQL 和 CSV 的风格。它们记录在内置帮助中:

$ csvsql -h
usage: csvsql [-h] [-d DELIMITER] [-t] [-q QUOTECHAR] [-u 0,1,2,3] [-b]
              [-p ESCAPECHAR] [-z MAXFIELDSIZE] [-e ENCODING] [-S] [-H] [-v]
              [--zero] [-y SNIFFLIMIT]
              [-i access,sybase,sqlite,informix,firebird,mysql,oracle,maxdb,postgresql,mssql]
              [--db CONNECTION_STRING] [--query QUERY] [--insert]
              [--tables TABLE_NAMES] [--no-constraints] [--no-create]
              [--blanks] [--no-inference] [--db-schema DB_SCHEMA]
              [FILE [FILE ...]]

Generate SQL statements for one or more CSV files, create execute those
statements directly on a database, and execute one or more SQL queries.
positional arguments:
  FILE                  The CSV file(s) to operate on. If omitted, will accept
                        input on STDIN.

optional arguments:
  -h, --help            show this help message and exit
  -d DELIMITER, --delimiter DELIMITER
                        Delimiting character of the input CSV file.
  -t, --tabs            Specifies that the input CSV file is delimited with
                        tabs. Overrides "-d".
  -q QUOTECHAR, --quotechar QUOTECHAR
                        Character used to quote strings in the input CSV file.
  -u 0,1,2,3, --quoting 0,1,2,3
                        Quoting style used in the input CSV file. 0 = Quote
                        Minimal, 1 = Quote All, 2 = Quote Non-numeric, 3 =
                        Quote None.
  -b, --doublequote     Whether or not double quotes are doubled in the input
                        CSV file.
  -p ESCAPECHAR, --escapechar ESCAPECHAR
                        Character used to escape the delimiter if --quoting 3
                        ("Quote None") is specified and to escape the
                        QUOTECHAR if --doublequote is not specified.
  -z MAXFIELDSIZE, --maxfieldsize MAXFIELDSIZE
                        Maximum length of a single field in the input CSV
                        file.
  -e ENCODING, --encoding ENCODING
                        Specify the encoding the input CSV file.
  -S, --skipinitialspace
                        Ignore whitespace immediately following the delimiter.
  -H, --no-header-row   Specifies that the input CSV file has no header row.
                        Will create default headers.
  -v, --verbose         Print detailed tracebacks when errors occur.
  --zero                When interpreting or displaying column numbers, use
                        zero-based numbering instead of the default 1-based
                        numbering.
  -y SNIFFLIMIT, --snifflimit SNIFFLIMIT
                        Limit CSV dialect sniffing to the specified number of
                        bytes. Specify "0" to disable sniffing entirely.
  -i access,sybase,sqlite,informix,firebird,mysql,oracle,maxdb,postgresql,mssql, --dialect access,sybase,sqlite,informix,firebird,mysql,oracle,maxdb,postgresql,mssql
                        Dialect of SQL to generate. Only valid when --db is
                        not specified.
  --db CONNECTION_STRING
                        If present, a sqlalchemy connection string to use to
                        directly execute generated SQL on a database.
  --query QUERY         Execute one or more SQL queries delimited by ";" and
                        output the result of the last query as CSV.
  --insert              In addition to creating the table, also insert the
                        data into the table. Only valid when --db is
                        specified.
  --tables TABLE_NAMES  Specify one or more names for the tables to be
                        created. If omitted, the filename (minus extension) or
                        "stdin" will be used.
  --no-constraints      Generate a schema without length limits or null
                        checks. Useful when sampling big tables.
  --no-create           Skip creating a table. Only valid when --insert is
                        specified.
  --blanks              Do not coerce empty strings to NULL values.
  --no-inference        Disable type inference when parsing the input.
  --db-schema DB_SCHEMA
                        Optional name of database schema to create table(s)
                        in.

其他一些工具也可以进行架构推断,包括:

Apache Spark 熊猫(Python) Blaze (Python) read.csv + R 中你最喜欢的 db 包

其中每一个都具有将 CSV(和其他格式)读入通常称为 DataFrame 或类似的表格数据结构的功能,从而推断过程中的列类型。然后,他们有其他命令来写出等效的 SQL 模式或将 DataFrame 直接上传到指定的数据库中。工具的选择将取决于数据量、存储方式、CSV 的特性、目标数据库以及您喜欢使用的语言。

【讨论】:

您能否详细说明创建该 SQL 架构和/或将带有推断架构的 csv 上传到数据库的工作流程?这就是我想要弄清楚的真正的肉和土豆......非常感谢!!! @DPSSpatial 我已经添加了使用 csvkit 执行此操作的说明,这应该适用于大多数情况。 是的。这太棒了-再次感谢您添加此答案! CSVKIT 中有很多很棒的东西!【参考方案2】:

基本上,您应该使用现成的工具或使用 python、ruby 或您选择的语言在数据库之外准备数据(包括其结构)。 但是,在缺乏此类机会的情况下,您可以使用 plpgsql 做很多事情。

创建带有文本列的表格

csv 格式的文件不包含有关列类型、主键或外键等的任何信息。 您可以相对轻松地创建包含文本列的表并将数据复制到其中。 之后,您应该手动更改列类型并添加约束。

create or replace function import_csv(csv_file text, table_name text)
returns void language plpgsql as $$
begin
    create temp table import (line text) on commit drop;
    execute format('copy import from %L', csv_file);

    execute format('create table %I (%s);', 
        table_name, concat(replace(line, ',', ' text, '), ' text'))
    from import limit 1;

    execute format('copy %I from %L (format csv, header)', table_name, csv_file);
end $$;

文件c:\data\test.csv中的示例数据:

id,a_text,a_date,a_timestamp,an_array
1,str 1,2016-08-01,2016-08-01 10:10:10,"1,2"
2,str 2,2016-08-02,2016-08-02 10:10:10,"1,2,3"
3,str 3,2016-08-03,2016-08-03 10:10:10,"1,2,3,4"

进口:

select import_csv('c:\data\test.csv', 'new_table');

select * from new_table;

 id | a_text |   a_date   |     a_timestamp     | an_array  
----+--------+------------+---------------------+-----------
 1  | str 1  | 2016-08-01 | 2016-08-01 10:10:10 | 1,2
 2  | str 2  | 2016-08-02 | 2016-08-02 10:10:10 | 1,2,3
 3  | str 3  | 2016-08-03 | 2016-08-03 10:10:10 | 1,2,3,4
(3 rows)

大型 csv 文件

上述函数两次导入数据(到临时表和目标表)。 对于大文件,这可能会造成严重的时间损失和服务器上不必要的负载。 一种解决方案是将 csv 文件拆分为两个文件,一个带有标题,一个带有数据。 那么函数应该是这样的:

create or replace function import_csv(header_file text, data_file text, table_name text)
returns void language plpgsql as $$
begin
    create temp table import (line text) on commit drop;
    execute format('copy import from %L', header_file);

    execute format('create table %I (%s);', 
        table_name, concat(replace(line, ',', ' text, '), ' text'))
    from import;

    execute format('copy %I from %L (format csv)', table_name, data_file);
end $$;

更改列类型

您可以尝试根据内容自动更改列类型。 如果您正在处理简单类型并且文件中的数据始终保持特定格式,您就可以成功。但是,一般来说,这是一项复杂的任务,下面列出的功能应仅作为示例考虑。

根据其内容确定列类型(编辑函数以添加所需的转换):

create or replace function column_type(val text)
returns text language sql as $$
    select 
        case 
            when val ~ '^[\+-]0,1\d+$' then 'integer'
            when val ~ '^[\+-]0,1\d*\.\d+$' then 'numeric'
            when val ~ '^\d\d\d\d-\d\d-\d\d$' then 'date'
            when val ~ '^\d\d\d\d-\d\d-\d\d \d\d:\d\d:\d\d$' then 'timestamp'
        end
$$;

使用上述函数改变列类型:

create or replace function alter_column_types(table_name text)
returns void language plpgsql as $$
declare
    rec record;
    qry text;
begin
    for rec in
        execute format(
            'select key, column_type(value) ctype
            from (
                select row_to_json(t) a_row 
                from %I t 
                limit 1
            ) s, json_each_text (a_row)',
            table_name)
    loop
        if rec.ctype is not null then
            qry:= format(
                '%salter table %I alter %I type %s using %s::%s;', 
                qry, table_name, rec.key, rec.ctype, rec.key, rec.ctype);
        end if;
    end loop;
    execute(qry);
end $$;

用途:

select alter_column_types('new_table');

\d new_table

               Table "public.new_table"
   Column    |            Type             | Modifiers 
-------------+-----------------------------+-----------
 id          | integer                     | 
 a_text      | text                        | 
 a_date      | date                        | 
 a_timestamp | timestamp without time zone | 
 an_array    | text                        |

(好吧,正确识别数组类型是相当复杂的)

【讨论】:

以上是关于如何从 CSV 为 PostgreSQL 副本生成模式的主要内容,如果未能解决你的问题,请参考以下文章

如何在python flask app中将数据从postgresql渲染到csv?

如何在 Ruby 中创建 CSV 文件的某些列的副本,其中一列中有不同的数据?

如何在 Datagrip 中使用 PostgreSQL 查询导出附加 CSV 文件?

如何使用 CSV 文件中的标题从 CSV 文件复制到 PostgreSQL 表?

如何将表数据从 PostgreSQL (pgAdmin) 导出到 CSV 文件?

如何在 gorm postgresql 中定义只读副本