I am having an issue with creating a set of relationships among 3 of my tables. When I run the code to create the tables I get a circular dependency error.
I tried fiddling around with use_alter and post_update based on responses to similar posts, but I wasn't able to solve the issue.
Basically a map has a set of locations, a character has a set of maps, but a character is also located on one of those map locations.
In addition, a map can have parent/child relationships with other maps.
class Character(Base):
__tablename__ = 'character'
ID = Column(Integer, primary_key=True)
name = Column(Unicode(255), nullable=False)
classID = Column(Integer, nullable=False)
created = Column(DateTime, nullable=False, default=datetime.datetime.utcnow)
profileID = Column(Integer, ForeignKey('profile.ID'), nullable=False)
locationID = Column(Integer, ForeignKey('location.ID'))
location = relationship("Location")
maps = relationship("Map", backref="owner", cascade="save-update, merge, delete, delete-orphan")
class Map(Base):
__tablename__ = 'map'
ID = Column(Integer, primary_key=True)
name = Column(Unicode(255))
maptypeID = Column(Integer, nullable=False)
created = Column(DateTime, nullable=False, default=datetime.datetime.utcnow)
parentID = Column(Integer, ForeignKey('map.ID'))
ownerID = Column(Integer, ForeignKey('character.ID'))
children = relationship("Map", backref=backref("parent", remote_side="Map.ID"))
locations = relationship("Location", backref='map', cascade="save-update, merge, delete, delete-orphan")
class Location(Base):
__tablename__ = 'location'
ID = Column(Integer, primary_key=True)
x = Column(Integer, nullable=False)
y = Column(Integer, nullable=False)
locationtypeID = Column(Integer, nullable=False)
created = Column(DateTime, nullable=False, default=datetime.datetime.utcnow)
mapID = Column(Integer, ForeignKey('map.ID'), nullable=False)
How can I solve this issue?
Edit (Solved):
After some more playing around with use_alter, I was able to solve the problem by changing the mapID definition in the Location class from:
mapID = Column(Integer, ForeignKey('map.ID'), nullable=False)
To:
mapID = Column(Integer, ForeignKey('map.ID', use_alter=True, name="fk_location_map"), nullable=False)
In response to the recommendation of breaking the cyclic dependency, I'd rather have the correct relationships and data integrity represented in the schema. I'd much rather worry about fixing ORM issues or changing ORMs rather than fudging the schema to fit the ORM's expectations.
In this particular case, I can't think of a more concise and elegant way to represent all of the information(in the most fundamental sense of the word) that I need the schema to represent for the application.
Side note: Having worked with other languages/frameworks/ORMs, this kind of snafu is often automatically accounted for by the ORM. For example, in .NET E/F, I believe the FK constraints are typically added and activated after all the table creation statements.
You can probably get alembic to help you with a cyclic dependency in your schema, but I'm not going to help you with that. Instead, I'm going to strongly urge you to break the cyclic dependency.
If i'm understanding your schema, you have the rooms or buildings in your model represented by Location. the players/monsters in your schema are Characters, which at any time must be in exactly one location. Additionally, there are some Map items floating around, which are always about some specific location, and are always in the inventory of exactly one character.
So here's how I'd spell that
class Location:
id = Column(Integer, primary_key=True)
class Character:
id = Column(Integer, primary_key=True)
location_id = Column(ForeignKey(Location.id))
location = relationship(Location, backref="characters")
class Map:
id = Column(Integer, primary_key=True)
location_id = Column(ForeignKey(Location.id))
owner_id = Column(ForeignKey(Character.id))
location = relationship(Location, backref="maps")
owner = relationship(Character, backref="inventory")
Related
I am into a very confusing situation where I have one to many relation and I want to query data like I want all parent table data but want to get only data from child tables which fulfill condition of site_id = 100.
class Policy(Base):
"""table containing details for Policies"""
__tablename__ = "UmbrellaPolicy"
id = Column(Integer, primary_key=True)
policy_id = Column(Integer, nullable=False, index=True)
user_defined_name = Column(String(255), nullable=True)
and child is like this
class Site(Base):
__tablename__ = "Site"
id = Column(Integer, primary_key=True)
policy_id = Column(Integer, ForeignKey("Policy.id"))
site_id = Column(String(32), nullable=False, index=True)
policy = relationship("Policy", backref="sites")
You should be able to filter join relations like this
parents = Policy.objects.filter(site__site_id=100)
You can find more info about the Django query API here but its generally of the form where you reference the relation with classname__columnname there are many other ways to filter/query that you can reference in the docs
I can't describe the column "invited_name" (column_property). I don't know how to do this correctly.
class Worker(declarative_base()):
__tablename__ = "staff_worker_info"
id = Column(Integer, primary_key=True)
first_name = Column(String(40), nullable=False)
last_name = Column(String(40), nullable=False)
invited_id = Column(Integer, ForeignKey('staff_worker_info.id'))
invited = relationship("Worker", uselist=False, remote_side=[id], join_depth=1)
# I don't know how to describe this column
invited_name = column_property(
select([Worker.first_name]). \
where(Worker.id == invited_id).\
label('invited_n'))
I understand why this doesn't work, but I don't know how to write it differently.
I should get such a SQL query.
SELECT staff_worker_info.id, staff_worker_info.first_name staff_worker_info.last_name, staff_worker_info.invited_id,
(SELECT worker_invited.first_name
FROM staff_worker_info AS worker_invited
WHERE staff_worker_info.invited_id = worker_invited.id) AS invited_n,
FROM staff_worker_info
Might be a bit late, but I recently faced a similar question. I think your problem is quite easy to solve with only the relationship. If you want you can also solve it by using a column_property.
First, using the relationship. If you make the invited relationship joined, then the actual query that is send to the database is a self-join. You can access the first name via that relationship (reference https://docs.sqlalchemy.org/en/14/orm/self_referential.html).
class Worker(declarative_base()):
__tablename__ = "staff_worker_info"
id = Column(Integer, primary_key=True)
first_name = Column(String(40), nullable=False)
last_name = Column(String(40), nullable=False)
invited_id = Column(Integer, ForeignKey('staff_worker_info.id'))
invited = relationship("Worker", uselist=False, remote_side=[id], join_depth=1, lazy='joined')
#property
def invited_name(self):
return self.invited.first_name
Then, if the query you want to do is more complex, and it requires you to create a column_property, you can also do it as follows (reference https://docs.sqlalchemy.org/en/14/orm/mapped_sql_expr.html):
from sqlalchemy import inspect
from sqlalchemy.orm import aliased
class Worker(declarative_base()):
__tablename__ = "staff_worker_info"
id = Column(Integer, primary_key=True)
first_name = Column(String(40), nullable=False)
last_name = Column(String(40), nullable=False)
invited_id = Column(Integer, ForeignKey('staff_worker_info.id'))
invited = relationship("Worker", uselist=False, remote_side=[id], join_depth=1)
# Requires alias
worker = aliased(Worker)
inspect(Worker).add_property(
"invited_name",
column_property(
select([worker.first_name]). \
where(worker.id == Worker.invited_id)
)
)
I found a method. But he did not like it.
invited_name = column_property(
select([text("invited_table.first_name")]).
where(text("invited_table.id = staff_worker_info.invited_id")).
select_from(text("staff_worker_info AS invited_table")).
label('invited_n'))
I'm trying to properly define the relations between a few tables that all use composite keys. The issue I am facing is that one Column used in those keys is common to all the tables.
Consider the following tables and relations, written using sqlalchemy:
class Match(Base):
__tablename__ = 'match'
id = Column(String, primary_key=True, nullable=False)
players = relationship('PlayerMatch', back_populates='match')
region = Column(String, primary_key=True, nullable=False)
class Player(Base):
__tablename__ = 'player'
id = Column(String, primary_key=True, nullable=False)
matches = relationship('PlayerMatch', back_populates='player')
region = Column(String, primary_key=True, nullable=False)
class PlayerMatch(Base):
__tablename__ = 'player_match'
match = relationship('Match', back_populates='players')
match_id = Column(String, primary_key=True, nullable=False)
player = relationship('Player', back_populates='matches')
player_id = Column(String, primary_key=True, nullable=False)
region = Column(String, primary_key=True, nullable=False)
__table_args__ = (
ForeignKeyConstraint(['region', 'player_id'], ['player.region', 'player.id']),
ForeignKeyConstraint(['region', 'match_id'], ['match.region', 'match.id']),
)
This is obviously conflictual for sqlachemy since it doesn't know how to fill the region attribute of PlayerMatch. It throws the following warning:
SAWarning: relationship 'PlayerMatch.match' will copy column match.region to column player_match.region, which conflicts with relationship(s): 'PlayerMatch.match' (copies player.region to player_match.region). Consider applying viewonly=True to read-only relationships, or provide a primaryjoin condition marking writable columns with the foreign() annotation.
Note that region is guaranteed to be the same for both the match and player involved in this relation. As is, it still works but gives me the previous warning. I've spent some time trying to define join conditions of the various relationships but I can't get it to work properly. I'd be fine with a mechanism to simply indicate which region (coming from Match or from Player) is to actually be persisted in PlayerMatch.
What is the correct way to achieve this?
Thanks for your help.
The solution is to specify the primaryjoin attribute of the PlayerMatch.player relationship in order for it to use both Player.id and Player.region, but to prevent from persisting the region coming from Player, as follows:
player = relationship(
'Player',
back_populates='matches',
primaryjoin="and_(Player.id == foreign(PlayerMatch.player_id), "
"Player.region == PlayerMatch.region)"
)
I have 2 tables defined:
class TCableSet(Base):
__tablename__ = 'tCableSet'
ixCableSet = Column(Integer, primary_key=True)
decCableSetOne = Column(Numeric(8, 2))
decCableSetTwo = Column(Numeric(8, 2))
decCableSetThree = Column(Numeric(8, 2))
class TStepVoltage(Base):
__tablename__ = 'tStepVoltage'
ixStepVoltage = Column(Integer, primary_key=True)
ixSubReport = Column(Integer, ForeignKey('tSubReport.ixSubReport'), nullable=False)
iVoltage = Column(Integer)
ixPhaseA = Column(Integer, ForeignKey('tCableSet.ixCableSet'), nullable=False)
ixPhaseB = Column(Integer, ForeignKey('tCableSet.ixCableSet'), nullable=False)
ixPhaseC = Column(Integer, ForeignKey('tCableSet.ixCableSet'), nullable=False)
sub_report = relationship('TSubReport',
backref=backref('step_voltage'))
I understand why I am getting this error but can't figure out a proper way (yet).
When the table gets saved, I store the values in the tCableSet table and then use the id as a foreign key in my tStepVoltage table. The problem I have is when I go to retrieve the data, I want to be able to get the values(tCableSet row) along with the rest of my tStepVoltage table via a relationship, however I'm not sure how to go about this since I don't have a field in my tCableSet that can directly be linked via relationship to my tStepVoltage. I basically just needed the tCableSet for normalization
Since you have more than one foreign key that points to the same table, you have to tell SQLAlchemy which foreign key to use.
For example:
sub_report_a = relationship('TSubReport',
backref=backref('step_voltage'),
foreign_keys=[ixPhaseA])
I have two models:
class Report(Base):
__tablename__ = 'report'
id = Column(Integer, primary_key=True)
class ReportPhoto(Base):
__tablename__ = 'report_photo'
id = Column(Integer, primary_key=True)
report_id = Column(Integer, ForeignKey(Report.id), nullable=False)
report = relationship(Report, uselist=False, backref=backref('report_photo', uselist=True))
And I would like to add column to Report model which indicates is there any records within ReportPhoto. I try to use column_property this way:
class Report(Base):
__tablename__ = 'report'
id = Column(Integer, primary_key=True)
has_photo = column_property(
select(ReportPhoto.any())
)
but get an error NameError: name 'ReportPhoto' is not defined. How I can fix this issue?
something like that should work:
class ReportPhoto(Base):
__tablename__ = 'report_photo'
id = Column(Integer, primary_key=True)
report_id = Column(Integer, ForeignKey('report.id'), nullable=False)
class Report(Base):
__tablename__ = 'report'
id = Column(Integer, primary_key=True)
report_photos = relationship(ReportPhoto, backref='report')
has_photo = column_property(
exists().where(ReportPhoto.report_id==id)
)
I will add to #Vladimir lliev's response with some clarification for anyone else that might not see how to do this.
Place the table that will have the 'foreign table referencing' column_property after that which it references. In this case, it means placing Report after ReportPhoto. This will solve your NameError, however, you would be left with a new error on your ReportPhoto foreign key reference. To solve this, place your foreign key table reference in quotes. You can read more by referencing the declarative documentation (e.g., declarative.py) and looking under "Configuring Relationships" --- specifically, read the portion on quoting your foreign references.
With your code, this would look like:
class ReportPhoto(Base):
# This now goes first
__tablename__ = 'report_photo'
id = Column(Integer, primary_key=True)
# Notice the quotations around Report references here
report_id = Column(Integer, ForeignKey("Report.id"), nullable=False)
# Notice the quotations around Report references here
report = relationship("Report",
uselist=False,
backref=backref("report_photo", uselist=True))
class Report(Base):
# This is now _after_ ReportPhoto
__tablename__ = 'report'
id = Column(Integer, primary_key=True)
# ReportPhoto now exists and we will not trip a NameError exception
has_photo = column_property(
select(ReportPhoto.any())
)