繁体   English   中英

如何有效地进行具有唯一约束的多对多条目

[英]How to do many-to-many entries with unique constraints efficiently

我想不通。 我正在尝试学习 sqlalchemy,并且在堆栈溢出方面进行了很多搜索。 我需要让这个脚本工作。 类型有问题,我无法弄清楚。

主要问题是不允许将先前添加的类型添加或再次更新到不同的电影。

E4:如何重新定义我的 model 以允许 sqlalchemy 处理唯一约束并且仍然能够插入新标题,将其链接到流派并在流派表中不存在时添加新流派?

from sqlalchemy import exc, orm
from datetime import datetime

from flask import Flask
from flask_sqlalchemy import SQLAlchemy
import pandas as pd


class Config:
    SQLALCHEMY_DATABASE_URI = 'sqlite:///:memory:?charset=utf8'
    SQLALCHEMY_DATABASE_ECHO = False
    SQLALCHEMY_TRACK_MODIFICATIONS = 'False'
    FLASK_ENV = 'test'
    DEBUG = True


db = SQLAlchemy()
conf = Config()


def create_app():
    app = Flask(__name__)
    app.config.from_object(conf)
    db.init_app(app)

    return app


association_table = db.Table('association',
                             db.Column('movies_id', db.Integer,
                                       db.ForeignKey('movies.id')),
                             db.Column('genres_id', db.Integer,
                                       db.ForeignKey('genres.id'))
                             )


class Rating(db.Model):
    __tablename__ = 'ratings'
    id = db.Column(db.Integer, db.ForeignKey("movies.id"), primary_key=True)
    rating = db.Column(db.String(4),
                       index=True, nullable=False)
    movie = db.relationship("Movie", back_populates="rating")

    def __repr__(self):
        return '{}'.format(self.rating)


class Movie(db.Model):
    __tablename__ = 'movies'
    id = db.Column(db.Integer, primary_key=True)
    title = db.Column(db.String(80), index=True, unique=True, nullable=False)
    director = db.Column(db.String(30), primary_key=False,
                         unique=False, nullable=False)
    added = db.Column(db.Date, nullable=False)
    rating = db.relationship("Rating", uselist=False, back_populates="movie")
    genres = db.relationship(
        "Genre", secondary=association_table, backref=db.backref('movies'))

    def __repr__(self):
        return '{}'.format(self.title)


class Genre(db.Model):
    __tablename__ = 'genres'
    id = db.Column(db.Integer, primary_key=True)
    category = db.Column(db.String(80), index=True,
                         unique=True, nullable=False)

    def __repr__(self):
        return '{}'.format(self.category)


def set_commit(set_title, set_director, set_rating, set_genre):
    desired_title = set_title
    desired_director = set_director
    desired_rating = set_rating
    desired_genre = set_genre
    msg = f'status:{desired_title}:{desired_director}:{desired_rating}:{desired_genre}:'

    existing_title = Movie.query.filter_by(title=desired_title).first()
    existing_genre = Genre.query.filter_by(category=desired_genre).first()
    if not existing_title:
        movie = Movie(title=desired_title.lower(),
                  director=desired_director.lower(), added=datetime.today())
        rating = Rating(rating=desired_rating, movie=movie)
        genre = Genre(category=desired_genre.lower())
        db.session.add_all([movie, rating])
        msg += 'added all:'
        if not existing_genre:
            db.session.add(genre)
            msg += 'added:'
        if existing_genre not in movie.genres:
            movie.genres.append(genre)
            msg += f'appended:{genre}:{movie.genres}:'
        try:
            db.session.commit()
            msg += 'committed:'
        except Exception as e:
            print(f"commit | {e}")
            db.session.rollback()
            msg += 'rolled back:'
    return msg


def get_entries():
    query = (
        db.session
        .query(
            Rating.rating,
            Movie.title,
            Movie.director,
            Movie.added,
            db.func.GROUP_CONCAT(Genre.category, ", ").label("genres")
        )
        .select_from(Movie)
        .where(Rating.id == Movie.id)
        .join(Genre, Movie.genres)
        .group_by(
            Rating.rating,
            Movie.title,
            Movie.director,
        )
        .limit(10)
    )
    items = pd.read_sql_query(query.statement, db.engine)
    return items


app = create_app()
context = app.app_context()
context.push()
db.create_all()

print(set_commit("t1", "james cameron", 5.0, "horror"))
print(set_commit("t1", "james cameron", 5.0, "comedy"))
print(set_commit("t2", "james cameron", 5.0, "action"))
print(set_commit("t3", "JOnatHan Mostow", 5.0, "aCtion"))
print(set_commit("t4", "Joseph McGinty Nichol", 5.0, "drama"))
print(set_commit("t5", "Jonathan Mostow", 5.0, "drama"))
print(set_commit("t6", "foo bar", 5.0, "biography"))
print(set_commit("t7", "Jonathan Mostow", 5.0, "documentary"))
print(set_commit("t8", "Jonathan Mostow", 5.0, "drama"))
print(set_commit("t9", "Jonathan Mostow", 5.0, "drama"))
print(set_commit("t10", "Jonathan Mostow", 5.0, "biography"))
print(set_commit("t11", "Jonathan Mostow", 4.9, "documentary"))
print(set_commit("t4", "John Cameron", 5.0, "biography"))
print(set_commit("t5", "Jonathan Mostow", 5.0, "horror"))

print(get_entries())
context.pop()

我的堆栈跟踪。 看来流派有问题。 所以我不知道如何处理唯一约束和多个条目。

status:t1:james cameron:5.0:horror:added all:added:appended:horror:[horror]:committed:
status:t1:james cameron:5.0:comedy:
status:t2:james cameron:5.0:action:added all:added:appended:action:[action]:committed:
commit | (sqlite3.IntegrityError) UNIQUE constraint failed: genres.category
[SQL: INSERT INTO genres (category) VALUES (?)]
[parameters: ('action',)]
(Background on this error at: http://sqlalche.me/e/14/gkpj)
status:t3:JOnatHan Mostow:5.0:aCtion:added all:added:appended:action:[action]:rolled back:
status:t4:Joseph McGinty Nichol:5.0:drama:added all:added:appended:drama:[drama]:committed:
commit | (sqlite3.IntegrityError) UNIQUE constraint failed: genres.category
[SQL: INSERT INTO genres (category) VALUES (?)]
[parameters: ('drama',)]
(Background on this error at: http://sqlalche.me/e/14/gkpj)
status:t5:Jonathan Mostow:5.0:drama:added all:appended:drama:[drama]:rolled back:
status:t6:foo bar:5.0:biography:added all:added:appended:biography:[biography]:committed:
status:t7:Jonathan Mostow:5.0:documentary:added all:added:appended:documentary:[documentary]:committed:
commit | (sqlite3.IntegrityError) UNIQUE constraint failed: genres.category
[SQL: INSERT INTO genres (category) VALUES (?)]
[parameters: ('drama',)]
(Background on this error at: http://sqlalche.me/e/14/gkpj)
status:t8:Jonathan Mostow:5.0:drama:added all:appended:drama:[drama]:rolled back:
commit | (sqlite3.IntegrityError) UNIQUE constraint failed: genres.category
[SQL: INSERT INTO genres (category) VALUES (?)]
[parameters: ('drama',)]
(Background on this error at: http://sqlalche.me/e/14/gkpj)
status:t9:Jonathan Mostow:5.0:drama:added all:appended:drama:[drama]:rolled back:
commit | (sqlite3.IntegrityError) UNIQUE constraint failed: genres.category
[SQL: INSERT INTO genres (category) VALUES (?)]
[parameters: ('biography',)]
(Background on this error at: http://sqlalche.me/e/14/gkpj)
status:t10:Jonathan Mostow:5.0:biography:added all:appended:biography:[biography]:rolled back:
commit | (sqlite3.IntegrityError) UNIQUE constraint failed: genres.category
[SQL: INSERT INTO genres (category) VALUES (?)]
[parameters: ('documentary',)]
(Background on this error at: http://sqlalche.me/e/14/gkpj)
status:t11:Jonathan Mostow:5.0:documentary:added all:appended:documentary:[documentary]:rolled back:
status:t4:John Cameron:5.0:biography:
commit | (sqlite3.IntegrityError) UNIQUE constraint failed: genres.category
[SQL: INSERT INTO genres (category) VALUES (?)]
[parameters: ('horror',)]
(Background on this error at: http://sqlalche.me/e/14/gkpj)
status:t5:Jonathan Mostow:5.0:horror:added all:appended:horror:[horror]:rolled back:
  rating title               director       added       genres
0    5.0    t1          james cameron  2021-05-07       horror
1    5.0    t2          james cameron  2021-05-07       action
2    5.0    t4  joseph mcginty nichol  2021-05-07        drama
3    5.0    t6                foo bar  2021-05-07    biography
4    5.0    t7        jonathan mostow  2021-05-07  documentary

我怎样才能让它工作?

E3:

我试过这个建议

def set_commit(set_title, set_director, set_rating, set_genre):
    desired_title = set_title.lower()
    desired_director = set_director.lower()
    desired_rating = set_rating
    desired_genre = set_genre.lower()
    msg = f'status:{desired_title}:{desired_director}:{desired_rating}:{desired_genre}:'

    existing_title = Movie.query.filter_by(title=desired_title).first()
    existing_genre = Genre.query.filter_by(category=desired_genre).first()
    if not existing_title:
        movie = Movie(title=desired_title,
                      director=desired_director, added=datetime.today())
        rating = Rating(rating=desired_rating, movie=movie)
        genre = Genre(category=desired_genre)
        db.session.add_all([movie, rating])
        msg += 'added all:'
        if not existing_genre:
            db.session.add(genre)
            movie.genres.append(genre)
            msg += 'added:'
        if existing_genre not in movie.genres:
            movie.genres.append(genre)
            msg += f'appended:{genre}:{movie.genres}:'
        try:
            db.session.commit()
            msg += 'committed:'
        except Exception as e:
            print(f"commit | {e}")
            db.session.rollback()
            msg += 'rolled back:'
    return msg

它的堆栈跟踪的一部分:

  rating title               director       added                    genres
0    5.0    t1          james cameron  2021-05-08            horror, horror
1    5.0    t2          james cameron  2021-05-08            action, action
2    5.0    t4  joseph mcginty nichol  2021-05-08              drama, drama
3    5.0    t6                foo bar  2021-05-08      biography, biography
4    5.0    t7        jonathan mostow  2021-05-08  documentary, documentary

我认为 SQLAlchemy 可以自己处理这个问题。 是否有更直观的方式来重写此代码?

这就是您在数据库中查询Genre的方式:

existing_genre = Genre.query.filter_by(category=desired_genre).first()

在您的代码desired_genre提供了一个字符串(这将起作用),在您的最后一个示例中:

set_commit("t4", "Joseph McGinty Nichol", 5.0, ["action", "postapocalyptic"])

你提供了一个列表,这不起作用,这没有意义:

existing_genre = Genre.query.filter_by(category=["action", "postapocalyptic"]).first()

所以你需要代码来检查它是一个列表还是一个字符串——如果它是一个列表,遍历它并触发一个查询来检查每个条目。

问题更新后

所以问题是——如果你在数据库中有一个现有的流派(“动作”),提供另一个:

print(set_commit("t3", "JOnatHan Mostow", 5.0, "aCtion"))

调用,会导致一个独特的错误,在这种情况下,数据库中已经存在action ,但是我们将aCtion为“新”条目。 该错误告诉我们您正在尝试添加另一种action类型,所以我立即想到的是我们正在创建一个与lower()相关的错误并添加条目。 所以让我们来看看:

existing_genre = Genre.query.filter_by(category=desired_genre).first()

在这一点上,我希望None返回desired_genreaCtion ,但我们知道只有action存在——所以没有返回任何内容。 我建议:

existing_genre = Genre.query.filter_by(category=desired_genre.lower()).first()

既然你知道你总是想规范化为小写。 下一个问题是:

if not existing_genre:
    db.session.add(genre)
    msg += 'added:'
if existing_genre not in movie.genres:
    movie.genres.append(genre)
    msg += f'appended:{genre}:{movie.genres}:'

如果我们合法地创建了一个新的流派——比如说noir ,第一个if将是false所以我们创建条目,这是有道理的——但是即使我们创建了条目,第二个if也会是 false 因为existing_genre没有改变。 有几种方法可以解决这个问题,但简单的方法是:

if not existing_genre:
    db.session.add(genre)
    movie.genres.append(genre)
    msg += 'added:'
if existing_genre not in movie.genres:
    movie.genres.append(genre)
    msg += f'appended:{genre}:{movie.genres}:'

如果我们需要添加一个流派,那么它不能已经分配给一部电影——所以让我们就地分配它,当存在现有流派时,我们将留下另一个检查。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM