Mike, thank you very much for this very thorough answer. I now understand 
that my use case cannot be generally supported by SQLAlchemy, given the 
limitations of the backends.

Yet, I still wonder how to efficiently perform inserts of large numbers of 
rows where the server-generated ids are subsequently needed as foreign key 
values. Replacing one multi-row insert by many single-row inserts must 
impose an enormous performance penalty, whereas retrieving the ids via a 
subsequent SELECT would have to rely on the specifics of the inserted data 
(which may not be unique).

On Thursday, May 13, 2021 at 7:29:42 PM UTC-7 Mike Bayer wrote:

> So the use case you are doing there was never something supported, and in 
> particular the value you are getting on 1.3 is not really universally 
> reliable, as it is relying upon cursor.lastrowid that is not defined for 
> multiple-row inserts, even though for InnoDB specifically the behavior is 
> likely deterministic.
>
> Per MySQLs docs at 
> https://dev.mysql.com/doc/refman/8.0/en/information-functions.html#function_last-insert-id,
>  
> "If you insert multiple rows using a single INSERT 
> <https://dev.mysql.com/doc/refman/8.0/en/insert.html> statement, 
> LAST_INSERT_ID() 
> <https://dev.mysql.com/doc/refman/8.0/en/information-functions.html#function_last-insert-id>
>  
> returns the value generated for the *first* inserted row *only*. The 
> reason for this is to make it possible to reproduce easily the same INSERT 
> <https://dev.mysql.com/doc/refman/8.0/en/insert.html> statement against 
> some other server.".   
>
> now can you assume that if you inserted five rows and last_insert_id() 
> gave you the number 1, the rows will be numbered 1, 2, 3, 4, 5?  won't 
> concurrent inserts possibly interfere with this?   the consensus seems to 
> be "the values probably are predictable, for a single server MySQL 
> database, but it's not documented anywhere", see  
> https://stackoverflow.com/questions/6895679/mysqls-auto-increment-behavior-in-a-multiple-row-insert
>  
> .    that MySQL won't document this I would say is pretty telling that this 
> is not a long term / generalized guarantee (like what happens if this is on 
> Galera for example?) even though it might be the current behavior.     
>
> For SQLAlchemy, which gets cursor.lastrowid from other database engines 
> also, it was definitely never assumed that "cursor.lastrowid" is useful for 
> an INSERT statement that has multiple rows, and this has always been an 
> undefined use case.   The spec itself documents this: 
> https://www.python.org/dev/peps/pep-0249/#lastrowid  "The semantics of 
> .lastrowid are undefined in case the last executed statement modified 
> more than one row, e.g. when using INSERT with .executemany()."
>      
> This is also not what we refer to as a "returned primary keys", which 
> usually refers to the RETURNING SQL syntax.  Only MariaDB as of 10.5 
> supports that syntax, which we have not yet added support for - other than 
> that MySQL DBs only give us cursor.lastrowid.
>
> 1.4's architecture is more explicit about how things are handled so when 
> you send an insert().values() with  a list, that's automatically a 
> multi-row insert and we don't collect cursor.lastrowid in this case.  this 
> caused no regressions because cursor.lastrowid was never considered to be 
> defined in this case and no tests have covered it.
>
> if you send just one row at a time, you get the inserted_primary_key back:
>
> with engine.begin() as conn:
>     result = conn.execute(messages.insert().values({'message': 'Hello 
> World'}))
>     first_id = result.inserted_primary_key[0]
>
> to bypass the result and go straight to the pymysql cursor if you'd like 
> to rely upon the driver-specfic behavior, you can get to it as 
> result.context.cursor.lastrowid:
>
> with engine.begin() as conn:
>     result = conn.execute(messages.insert().values(values))
>     first_id = result.context.cursor.lastrowid
>
>
>
>
>
>
>
> On Thu, May 13, 2021, at 8:49 PM, Thorsten von Stein wrote:
>
>
> I use SQLAlchemy with MySQL. In one application, I need to obtain the 
> server-generated primary key values of newly inserted rows by obtaining the 
> value of the first inserted row with the property inserted_primary_key. 
> However, after upgrading to version 1.4.15, this property return only 
> (None,). 
>
> See the following trivial example:
>
> from sqlalchemy import create_engine
> from sqlalchemy import Column, Integer, Text, MetaData, Table
>
> engine = 
> create_engine('mysql+pymysql://testuser:xyz6789@localhost/testdb?charset=utf8')
>
>
> metadata = MetaData()
> messages = Table(
>     'messages', metadata,
>     Column('id', Integer, primary_key=True),
>     Column('message', Text),
> )
>
> # on first run:
> # messages.create(bind=engine)
>
>
> values = [
>     {'message': 'Hello World'},
>     {'message': 'Hallo Welt'},         
>          ]
>
> with engine.begin() as conn:
>     result = conn.execute(messages.insert().values(values))
>     first_id = result.inserted_primary_key[0]
>
>     
> print(f'first_id = {first_id}')
>
>
> Result in SQLAlchemy-1.3.15:
>
> first_id = 1
>
>
> But in SQLAlchemy-1.4.15:
>
> first_id = None
>
> Converting to new-style querying does not help. The new property 
> inserted_primary_key_rows returns just nested tuples of None whenever more 
> than one row is inserted. This is metioned in the documentation for 
> backends that do not support returned primary keys -- but MySQL obviously 
> does!
>
> Any help highly appreciated. Right now I cannot think of any other 
> solution than sticking with 1.3.
>
>
> -- 
> 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+...@googlegroups.com.
> To view this discussion on the web visit 
> https://groups.google.com/d/msgid/sqlalchemy/dd6eb61d-da0a-48d0-88d7-ef0e3ba437d8n%40googlegroups.com
>  
> <https://groups.google.com/d/msgid/sqlalchemy/dd6eb61d-da0a-48d0-88d7-ef0e3ba437d8n%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/a40c392a-0b65-4526-bf21-a343d53ceafcn%40googlegroups.com.

Reply via email to