#python #sql #sqlalchemy
#python #sql #sqlalchemy
Вопрос:
Моя идея заключается в следующем:
- Существует основная таблица (
Documents
), которая содержит некоторые тексты, например, сообщения в блоге. Каждый документ имеет уникальный идентификаторtext_id
. - Существует вторичная таблица (
Links
), в которой хранятся уникальные URL-адреса, которые появляются в этих сообщениях. Каждый URL-адрес имеет уникальный идентификаторurl_id
. - Они связаны таблицей ассоциаций (
Association
), которая сопоставляет идентификатор текста с идентификатором домена.
Я хочу иметь возможность получать сообщения, собирать из них URL-адреса, а затем:
- создайте новую запись в
Documents
- если он содержит новые URL-адреса — добавьте их в
Links
документ и свяжите с ним черезAssociation
- если документ содержит уже существующие URL-адреса — создайте только ассоциацию между новым документом и этими.
Для начала я создал три класса, как здесь:
class Association(Base):
__tablename__ = 'association'
text_id = Column('text_id', Integer, ForeignKey('left.text_id'), primary_key=True)
url_id = Column('url_id', Integer, ForeignKey('right.url_id'), primary_key = True)
child = relationship("Links", back_populates='parents')
parent = relationship("Documents", back_populates='children')
class Documents(Base):
__tablename__ = 'left'
text_id = Column(Integer, primary_key=True, unique=True)
text = Column(Text)
children = relationship("Association", back_populates='parent')
class Links(Base):
__tablename__ = 'right'
url_id = Column(Integer, primary_key=True, autoincrement=True, unique=True)
url = Column(Text, unique=True)
parents = relationship('Association', back_populates = 'child')
Base.metadata.create_all(engine)
Затем я пытаюсь загрузить данные:
data = [
{'id':1, 'text':'sometext', 'url':'facebook.com'},
{'id':2, 'text':'sometext', 'url':'twitter.com'},
{'id':3, 'text':'sometext', 'url':'twitter.com'}
]
for row in data:
d = Document(text_id = row['id'])
a = Association()
a.child = Links(url = row['url'])
d.children.append(a)
session.add(d)
session.commit()
Что приводит к ошибке:
Traceback (most recent call last):
File "/home/user/.pyenv/versions/3.7.12/envs/myenv/lib/python3.7/site-packages/IPython/core/interactiveshell.py", line 3444, in run_code
exec(code_obj, self.user_global_ns, self.user_ns)
File "<ipython-input-13-325b1cd57576>", line 5, in <module>
p.children.append(a)
File "/home/user/.pyenv/versions/3.7.12/envs/myenv/lib/python3.7/site-packages/sqlalchemy/util/langhelpers.py", line 1240, in __getattr__
return self._fallback_getattr(key)
File "/home/user/.pyenv/versions/3.7.12/envs/myenv/lib/python3.7/site-packages/sqlalchemy/util/langhelpers.py", line 1214, in _fallback_getattr
raise AttributeError(key)
AttributeError: append
Я действительно не понимаю, почему, поскольку, похоже, я сделал все, как предлагает официальная документация.
С другой стороны, даже если это сработает, я подозреваю, что добавление уже существующего URL-адреса через p.children.append(a)
может привести к ошибке, поскольку он, по сути, попытается создать дубликат и Links
не допускает этого.
Я использую MySQL и MariaDB, если это имеет значение.
Возможно, я выбрал неправильный инструмент для работы — я был бы признателен, если бы вы могли предложить, что может быть альтернативой.
UPD: я не смог вставить, потому что я создал экземпляр базы с automap_base()
помощью вместо declarative_base()
. Однако теперь я могу добавлять повторяющиеся записи, которые действительно являются проблемой:
sqlalchemy.exc.IntegrityError: (pymysql.err.IntegrityError) (1062, "Duplicate entry 'twitter.com' for key 'url'")
[SQL: INSERT INTO `right` (url) VALUES (%(url)s)]
[parameters: {'url': 'twitter.com'}]
(Background on this error at: https://sqlalche.me/e/14/gkpj)
Ответ №1:
Во-первых, будет легче отлаживать, если вы используете правильные доменные имена вместо: right
, left
, child
, children
. Я знаю, что это копия из документов, но документы являются общими, в то время как ваш случай специфичен. Ваш код будет намного более читабельным.
Чтобы избежать дубликатов, вы должны проверить перед вставкой, что запись уже существует ( Documents
есть уникальная text_id
, Links
есть уникальная url
).
for row in data:
d = session.query(Document).filter_by(text_id=row['id']).first()
if not d:
d = Document(text_id=row['id'])
link = session.query(Links).filter_by(url=row['url']).first():
if not link:
link = Links(url=row['url'])
a = Association(child=link)
d.children.append(a)
session.add(d)
session.flush()
session.commit()
Комментарии:
1. Большое спасибо за решение! И да, в следующий раз я уделю больше внимания именованию 🙂