Create many to many on one table - python

Flask-SQLAlchemy gives an example of how to create a many to many relationship. It is done between two different tables.
Is it possible to create a many to many relationship on the same table? For example a sister can have many sisters, who would also have many sisters. I have tried:
girl_sister_map = db.Table('girl_sister_map',
db.Column('girl_id',
db.Integer,
db.ForeignKey('girl.id')),
db.Column('sister_id',
db.Integer,
db.ForeignKey('girl.id')))
class Girl(db.Model):
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String)
sisters = db.relationship('Girl',
secondary=girl_sister_map,
backref=db.backref('othersisters', lazy='dynamic'))
But when I try to add a sister to a girl I get:
sqlalchemy.exc.AmbiguousForeignKeysError: Could not determine join condition between parent/child tables on relationship Girl.sisters - there are multiple foreign key paths linking the tables via secondary table 'girl_sister_map'. Specify the 'foreign_keys' argument, providing a list of those columns which should be counted as containing a foreign key reference from the secondary table to each of the parent and child tables.
Is this possible? How should I be doing it?

You are trying to build what is called an adjacency list. That is you have a table with foreign key to itself.
In your specific case it is a self referencial many to many relationship.
This is supported in SQLAlchemy as you will discover by following the previous link. The doc contains several examples.
Basically, you will need the primaryjoin and secondaryjoin arguments to establish how you would like to join the table. Straight from the doc:
Base = declarative_base()
node_to_node = Table("node_to_node", Base.metadata,
Column("left_node_id", Integer, ForeignKey("node.id"), primary_key=True),
Column("right_node_id", Integer, ForeignKey("node.id"), primary_key=True)
)
class Node(Base):
__tablename__ = 'node'
id = Column(Integer, primary_key=True)
label = Column(String)
right_nodes = relationship("Node",
secondary=node_to_node,
primaryjoin=id==node_to_node.c.left_node_id,
secondaryjoin=id==node_to_node.c.right_node_id,
backref="left_nodes"
)

Related

SqlAlchemy - How to define a foreign key column using model class instead of physical table name

In brief
I want to use model class to define a foreign-key column.
My google search on this topic is not helpful so I asked here.
In full
Normally we define column which is a foreign key via physical table name e.g. guided here
author_id = db.Column(db.Integer, db.ForeignKey('author.id'))
The phrase ForeignKey('author.id') helps define column author_id as foreign key column - it refers to talble author where author is the table name.
I want to use model class name i.e.
author_id = db.Column(db.Integer, db.ForeignKey(Author.id))
But this code gets error
Could not determine join condition between parent/child tables on relationship XX.YYY - there are no foreign keys linking these tables. Ensure that referencing columns are associated with a ForeignKey or ForeignKeyConstraint, or specify a 'primaryjoin' expression.
How can we get to it?
In brief
From model other than Author
author_id = db.Column(db.Integer, db.ForeignKey(Author.__table__.c.id))
From Author model itself i.e. self-referential - just list the column name
author_id = db.Column(db.Integer, db.ForeignKey(id))
CANNOT use string value
author_id = db.Column(db.Integer, db.ForeignKey('Author.id'))
Full details
ForeignKey accepts column as first argument which can be of type Column or string in format schema_name.table_name.column_name or table_name.column_name. Columns that you define in declarative model turn to InstumentedAttribute objects. That is why db.ForeignKey(Author.id) leads to an error. You can access actual column via __table__ attribute of a model:
author_id = db.Column(db.Integer, db.ForeignKey(Author.__table__.c['id']))
or
author_id = db.Column(db.Integer, db.ForeignKey(Author.__table__.c.id))
If you need to define self-referencing foreign key you can simply pass the name of column. While declaration of a model is not finished yet it still has Column type:
id = db.Column(db.Integer, primary_key=True)
parent_id = db.Column(db.Integer, db.ForeignKey(id))
Note that you CANNOT define foreign key this way:
author_id = db.Column(db.Integer, db.ForeignKey('Author.id'))
You need to specify physical table name, mapping class name won't work for it.

sqlalchemy association table with three foreign keys

I am learning about association objects in sqlalchemy and about table relationships in general. I have an application where users apply tags to articles. To capture this, I have created an association table with three foreign keys. I am beginning to understand that you never do this, as an association can only have a left and a right (ie associate two tables). However, the idea still seems good to me. I would use this association object to look up what articles a user tagged with a given tag. What am I not understanding?
class UsersAppliedTags(alchemyDB.Model):
'''
a association object to handle the tags which a user applies to other people's entries
'''
__tablename__ = 'users_applied_tags'
id = alchemyDB.Column(alchemyDB.Integer, primary_key = True)
tag_id = alchemyDB.Column(alchemyDB.Integer, alchemyDB.ForeignKey('tagTable.id'))
entry_id = alchemyDB.Column(alchemyDB.Integer, alchemyDB.ForeignKey('entries.id'))
user_id = alchemyDB.Column(alchemyDB.Integer, alchemyDB.ForeignKey('users.id'))

How to achieve that every record in two tables have different id?

I have two tables inherited from base table ( SQLALCHEMY models)
class Base(object):
def __tablename__(self):
return self.__name__.lower()
id = Column(Integer, primary_key=True, nullable=False)
utc_time = Column(Integer, default=utc_time(), onupdate=utc_time())
datetime = Column(TIMESTAMP, server_default=func.now(), onupdate=func.current_timestamp())
and inherited tables Person and Data
How to achieve that every Person and Data have different id, every id to be unique for two tables ? ( Person when generate of id to be aware of Data ids and vice versa)
if you're using Postgresql, Firebird, or Oracle, use a sequence that's independent of both tables to generate primary key values. Otherwise, you need to roll some manual process like an "id" table or something like that - this can be tricky to do atomically.
Basically if I were given this problem, I'd ask why exactly would two tables need unique primary key values like that - if the primary key is an autoincrementing integer, that indicates it's meaningless. It's only purpose is to provide a unique key into a single table.

SQLAlchemy one-to-many without the child table having a primary key

Is it possible to create a table without a primary key in SQLAlchemy? The relationship I want to define is as follows:
class TPost(Base):
__tablename__ = "forum_post"
id = Column(Integer, primary_key = True)
topic_id = Column(Integer, ForeignKey("forum_topic.id"))
index = Column(Integer)
page = Column(Integer)
user_id = Column(Integer, ForeignKey("forum_user.id"))
posted_at = Column(DateTime)
post_text = Column(String)
has_quotes = Column(Boolean)
quotes = relationship("TQuote")
class TQuote(Base):
__tablename__ = "forum_quotes"
id = Column(Integer, ForeignKey("forum_post.id"))
is_direct = Column(Boolean)
quoted_text = Column(String)
quoted_id = Column(Integer)
As you can see I don't really need a primary key, and I don't intend to extend the Quote relationship in the future.
My problem specifically is represented by this error message :
sqlalchemy.exc.ArgumentError: Mapper Mapper|TQuote|forum_quotes
could not assemble any primary key columns for mapped table 'forum_quotes'
edit :
The (id,quoted_id) pair is unique, and it present for the majority of the data, however when the quote is not direct(and doesn't have a quoted_id in that case), I inline the quoted text directly into the quote relationship. I could use a dual table approach (where the indrect quotes have a table with a primary key), but I'd really rather implement this as a single one-to-many relationship. I don't want to have to do more than a single join.
edit 2:
I'll number the quotes and use the foreign-key + app generated number as a pkey, still annoying tho. Now to figure out the syntax.
edit 3:
Solved the problem as outlined in edit 2. Quite annoyed with sql alchemy since it has all the information it needs to implement the relatioship even when modelling the data at a high level. I understand the reasons why Sql Alchemy wants to have a primary key (makes the orm easier to implement).
I am beginning to question why I am using Sql Alchemy, without it I could implement one way UPSERT or CREATE_IF_NOT_EXIST asynchronous operations using psycopg2. ORM's really need to catch up.
I am assuming #TokenMacGuy is right, and you really are confusing the notions of PrimaryKey, and a surrogate key. In which case the answer to your question is:
NO, SA does not support tables (and therefore relations to tables) without a primary key
and NO, you do not need to create a surrogate key for each table for the purpose of serving as a primary key. You can define a PK using any combination of columns with are unique.
See the code below for an example:
class TPost(Base):
__tablename__ = 'forum_post'
id = Column(Integer, primary_key = True)
post_text = Column(String)
quotes = relationship("TQuote", backref="post")
class TQuote(Base):
__tablename__ = "forum_quotes"
id = Column(Integer, ForeignKey("forum_post.id"))
is_direct = Column(Boolean)
quoted_text = Column(String)
quoted_id = Column(Integer)
__table_args__ = (PrimaryKeyConstraint(id, quoted_id),)
Add an additional column to give the quotes a index, and then add make a composite key of this new column + the foreign key.

Modeling a database many to many relationship between three tables using sqlalchemy in python

Let's consider the following table models for sqlalchemy in python.
class Worker(Base):
id Column(Integer, primary = True)
name Column(String, nullable = False)
class Project(Base):
id Column(Integer, primary = True)
name Column(String, nullable = False)
class Rating(Base):
id Column(Integer, primary = True)
description Column(String, nullable = False)
Let's consider some restrictions on these tables. (Don't question the sense of this example please, it is the best I came up with to describe what I'd like to learn. :-))
A worker may have multiple projects to work on.
A project may have multiple workers assigned to work on it.
For each tuple (worker, project) you have a distinct rating
I understand, that I have three many to many relationship. Those are:
worker and project
worker and rating
rating and project
I have no problems defining a many to many relationship between two tables. I just would have to add the folowing association table and a realtionship to my models.
worker_project = Table('worker_project', METADATA,
Column('worker_id', Integer, ForeignKey('workers.id')),
Column('project_id', Integer, ForeignKey('projects.id')),
class Worker(Base):
# [...]
papers = relationship('Project',
secondary=worker_project,
backref='workers')
What I do fail in, is to link the table "ratings" like described above. Any help is appreciated.
Make the ratings table something like this.
Rating
---------------------
rating_id (primary key)
worker_id (foreign key - worker(worker_id))
project_id (foreign key - project(project_id))
description
If I'm understanding this correctly that is.

Categories

Resources