J'écris une extension Django-ORM qui tente de mettre en cache des modèles et de différer leur enregistrement jusqu'à la fin de la transaction. Tout est presque terminé, mais je suis tombé sur une difficulté inattendue dans la syntaxe SQL.
Je ne suis pas un gros administrateur de bases de données, mais d'après ce que j'ai compris, les bases de données ne fonctionnent pas efficacement pour de nombreuses petites requêtes. Quelques grandes requêtes sont bien meilleures. Par exemple, il est préférable d’utiliser de gros lots d’insert (par exemple 100 lignes à la fois) au lieu de 100 one-liners.
D'après ce que je peux voir, SQL ne fournit aucune instruction permettant d'effectuer une mise à jour par lots sur une table. Le terme semble être confus alors, je vais expliquer ce que je veux dire par là. J'ai un tableau de données arbitraires, chaque entrée décrivant une seule ligne dans une table. J'aimerais mettre à jour certaines lignes de la table, chacune utilisant les données de l'entrée correspondante dans le tableau. L'idée est très similaire à un insert de lot.
Par exemple: Ma table pourrait avoir deux colonnes "id"
et "some_col"
. Le tableau décrivant les données pour une mise à jour par lot est composé de trois entrées: (1, 'first updated')
, (2, 'second updated')
et (3, 'third updated')
. Avant la mise à jour, la table contient des lignes: (1, 'first')
, (2, 'second')
, (3, 'third')
.
Je suis tombé sur ce post:
ce qui semble faire ce que je veux, mais je ne peux pas vraiment comprendre la syntaxe à la fin.
Je pourrais également supprimer toutes les lignes nécessitant une mise à jour et les réinsérer à l'aide d'une insertion de lot. Toutefois, j'ai du mal à croire que cela fonctionnerait mieux.
Je travaille avec PostgreSQL 8.4, donc certaines procédures stockées sont également possibles ici. Cependant, étant donné que j’ai l’intention d’obtenir le projet en source libre, toute idée plus portable ou tout moyen de faire la même chose sur un SGBDR différent sont les bienvenus.
Question de suivi: Comment faire une instruction "insérer-ou-mettre à jour"/"upsert" par lot?
Résultats de test
J'ai effectué 100x fois 10 opérations d'insertion réparties sur 4 tables différentes (soit 1000 inserts au total). J'ai testé sur Django 1.3 avec un backend PostgreSQL 8.4.
Ce sont les résultats:
Conclusion: exécutez autant d'opérations que possible dans un seul fichier connection.execute (). Django lui-même introduit une surcharge substantielle.
Clause de non-responsabilité: je n'ai introduit aucun index autre que les index de clé primaire par défaut. Les opérations d'insertion pourraient donc s'exécuter plus rapidement à cause de cela.
J'ai utilisé 3 stratégies pour le travail transactionnel par lots:
flush()
sur Hibernate Session
, et non sur la connexion JDBC sous-jacente. Il accomplit la même chose que le traitement par lots JDBC.Par ailleurs, Hibernate prend également en charge une stratégie de traitement par lots lors de la récupération des collections. Si vous annotez une collection avec @BatchSize
, lors de la récupération des associations, Hibernate utilisera IN
au lieu de =
, ce qui entraînera moins d'instructions SELECT
pour charger les collections.
Vous pouvez modifier l'insertion en bloc de trois colonnes par Ketema:
INSERT INTO "table" (col1, col2, col3)
VALUES (11, 12, 13) , (21, 22, 23) , (31, 32, 33);
Il devient:
INSERT INTO "table" (col1, col2, col3)
VALUES (unnest(array[11,21,31]),
unnest(array[12,22,32]),
unnest(array[13,23,33]))
Remplacer les valeurs par des espaces réservés:
INSERT INTO "table" (col1, col2, col3)
VALUES (unnest(?), unnest(?), unnest(?))
Vous devez passer des tableaux ou des listes comme arguments de cette requête. Cela signifie que vous pouvez faire de gros inserts en vrac sans faire de concaténation de ficelle (et de tous ses inconvénients et dangers: injection SQL et citer l'enfer).
PostgreSQL a ajouté l’extension FROM à UPDATE. Vous pouvez l'utiliser de cette façon:
update "table"
set value = data_table.new_value
from
(select unnest(?) as key, unnest(?) as new_value) as data_table
where "table".key = data_table.key;
Il manque une bonne explication dans le manuel, mais il existe un exemple dans la liste de diffusion postgresql-admin . J'ai essayé de développer là-dessus:
create table tmp
(
id serial not null primary key,
name text,
age integer
);
insert into tmp (name,age)
values ('keith', 43),('leslie', 40),('bexley', 19),('casey', 6);
update tmp set age = data_table.age
from
(select unnest(array['keith', 'leslie', 'bexley', 'casey']) as name,
unnest(array[44, 50, 10, 12]) as age) as data_table
where tmp.name = data_table.name;
Il y a aussi autreposts sur StackExchange expliquant UPDATE...FROM..
en utilisant une clause VALUES
au lieu d'une sous-requête. Ils pourraient être plus faciles à lire, mais sont limités à un nombre fixe de lignes.
Les inserts en vrac peuvent être réalisés comme tels:
INSERT INTO "table" ( col1, col2, col3)
VALUES ( 1, 2, 3 ) , ( 3, 4, 5 ) , ( 6, 7, 8 );
Va insérer 3 rangées.
Les mises à jour multiples sont définies par le standard SQL mais ne sont pas implémentées dans PostgreSQL.
Citation:
"Selon le standard, la syntaxe de liste de colonnes doit permettre d'affecter une liste De colonnes à partir d'une seule expression de valeur de ligne, telle que
UPDATE accounts SET (nom du dernier contact, nom du premier contact) = (SELECT nom, prénom, prénom FROM vendeurs WHERE salesmen.id = accounts.sales_id); "
Référence: http://www.postgresql.org/docs/9.0/static/sql-update.html
il est assez rapide de peupler json en recordset (postgresql 9.3+)
big_list_of_tuples = [
(1, "123.45"),
...
(100000, "678.90"),
]
connection.execute("""
UPDATE mytable
SET myvalue = Q.myvalue
FROM (
SELECT (value->>0)::integer AS id, (value->>1)::decimal AS myvalue
FROM json_array_elements(%s)
) Q
WHERE mytable.id = Q.id
""",
[json.dumps(big_list_of_tuples)]
)
Désactivez autocommit et faites juste un commit à la fin. En langage SQL simple, cela signifie qu’il faut lancer BEGIN au début et COMMIT à la fin. Vous auriez besoin de créer une fonction pour effectuer une ascension réelle.