Python SQLAlchemy: ошибка целостности Ограничение UNIQUE не выполнено, несмотря на отсутствие дубликатовPython

Программы на Python
Ответить
Anonymous
 Python SQLAlchemy: ошибка целостности Ограничение UNIQUE не выполнено, несмотря на отсутствие дубликатов

Сообщение Anonymous »

Резюме
Я пытаюсь сохранить некоторые данные, которые у меня есть в формате CSV, в базе данных SQLite, используя SQLAlchemy в Jupyter Notebook. При попытке очистить или объединить данные сеанса для соединительных таблиц я получаю ошибку IntegrityError, сообщающую, что существует дублированная строка. Я не верю, что это правда, поскольку я проверил источник данных и не нашел дубликатов, а также проверил каждую строку перед их добавлением и также не нашел дубликатов. Таким образом, я не понимаю, почему происходит эта ошибка.
Проблема
Во-первых, CSV импортируются в фреймы данных, например:

Код: Выделить всё

recipe_df = pd.read_csv("csv/recipes.csv").replace({np.nan: None})
material_df = pd.read_csv("csv/materials.csv").replace({np.nan: None})
crystal_df = pd.read_csv("csv/crystals.csv").replace({np.nan: None})
и классы таблиц ORM выглядят так:

Код: Выделить всё

class Recipe(Base):
__tablename__ = "recipe"

id = Column(Integer, primary_key=True, autoincrement=True)
name = Column(String)

# ...

# Relationships
materials = relationship("Material", secondary="recipe_to_material", back_populates="recipes")
crystals = relationship("Crystal", secondary="recipe_to_crystal", back_populates="recipes")

class Material(Base):
__tablename__ = "material"

id = Column(Integer, primary_key=True, autoincrement=True)
name = Column(String)
icon = Column(String)
is_recipe = Column(Boolean)

# Relationships
recipes = relationship("Recipe", secondary="recipe_to_material", back_populates="materials")

class Crystal(Base):
__tablename__ = "crystal"

id = Column(Integer, primary_key=True, autoincrement=True)
name = Column(String)
icon = Column(String)

# Relationships
recipes = relationship("Recipe", secondary="recipe_to_crystal", back_populates="crystals")

class RecipeToMaterial(Base):
__tablename__ = "recipe_to_material"

recipe_id = Column(Integer, ForeignKey("recipe.id"), primary_key=True)
material_id = Column(Integer, ForeignKey("material.id"), primary_key=True)
amount = Column(Integer)

class RecipeToCrystal(Base):
__tablename__ = "recipe_to_crystal"

recipe_id = Column(Integer, ForeignKey("recipe.id"), primary_key=True)
crystal_id = Column(Integer, ForeignKey("crystal.id"), primary_key=True)
amount = Column(Integer)

class MaterialHasRecipe(Base):
__tablename__ = "material_has_recipe"

material_id = Column(Integer, ForeignKey("material.id"), primary_key=True)
recipe_id = Column(Integer, ForeignKey("recipe.id"), primary_key=True)
Экспорт в таблицы рецептов, материалов и кристаллов не вызывает проблем, однако возникают проблемы при попытке сделать то же самое для соединительных таблиц. По сути, рецепт_df содержит как данные, необходимые для таблицы рецептов, так и названия требуемых материалов и кристаллов. Строки таблицы соединений должны быть построены путем использования идентификатора рецепта и идентификатора материала/кристалла, который соответствует имени в каждом столбце этого рецепта, например:

Код: Выделить всё

def add_recipe_crystals():
recipe_ids:list[int] = []
crystal_ids:list[int] = []
amounts:list[int] = []

# Get all recipe and crystal ids
for _, row in recipe_df.iterrows():
recipe_id = session.query(Recipe.id).filter(Recipe.name == row["name"]).first()[0]

for i in range(1, 3):
crystal_name = row[f"crys_{i}_name"]
crystal_id = session.query(Crystal.id).filter(Crystal.name == crystal_name).first()
# if crystal_i doesn't exist, just skip this one
if crystal_id is None:
continue

crystal_id = crystal_id[0]

recipe_ids.append(recipe_id)
crystal_ids.append(crystal_id)
amounts.append(int(row[f"crys_{i}_count"]))

recipe_to_crystal_df = pd.DataFrame({
"recipe_id": recipe_ids,
"crystal_id": crystal_ids,
"amount": amounts,
})

# Create RecipeToCrystal instances
for i, row in recipe_to_crystal_df.iterrows():
rec_2_crys = RecipeToCrystal(
recipe_id = row["recipe_id"],
crystal_id = row["crystal_id"],
amount = row["amount"]
)
session.add(rec_2_crys)
Похоже, что это работает отлично, пока не придет время попытаться очистить или объединить сеанс. Когда это происходит, я получаю сообщение об ошибке типа:

Код: Выделить всё

IntegrityError: (sqlite3.IntegrityError) UNIQUE constraint failed: recipe_to_crystal.recipe_id, recipe_to_crystal.crystal_id
[SQL: INSERT INTO recipe_to_crystal (recipe_id, crystal_id, amount) VALUES (?, ?, ?)]
Попытки исправления
Подумав, что это может быть проблема с автоматической очисткой, я обернул функции для добавления в соединительные таблицы в функцию add_junction_tables() и обернул их в контекстный менеджер session.no_autoflush, например:

Код: Выделить всё

with session.no_autoflush:
add_junction_tables()
session.commit()
Я также проверил рецепт_df на наличие строк, в которых кристаллы имели одинаковые имена, например:

Код: Выделить всё

count = 0
for i, row in recipe_df.iterrows():
crys_1 = row["crys_1_name"]
crys_2 = row["crys_2_name"]

if crys_2 is None:
continue

if crys_1 in crys_2:
count += 1
print(f"In recipe {row["name"]}, the crystals {crys_1} and {crys_2} are the same")

if count == 0:
print("No duplicates found")
Это напечатало «Дубликаты не найдены».
Я также сделал функцию проверки, например:

Код: Выделить всё

def recipe_crystal_exists(rec_id:int, crys_id:int):
existing_entry = session.query(RecipeToCrystal).filter(
RecipeToCrystal.recipe_id == rec_id,
RecipeToCrystal.crystal_id == crys_id
).first()

if existing_entry:
return True

return False
и добавил проверку в add_recipe_crystals() перед добавлением идентификаторов в соответствующие списки, например:

Код: Выделить всё

if recipe_crystal_exists(recipe_id, crystal_id):
raise ValueError(f"Recipe {recipe_id} and crystal {crystal_id} already exists in RecipeToCrystals")
Эта ошибка никогда не возникала.
Я также заменил session.add() на session.merge() , удалил сеанс, попытался полностью удалить базу данных и повторил попытку.
Вывод
Однако ошибка все еще сохраняется, и я остался в тупике. Самое странное, что add_recipe_materials(), который функционально идентичен и запускается до add_recipe_crystals(), похоже, не имеет этой проблемы. В противном случае я бы предположил, что ошибка будет выдана в первую очередь.
Что вызывает эту ошибку и/или есть ли какой-либо способ узнать, какие конкретно строки являются дубликатами, чтобы лучше диагностировать эту проблему? Любая помощь будет оценена по достоинству.

Подробнее здесь: https://stackoverflow.com/questions/798 ... -duplicate
Ответить

Быстрый ответ

Изменение регистра текста: 
Смайлики
:) :( :oops: :roll: :wink: :muza: :clever: :sorry: :angel: :read: *x)
Ещё смайлики…
   
К этому ответу прикреплено по крайней мере одно вложение.

Если вы не хотите добавлять вложения, оставьте поля пустыми.

Максимально разрешённый размер вложения: 15 МБ.

Вернуться в «Python»