As you may have guessed the DEFAULT keyword is used to "even" the size of the tuples in the VALUES clause and explicitly asks the RDBMS to replace itself with the currently defined default value for the corresponding column. The RDBMS in question is Postgres (>=11).
The context is we want to insert, in batches, data that comes for a message queue. Data comes in as (flat) JSON documents, but where some keys may be omitted. We'd like to insert the batches without the need to know the server defaults. We don't use the ORM layer, only the core layer (though I don't think that would make any difference). I attached an example that shows why using the DEFAULT keyword seems useful: it avoids errors or data losses. A short comment explains what's wrong at the top of each case. The example, to be run successfully, assumes a Postgres server is running, accessible through its unix socket, and the current user as access to a default database (generally one which name matches is login name) and can create a table in the public schema. You can change the server address if need be (see l. 48), change the schema (l. 42) or table name (l. 36). The important thing in the previous example was that we have tuples of different sizes: a 3-tuple and a 2-tuple (when DEFAULT is omitted). Something that, if fed as is to your SQL server, it will probably choke on: (1,'one', 'I') and (1000000, 'one million'). If I do what you suggest (omit the roman column) then I cannot pass an explicit value for that column when I have one. A detail I forgot in the previous message is that the `roman` column in the `numbers` table is *not nullable*. CREATE TABLE number ( i INTEGER, letters STRING, roman STRING DEFAULT 'inexpressible' NOT NULL, ) ; I mention this because I found old posts that mention in some case tuples may be complete with NULL(s), but that is not what I observed and would not work anyway in our context. You might argue why not drop the NOT NULL and make NULL equivalent to 'inexpressible'. That would make perfect sense, I a perfect world. But assume the database is ages old, hence has lots of quirks I cannot get rid of (yet). Of course, I may be missing something, probably obvious, that would explain why nobody asked about this before. Hope it is a bit clearer. Regards, Nicolas Le mardi 14 avril 2020 18:35:48 UTC+2, Mike Bayer a écrit : > > > > On Tue, Apr 14, 2020, at 10:41 AM, Nicolas Caniart wrote: > > Hi ! > > I've been looking into the documentation but could not find it. > What is the proper way to insert a DEFAULT keywork in the tuples of the > VALUES clause in an INSERT statement ? As in > > CREATE TABLE number ( > i INTEGER, > letters STRING, > roman STRING DEFAULT '', > ) ; > > INSERT INTO number (c1, c2, c3) VALUES (1, 'one', 'I'), (1000000, 'one > million', DEFAULT) ; -- NOTE THE DEFAULT HERE > > > I'm not familiar with this keyword but to my knowledge there is no need > for it on any backend I'm familiar with, you instead omit the "roman" > column from the insert construct itself by only specifying values for the > "i" and "letters" columns. The "roman" column will not be present and > the SQL side default will fire off. > > If you can share the reason that this special DEFAULT keyword is otherwise > needed as well as what kind of database this is feel free, literal_column() > would be the only way to go. > > > (yes this example is silly) > > The best I came up with, is using the `literal_column('DEFAULT', > type_=...)` construct. > > I wondered if there is a better way, cause I was worried about the keyword > possibly getting quoted at some point, though I am not 100% sure that could > happen. > > Thanks in advance ! > > Regards, > Nicolas. > > > -- > SQLAlchemy - > The Python SQL Toolkit and Object Relational Mapper > > http://www.sqlalchemy.org/ > > To post example code, please provide an MCVE: Minimal, Complete, and > Verifiable Example. See http://stackoverflow.com/help/mcve for a full > description. > --- > You received this message because you are subscribed to the Google Groups > "sqlalchemy" group. > To unsubscribe from this group and stop receiving emails from it, send an > email to sqlal...@googlegroups.com <javascript:>. > To view this discussion on the web visit > https://groups.google.com/d/msgid/sqlalchemy/9c133c97-a5c6-4863-8f40-d8174949f4d7%40googlegroups.com > > <https://groups.google.com/d/msgid/sqlalchemy/9c133c97-a5c6-4863-8f40-d8174949f4d7%40googlegroups.com?utm_medium=email&utm_source=footer> > . > > > -- SQLAlchemy - The Python SQL Toolkit and Object Relational Mapper http://www.sqlalchemy.org/ To post example code, please provide an MCVE: Minimal, Complete, and Verifiable Example. See http://stackoverflow.com/help/mcve for a full description. --- You received this message because you are subscribed to the Google Groups "sqlalchemy" group. To unsubscribe from this group and stop receiving emails from it, send an email to sqlalchemy+unsubscr...@googlegroups.com. To view this discussion on the web visit https://groups.google.com/d/msgid/sqlalchemy/93a9c888-976c-4fcf-a739-dffbf62172bd%40googlegroups.com.
# -*- coding: utf-8; -*- import logging from sqlalchemy import (Column, create_engine, exc, insert, Integer, literal, literal_column, MetaData, String, Table,) def _fix(defaults, *records): for record in records: for k in defaults: record[k] = record.get(k, defaults[k]) print(record) yield record def tabulize(result): print(' id | i | letter | roman') print('-------+-----------+---------------------------+---------') for row in result: print(' {: 5d} | {: 9d} | {:25s} | {:7s}'.format(*row)) logging.basicConfig() logging.getLogger('sqlalchemy.engine').setLevel(logging.INFO) logging.getLogger('sqlalchemy').setLevel(logging.INFO) Numbers = Table('numbers', MetaData(), Column('id', Integer, primary_key=True), Column('i', Integer, nullable=False), Column('letter', String, nullable=False), Column('roman', String, nullable=False, server_default=literal('not set')), schema='public', ) DEFAULTS = { 'roman': literal_column('DEFAULT', type_=Numbers.c.roman), } engine = create_engine('postgresql+psycopg2:///') Numbers.create(engine, checkfirst=True) engine.execute('TRUNCATE TABLE numbers;') # Fails: expects 3-tuples, needs 3 attributes per tuple first only has 2 try: insert_stmt1 = (Numbers .insert([Numbers.c.i, Numbers.c.letter, Numbers.c.roman]) .values([{'i': 1000001, 'letter': 'one million and one'}, {'i': 1, 'letter': 'one', 'roman': 'I'}, ])) except exc.ArgumentError as err1: # Can't mix single-values and multiple values formats in one statement print('1. ', err1) # Fails: (same) expects 3-tuples, needs 3 attributes per tuple first only has 2 try: insert_stmt2 = (Numbers .insert([Numbers.c.i, Numbers.c.letter, Numbers.c.roman]) .values([{'i': 2, 'letter': 'two', 'roman': 'II'}, {'i': 1000002, 'letter': 'one million and 2'}, ])) except exc.ArgumentError as err2: # Can't mix single-values and multiple values formats in one statement print('2. ', err2) # Fails: second "tuple" does not have as many attributes as first one. insert_stmt3 = (Numbers .insert() .values([{'i': 3, 'letter': 'three', 'roman': 'III'}, {'i': 1000003, 'letter': 'one million and 3'}, ])) with engine.begin() as t: try: t.execute(insert_stmt3) except exc.CompileError as err3: print('3. ', err3) # Works as expected insert_stmt4 = (Numbers .insert() .values(list(_fix(DEFAULTS, *[{'i': 1000004, 'letter': 'one million and four'}, {'i': 4, 'letter': 'four', 'roman': 'IV'}, ], )))) with engine.begin() as t: try: t.execute(insert_stmt4) except exc.IntegrityError as err4: print('4. ', err4) # Incorrect: value for the roman (V) is ignored default inserted insert_stmt5 = (Numbers .insert() .values([{'i': 1000005, 'letter': 'one million and five'}, {'i': 5, 'letter': 'five', 'roman': 'V'}, ], )) with engine.begin() as t: try: t.execute(insert_stmt5) except exc.IntegrityError as err5: print('5. ', err5) # Incorrect: value for the roman (VI) is ignored default inserted insert_stmt6 = (insert(Numbers) .values([{'i': 1000006, 'letter': 'one million and six'}, {'i': 6, 'letter': 'six', 'roman': 'VI'}, ], )) with engine.begin() as t: try: t.execute(insert_stmt6) except exc.IntegrityError as err6: print('6. ', err6) tabulize(engine.execute(Numbers.select())) # Numbers.drop(engine) # vim: et:sw=4:syntax=python:ts=4: