Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Deleting from many-to-many SQL-Alchemy and Postgresql

I'm trying to delete a child object from a many-to-many relationship in sql-alchemy.

I keep getting the following error:

StaleDataError: DELETE statement on table 'headings_locations' expected to delete 1 row(s); Only 2 were matched.

I have looked at a number of the existing stackexchange questions (SQLAlchemy DELETE Error caused by having a both lazy-load AND a dynamic version of the same relationship, SQLAlchemy StaleDataError on deleting items inserted via ORM sqlalchemy.orm.exc.StaleDataError, SQLAlchemy Attempting to Twice Delete Many to Many Secondary Relationship, Delete from Many to Many Relationship in MySQL) regarding this as well as read the documentation and can't figure out why it isn't working.

My code defining the relationships is as follows:

headings_locations = db.Table('headings_locations',
        db.Column('id', db.Integer, primary_key=True),
        db.Column('location_id', db.Integer(), db.ForeignKey('location.id')),
        db.Column('headings_id', db.Integer(), db.ForeignKey('headings.id')))


class Headings(db.Model):
    __tablename__ = "headings"
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(80))
    version = db.Column(db.Integer, default=1)
    special = db.Column(db.Boolean(), default=False)
    content = db.relationship('Content', backref=db.backref('heading'), cascade="all, delete-orphan") 
    created_date = db.Column(db.Date, default=datetime.datetime.utcnow())
    modified_date = db.Column(db.Date, default=datetime.datetime.utcnow(), onupdate=datetime.datetime.utcnow())

    def __init__(self, name):
        self.name = name



class Location(db.Model):

    __tablename__ = "location"
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(80), unique=True)
    account_id = db.Column(db.Integer, db.ForeignKey('account.id')) 
    version = db.Column(db.Integer, default=1)
    created_date = db.Column(db.Date, default=datetime.datetime.utcnow())
    modified_date = db.Column(db.Date, default=datetime.datetime.utcnow())
    location_prefix = db.Column(db.Integer)
    numbers = db.relationship('Numbers', backref=db.backref('location'), cascade="all, delete-orphan") 
    headings = db.relationship('Headings', secondary=headings_locations, 
                            backref=db.backref('locations', lazy='dynamic', cascade="all"))


    def __init__(self, name):
        self.name = name

And my delete code is as follows:

@content_blueprint.route('/delete_content/<int:location_id>/<int:heading_id>')
@login_required
def delete_content(location_id, heading_id):
    import pdb
    pdb.set_trace()
    location = db.session.query(Location).filter_by(id = location_id).first()
    heading = db.session.query(Headings).filter_by(id = heading_id).first()
    location.headings.remove(heading)
    #db.session.delete(heading)
    db.session.commit()
    flash('Data Updated, thank-you')
    return redirect(url_for('content.add_heading', location_id=location_id))

Whichever way i try and remove the child object (db.session.delete(heading) or location.headings.remove(heading) I still get the same error.

Any help is much appreciated.

My database is postgresql.

Edit: My code which adds the relationship:

        new_heading = Headings(form.new_heading.data)
        db.session.add(new_heading)
        location.headings.append(new_heading)
        db.session.commit()
like image 969
rob123 Avatar asked Aug 02 '15 04:08

rob123


People also ask

Can I use SQLAlchemy with PostgreSQL?

PostgreSQL supports sequences, and SQLAlchemy uses these as the default means of creating new primary key values for integer-based primary key columns.

How do I delete multiple rows in a Flask SQLAlchemy?

Delete multiple rows in SQLAlchemyGet the books table from the Metadata object initialized while connecting to the database. Pass the delete query to the execute() function and get all the results using fetchall() function. Use a for loop to iterate through the results.

How do I delete records in Flask SQLAlchemy?

To delete a record by id in Python Flask-SQLAlchemy, we can call the session delete method. to call session. delete to mark obj1 and obj2 to be deleted. And then we call session.

How do you create a many to many relationship in SQLAlchemy?

Python Flask and SQLAlchemy ORM Many to Many relationship between two tables is achieved by adding an association table such that it has two foreign keys - one from each table's primary key.


1 Answers

I would assume that the error message is correct: indeed in your database you have 2 rows which link Location and Heading instances. In this case you should find out where and why did this happen in the first place, and prevent this from happening again

  1. First, to confirm this assumption, you could run the following query against your database:

    q = session.query(
        headings_locations.c.location_id,
        headings_locations.c.heading_id,
        sa.func.count().label("# connections"),
    ).group_by(
        headings_locations.c.location_id,
        headings_locations.c.heading_id,
    ).having(
        sa.func.count() > 1
    )
    
  2. Assuming, the assumption is confirmed, fix it by manually deleting all the duplicates in your database (leaving just one for each).

  3. After that, add a UniqueConstraint to your headings_locations table:

    headings_locations = db.Table('headings_locations',
            db.Column('id', db.Integer, primary_key=True),
            db.Column('location_id', db.Integer(), db.ForeignKey('location.id')),
            db.Column('headings_id', db.Integer(), db.ForeignKey('headings.id')),
            db.UniqueConstraint('location_id', 'headings_id', name='UC_location_id_headings_id'),
    )
    

Note that you need to need to add it to the database, it is not enough to add it to the sqlalchemy model.

Now the code where the duplicates are inserted by mistake will fail with the unique constraint violation exception, and you can fix the root of the problem.

like image 162
van Avatar answered Sep 28 '22 10:09

van