
[英]How to query many-to-many based on some constraints in flask sqlalchemy?
[英]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_genre
是aCtion
,但我们知道只有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.