PostgreSQL
 sql >> Baza danych >  >> RDS >> PostgreSQL

Użyj binarnej tabeli COPY FROM z psycopg2

Oto binarny odpowiednik COPY FROM dla Pythona 3:

from io import BytesIO
from struct import pack
import psycopg2

# Two rows of data; "id" is not in the upstream data source
# Columns: node, ts, val1, val2
data = [(23253, 342, -15.336734, 2494627.949375),
        (23256, 348, 43.23524, 2494827.949375)]

conn = psycopg2.connect("dbname=mydb user=postgres")
curs = conn.cursor()

# Determine starting value for sequence
curs.execute("SELECT nextval('num_data_id_seq')")
id_seq = curs.fetchone()[0]

# Make a binary file object for COPY FROM
cpy = BytesIO()
# 11-byte signature, no flags, no header extension
cpy.write(pack('!11sii', b'PGCOPY\n\377\r\n\0', 0, 0))

# Columns: id, node, ts, val1, val2
# Zip: (column position, format, size)
row_format = list(zip(range(-1, 4),
                      ('i', 'i', 'h', 'f', 'd'),
                      ( 4,   4,   2,   4,   8 )))
for row in data:
    # Number of columns/fields (always 5)
    cpy.write(pack('!h', 5))
    for col, fmt, size in row_format:
        value = (id_seq if col == -1 else row[col])
        cpy.write(pack('!i' + fmt, size, value))
    id_seq += 1  # manually increment sequence outside of database

# File trailer
cpy.write(pack('!h', -1))

# Copy data to database
cpy.seek(0)
curs.copy_expert("COPY num_data FROM STDIN WITH BINARY", cpy)

# Update sequence on database
curs.execute("SELECT setval('num_data_id_seq', %s, false)", (id_seq,))
conn.commit()

Aktualizacja

Przepisałem powyższe podejście do pisania plików do KOPIOWANIA. Moje dane w Pythonie znajdują się w tablicach NumPy, więc ich użycie ma sens. Oto kilka przykładowych data z 1 mln wierszy, 7 kolumn:

import psycopg2
import numpy as np
from struct import pack
from io import BytesIO
from datetime import datetime

conn = psycopg2.connect("dbname=mydb user=postgres")
curs = conn.cursor()

# NumPy record array
shape = (7, 2000, 500)
print('Generating data with %i rows, %i columns' % (shape[1]*shape[2], shape[0]))

dtype = ([('id', 'i4'), ('node', 'i4'), ('ts', 'i2')] +
         [('s' + str(x), 'f4') for x in range(shape[0])])
data = np.empty(shape[1]*shape[2], dtype)
data['id'] = np.arange(shape[1]*shape[2]) + 1
data['node'] = np.tile(np.arange(shape[1]) + 1, shape[2])
data['ts'] = np.repeat(np.arange(shape[2]) + 1, shape[1])
data['s0'] = np.random.rand(shape[1]*shape[2]) * 100
prv = 's0'
for nxt in data.dtype.names[4:]:
    data[nxt] = data[prv] + np.random.rand(shape[1]*shape[2]) * 10
    prv = nxt

W mojej bazie danych mam dwie tabele, które wyglądają następująco:

CREATE TABLE num_data_binary
(
  id integer PRIMARY KEY,
  node integer NOT NULL,
  ts smallint NOT NULL,
  s0 real,
  s1 real,
  s2 real,
  s3 real,
  s4 real,
  s5 real,
  s6 real
) WITH (OIDS=FALSE);

i inną podobną tabelę o nazwie num_data_text .

Oto kilka prostych funkcji pomocniczych do przygotowania danych do KOPIOWANIA (zarówno w formacie tekstowym, jak i binarnym) przy użyciu informacji z tablicy rekordów NumPy:

def prepare_text(dat):
    cpy = BytesIO()
    for row in dat:
        cpy.write('\t'.join([repr(x) for x in row]) + '\n')
    return(cpy)

def prepare_binary(dat):
    pgcopy_dtype = [('num_fields','>i2')]
    for field, dtype in dat.dtype.descr:
        pgcopy_dtype += [(field + '_length', '>i4'),
                         (field, dtype.replace('<', '>'))]
    pgcopy = np.empty(dat.shape, pgcopy_dtype)
    pgcopy['num_fields'] = len(dat.dtype)
    for i in range(len(dat.dtype)):
        field = dat.dtype.names[i]
        pgcopy[field + '_length'] = dat.dtype[i].alignment
        pgcopy[field] = dat[field]
    cpy = BytesIO()
    cpy.write(pack('!11sii', b'PGCOPY\n\377\r\n\0', 0, 0))
    cpy.write(pgcopy.tostring())  # all rows
    cpy.write(pack('!h', -1))  # file trailer
    return(cpy)

W ten sposób używam funkcji pomocniczych do porównania dwóch metod formatu COPY:

def time_pgcopy(dat, table, binary):
    print('Processing copy object for ' + table)
    tstart = datetime.now()
    if binary:
        cpy = prepare_binary(dat)
    else:  # text
        cpy = prepare_text(dat)
    tendw = datetime.now()
    print('Copy object prepared in ' + str(tendw - tstart) + '; ' +
          str(cpy.tell()) + ' bytes; transfering to database')
    cpy.seek(0)
    if binary:
        curs.copy_expert('COPY ' + table + ' FROM STDIN WITH BINARY', cpy)
    else:  # text
        curs.copy_from(cpy, table)
    conn.commit()
    tend = datetime.now()
    print('Database copy time: ' + str(tend - tendw))
    print('        Total time: ' + str(tend - tstart))
    return

time_pgcopy(data, 'num_data_text', binary=False)
time_pgcopy(data, 'num_data_binary', binary=True)

Oto dane wyjściowe z dwóch ostatnich time_pgcopy polecenia:

Processing copy object for num_data_text
Copy object prepared in 0:01:15.288695; 84355016 bytes; transfering to database
Database copy time: 0:00:37.929166
        Total time: 0:01:53.217861
Processing copy object for num_data_binary
Copy object prepared in 0:00:01.296143; 80000021 bytes; transfering to database
Database copy time: 0:00:23.325952
        Total time: 0:00:24.622095

Tak więc zarówno kroki NumPy → plik, jak i plik → baza danych są znacznie szybsze przy podejściu binarnym. Oczywistą różnicą jest sposób, w jaki Python przygotowuje plik COPY, który jest naprawdę wolny dla tekstu. Ogólnie rzecz biorąc, format binarny ładuje się do bazy danych w 2/3 przypadków jako format tekstowy dla tego schematu.

Na koniec porównałem wartości w obu tabelach w bazie danych, aby sprawdzić, czy liczby się różnią. Około 1,46% wierszy ma różne wartości dla kolumny s0 , a ten ułamek wzrasta do 6,17% dla s6 (prawdopodobnie związane z metodą losową, której użyłem). Niezerowe różnice bezwzględne między wszystkimi 70 M 32-bitowymi wartościami zmiennoprzecinkowymi mieszczą się w zakresie od 9.3132257e-010 do 7.6293945e-006. Te niewielkie różnice między metodami ładowania tekstowego i binarnego wynikają z utraty precyzji konwersji float → text → float wymaganych dla metody formatu tekstowego.




  1. Database
  2.   
  3. Mysql
  4.   
  5. Oracle
  6.   
  7. Sqlserver
  8.   
  9. PostgreSQL
  10.   
  11. Access
  12.   
  13. SQLite
  14.   
  15. MariaDB
  1. SSL dla połączenia PostgreSQL nodejs

  2. BŁĄD:nie można załadować biblioteki „/opt/PostgreSQL/9.0/lib/postgresql/plperl.so”:libperl.so:

  3. Wykonywanie sekwencji i seriali w Postgres-XL

  4. Klauzula CHECK dla aktualizowalnych widoków

  5. Postgresql wymusza unikalną dwukierunkową kombinację kolumn