Re: [sqlalchemy] Re: Deletion of a row from an association table

2020-08-15 Thread Simon King
SQLAlchemy normally presents a many-to-many relationship as a list on
both sides. You've got "Machine.children", which is a list of Options,
and "Option.parents", which is a list of Machines.

If you remove one of the options from a machine.children list, you'll
find that SQLAlchemy removes the entry from the association table.
Something like this:

machine.children.remove(option_to_remove)

However, this does have the downside that when you access
"machine.children", SQLAlchemy will load all the Options for that
Machine from the database. This is a waste of effort if you are only
trying to delete one of them. (But if you've got them loaded anyway,
it doesn't matter)

The other option is to delete the row explicitly, something like this:

statement = Machine_Options.delete().where(
Machine_Options.c.machine_FK == machine.machine_ID,
Machine_Options.c.options_FK == option.option_ID
)
session.execute(statement)

But beware that if you do this, any machines or options already loaded
in your session won't be aware that the delete happened. If they had
already loaded their "parents" or "children" relationships, that
cached data will not match what is in the database.

Hope that helps,

Simon

On Wed, Aug 12, 2020 at 3:05 AM William Phillips  wrote:
>
> For the sake of completeness I am including the code to disconnect an option 
> from a machine using only python/SQLite code.
>
> def removeOption(bladeKey,  OptionKey):
>
> """
> DELETE from blade_options
> WHERE blade_FK == ?
>AND options_FK == ?
> """
> import sqlite3
> dbPath = config.database_path
> sqliteConnection = sqlite3.connect(dbPath)
> cursor = sqliteConnection.cursor()
> sql = 'DELETE from blade_options WHERE blades_ID == ? AND options_ID == 
> ?; '
> cursor.execute(sql, (bladeKey,  OptionKey, ))
> sqliteConnection.commit()
> sqliteConnection.close()
> return
>
> --
> 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/45da4231-3550-4f5b-882e-9e61bef86bd5o%40googlegroups.com.

-- 
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/CAFHwexdhhOPgkXi8owvS0t1hXE68zZYf9kam_MOYZ6PpjCe4Ew%40mail.gmail.com.


Re: [sqlalchemy] Flask SQlAlchemy BaseQuery Paginate not working correctly

2020-08-15 Thread Simon King
This is the same problem: you're writing a query that joins 3 tables
together, and then applying a "LIMIT 20" to that query. If you look
carefully at your 20 rows of psql output, I expect you'll see the same
aggregates_id appear more than once. There are less than 20 distinct
Aggregate objects. When SQLAlchemy receives those rows, it skips the
duplicates.

The SQL looks something like this:

SELECT ... FROM aggregates
LEFT JOIN aggregate_blocks ON ...
LEFT JOIN blocks ON ...
LIMIT 20

You need the LIMIT to apply just to the aggregates table, rather than
the joined tables. This is a bit tricky because you want to filter by
a field in one of those joined tables. You could use an EXISTS
function, something like this:

SELECT * FROM aggregates
WHERE EXISTS (
  SELECT 1
  FROM aggregate_blocks
  INNER JOIN blocks ON aggregate_blocks.block_id = block.id
  WHERE aggregate_blocks.aggregate_id = aggregates.id
  AND blocks.is_complete = false
)
LIMIT 20

If you run that in psql, I think you should get 20 *different*
aggregates rows back.

If that works, then to turn it into SQLAlchemy ORM syntax, you should
use Query.exists():

https://docs.sqlalchemy.org/en/13/orm/query.html#sqlalchemy.orm.query.Query.exists

Hope that helps,

Simon

On Fri, Aug 14, 2020 at 3:56 PM Prerna Pandit  wrote:
>
> Hey Simon,
>
> Thanks so much for replying to my question.  I reworked my code to use 
> sqlalchemy ORM and took off flask and paginate so I can narrow down the 
> issue. My models now extend from declarative_base.
>
> engine = 
> create_engine('postgresql://postgres:postgres@localhost:5400/postgres')
> Session = sessionmaker(bind=engine)
>
> Base = declarative_base()
> session = Session()
>
> Models
>
> class Aggregate(Base):
> __tablename__ = 'aggregates'
> id = Column(UUID(as_uuid=True), primary_key=True,
>server_default=text('uuid_generate_v4()'))
> site_id = Column(UUID(as_uuid=True), nullable=True)
> created_at = Column(DateTime, default=sa.func.now())
> created_by = Column(UUID(as_uuid=True), nullable=True)
> updated_at = Column(DateTime, default=sa.func.now(), 
> onupdate=sa.func.now())
> updated_by = Column(UUID(as_uuid=True), nullable=True)
> blocks = relationship('AggregateBlock', cascade='all, delete-orphan',
>   passive_deletes=True, 
> back_populates='aggregate')
>
>
> class Block(Base):
>
> __tablename__ = 'blocks'
> id = Column(UUID(as_uuid=True), primary_key=True,
>server_default=text('uuid_generate_v4()'))
> type = Column(Text, nullable=False)
> heading = Column(Text, nullable=True)
> subheading = Column(Text, nullable=True)
> label = Column(Text, nullable=True)
> is_complete = Column(Boolean, default=False)
> created_at = Column(DateTime, default=sa.func.now())
> created_by = Column(UUID(as_uuid=True), nullable=True)
> updated_at = Column(DateTime, default=sa.func.now(), 
> onupdate=sa.func.now())
> updated_by = Column(UUID(as_uuid=True), nullable=True)
> aggregates = relationship('AggregateBlock', cascade='all, delete-orphan',
>  passive_deletes=True, back_populates='block')
>
>
> class AggregateBlock(Base):
> __tablename__ = 'aggregate_blocks'
> id = Column(UUID(as_uuid=True), primary_key=True,
>server_default=text('uuid_generate_v4()'))
> block_id = Column(UUID(as_uuid=True),
>  ForeignKey('blocks.id', ondelete='CASCADE'), 
> nullable=False, index=True)
> aggregate_id = Column(UUID(as_uuid=True),
>  ForeignKey('aggregates.id', 
> ondelete='RESTRICT'), nullable=False)
> position = Column(Integer, nullable=False)
> block = relationship('Block', back_populates='aggregates')
> aggregate = relationship('Aggregate', back_populates='blocks')
>
>
> Query:
>
> select = session.query(Aggregate).order_by(Aggregate.created_at) \
> .join(AggregateBlock) \
> .join(Block) \
> .filter(Block.is_complete == complete) \
>
> all_results = select.all()
> limit_results = select.limit(20).all()
>
>
> I still get inconsistent results when I apply limit. Like select.all() will 
> return 47 rows but  with limit it'll return anywhere between 11 to 15.  If I 
> take the generated SQL query and run it directly in psql, I get the correct 
> count.
>
> SELECT aggregates.id AS aggregates_id, aggregates.site_id AS 
> aggregates_site_id, aggregates.created_at AS aggregates_created_at, 
> aggregates.created_by AS aggregates_created_by, aggregates.updated_at AS 
> aggregates_updated_at, aggregates.updated_by AS aggregates_updated_by
> FROM aggregates JOIN aggregate_blocks ON aggregates.id = 
> aggregate_blocks.aggregate_id JOIN blocks ON blocks.id = 
> aggregate_blocks.block_id
> WHERE blocks.is_complete = false ORDER BY aggregates.created_at
>
>  LIMIT 20
>
>
>
>
>
> On Friday, August 14, 2020 at 10:08:23 AM UTC-4, Simon King