Подтвердить что ты не робот

Как сгенерировать схему из CSV для копии PostgreSQL

Учитывая CSV с несколькими десятками или более столбцами, как создать "схему", которая может использоваться в выражении CREATE TABLE SQL в PostgreSQL для использования с инструментом COPY?

Я вижу множество примеров для инструмента COPY и основных выражений CREATE TABLE, но в деталях о случаях, когда у вас есть потенциально запретительное количество столбцов для ручного создания схемы, нет ничего подробного.

4b9b3361

Ответ 1

Если CSV не слишком большой и доступен на вашей локальной машине, то csvkit является самым простым решением. Он также содержит ряд других утилит для работы с CSV, поэтому это полезный инструмент, чтобы знать в целом.

При простейшем вводе в оболочку:

$ 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
  • Pandas (Python)
  • Blaze (Python)
  • read.csv + ваш любимый пакет db в R

Каждый из них имеет функциональные возможности для чтения CSV (и других форматов) в табличную структуру данных, обычно называемую DataFrame или аналогичную, выводящей типы столбцов в процессе. Затем у них есть другие команды, чтобы либо выписать эквивалентную схему SQL, либо загрузить DataFrame непосредственно в указанную базу данных. Выбор инструмента будет зависеть от объема данных, их сохранения, особенностей вашего CSV, целевой базы данных и языка, на котором вы предпочитаете работать.

Ответ 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                        |

(ну, правильное распознавание типов массивов довольно сложно)