I am trying to create table for some courses, wհօse last column is another table and presents the lecturers names.
class Teachers(Base):
__tablename__ = 'teacher'
full_name = Column(String, unique=False, primary_key=True)
company = Column(String, unique = False)
class Course(Base):
__tablename__ = 'course'
course_name = Column(String, nullable=False)
course_id = Column(String,primary_key=True)
level = Column(String,unique=False)
teachers = Teachers
But I found following issue when the name of one lecturer is repeated in different courses:
sqlalchemy.exc.IntegrityError: (sqlite3.IntegrityError) UNIQUE constraint failed: teacher.full_name
How can I solve this problem?
Your teachers column is not a column. Instead you are adding a whole table as an attribute.
What you should do, is defining a column in Course that contains the primary key of the Teacher (assuming there is only one teacher per course) and mark it as a ForeignKey. You can than also add a relationship for easier work.
from pathlib import Path
import sqlalchemy.orm
from sqlalchemy import Column, String, ForeignKey, create_engine
from sqlalchemy.orm import declarative_base, relationship
Base = declarative_base()
class Teacher(Base):
__tablename__ = "teachers"
full_name = Column(String, unique=False, primary_key=True)
company = Column(String, unique=False)
class Course(Base):
__tablename__ = "courses"
course_id = Column(String, primary_key=True)
course_name = Column(String, nullable=False)
level = Column(String, unique=False)
teacher_name = Column(ForeignKey("teachers.full_name"))
teacher = relationship("Teacher")
def main():
if Path("test_database.db").exists():
Path("test_database.db").unlink()
engine = create_engine("sqlite:///test_database.db")
Base.metadata.create_all(engine)
session = sqlalchemy.orm.Session(autocommit=False, autoflush=False, bind=engine)
teacher_1 = Teacher(full_name="Bob")
course_1 = Course(course_id=1, course_name="Course 1", level="beginner", teacher=teacher_1)
course_2 = Course(course_id=2, course_name="Course 2", level="advanced", teacher=teacher_1)
session.add_all([course_1, course_2])
session.commit()
if __name__ == "__main__":
main()
Related
I'm trying to write a web app with user roles and I keep getting this error no matter the changes I make.
Could you please help me find what's wrong?
Thank you in advance!!
This is my models.py file:
from sqlalchemy import Column, Integer, String, Float, Boolean, ForeignKey
from flask_login import UserMixin
from sqlalchemy.orm import relationship, backref
import db
class Rol(db.Base):
__tablename__ = 'Roles'
id_rol = Column(Integer, primary_key=True)
nombre_rol = Column(String, nullable=False)
user_rol = relationship("User rol", back_populates="rol", uselist =False)
def __init__(self, nombre_rol):
self.nombre_rol = nombre_rol
class User(UserMixin, db.Base):
__tablename__ = 'Users'
user_id = Column(Integer, primary_key=True)
username = Column(String, unique=True, nullable=False)
password = Column(String, unique=True, nullable=False)
is_active = Column(Boolean, default=False)
user_rol = relationship("User rol", back_populates="user", uselist =False)
def __init__(self,username,password,is_active):
self.username = username
self.password = password
self.is_active = is_active
def get_id(self):
return self.id
def is_active(self):
return self.is_active
def activate_user(self):
self.is_active = True
def get_username(self):
return self.username
def get_rol(self):
return self.rol
class Roles_users(db.Base):
__tablename__ = 'User Roles'
user_id = Column(Integer, ForeignKey("Users.user_id"))
id_rol = Column(Integer, ForeignKey("Roles.id_rol"))
user = relationship("User", back_populates="user roles")
rol = relationship("Rol", back_populates="user roles")
I'm trying to build an example with several related models, like the following.
We have a model B with a 1:n relation with a model C; then we have a model A with a n:1 relation with B and a n:1 relation with C. (C has a 2-columns primary key)
I tried this code:
class C(db.Model):
__tablename__ = 'C'
key1 = Column(Integer, primary_key=True)
key2 = Column(Integer, primary_key=True)
attr1 = Column(Date)
attr2 = Column(Boolean)
related_b = Column(Integer, ForeignKey('B.spam'))
class B(db.Model):
__tablename__ = 'B'
spam = Column(Integer, default=1, primary_key=True)
eggs = Column(String, default='eggs')
null = Column(String)
n_relation = relationship(C, foreign_keys='C.related_b')
class A(db.Model):
__tablename__ = 'A'
foo = Column(String, default='foo', primary_key=True)
bar = Column(String, default='bar', primary_key=True)
baz = Column(String, default='baz')
rel = relationship(B, foreign_keys='A.related_b')
related_b = Column(Integer, ForeignKey('B.spam'))
related_c1 = Column(Integer, ForeignKey('C.key1'))
related_c2 = Column(Integer, ForeignKey('C.key2'))
other_rel = relationship(C, foreign_keys=(related_c1, related_c2))
just to get the exception:
sqlalchemy.exc.AmbiguousForeignKeysError: Could not determine join condition between parent/child tables on relationship A.other_rel - there are multiple foreign key paths linking the tables. Specify the 'foreign_keys' argument, providing a list of those columns which should be counted as containing a foreign key reference to the parent table.
but hey, I HAVE passed that argument.
I tried various versions for that argument, specifying A columns, C columns via name, C columns via direct reference, nothing seems to affect this error.
I also tried to use a single compound foreign key, like this:
class A(db.Model):
__tablename__ = 'A'
foo = Column(String, default='foo', primary_key=True)
bar = Column(String, default='bar', primary_key=True)
baz = Column(String, default='baz')
rel = relationship(B, foreign_keys='A.related_b')
related_b = Column(Integer, ForeignKey('B.spam'))
related_c1 = Column(Integer, ForeignKey('C.key1'))
related_c2 = Column(Integer, ForeignKey('C.key2'))
compound = ForeignKeyConstraint(('related_c1', 'related_c2'), ('C.key1', 'C.key2'))
other_rel = relationship(C, foreign_keys=compound)
but nothing changed.
Am I mistaking something or is it a bug? (at least, the error message is not correct...)
The issue here is that you have to declare the ForeignKeyConstraint in __table_args__, not in the body of the class.
In other words, the following code will NOT apply the foreign key constraint to the child table ...
from sqlalchemy import create_engine, Column, Integer, text, ForeignKeyConstraint, String
from sqlalchemy.orm import declarative_base, relationship
connection_url = r"mssql+pyodbc://#.\SQLEXPRESS/myDb?driver=ODBC+Driver+17+for+SQL+Server"
engine = create_engine(connection_url, echo=True,)
with engine.connect() as conn:
for tb_name in ["tbl_child", "tbl_parent"]:
conn.execute(text(f"DROP TABLE IF EXISTS [{tb_name}]"))
Base = declarative_base()
class Parent(Base):
__tablename__ = "tbl_parent"
id1 = Column(Integer, primary_key=True)
id2 = Column(Integer, primary_key=True)
parent_name = Column(String(50))
children = relationship("Child", back_populates="parent")
class Child(Base):
__tablename__ = "tbl_child"
id = Column(Integer, primary_key=True, autoincrement=False)
child_name = Column(String(50))
parent_id1 = Column(Integer)
parent_id2 = Column(Integer)
ForeignKeyConstraint(
["parent_id1", "parent_id2"], ["tbl_parent.id1", "tbl_parent.id2"]
)
parent = relationship(
"Parent",
foreign_keys="[Child.parent_id1, Child.parent_id2]",
back_populates="children",
)
Base.metadata.create_all(engine)
""" console output:
2020-04-30 06:57:13,899 INFO sqlalchemy.engine.Engine
CREATE TABLE tbl_parent (
id1 INTEGER NOT NULL,
id2 INTEGER NOT NULL,
parent_name VARCHAR(50),
PRIMARY KEY (id1, id2)
)
2020-04-30 06:57:13,899 INFO sqlalchemy.engine.Engine ()
2020-04-30 06:57:13,900 INFO sqlalchemy.engine.Engine COMMIT
2020-04-30 06:57:13,901 INFO sqlalchemy.engine.Engine
CREATE TABLE tbl_child (
id INTEGER NOT NULL,
child_name VARCHAR(50),
parent_id1 INTEGER,
parent_id2 INTEGER,
PRIMARY KEY (id)
)
2020-04-30 06:57:13,901 INFO sqlalchemy.engine.Engine ()
2020-04-30 06:57:13,901 INFO sqlalchemy.engine.Engine COMMIT
"""
... but this will ...
from sqlalchemy import create_engine, Column, Integer, text, ForeignKeyConstraint, String
from sqlalchemy.orm import declarative_base, relationship
connection_url = r"mssql+pyodbc://#.\SQLEXPRESS/myDb?driver=ODBC+Driver+17+for+SQL+Server"
engine = create_engine(connection_url, echo=True,)
with engine.connect() as conn:
for tb_name in ["tbl_child", "tbl_parent"]:
conn.execute(text(f"DROP TABLE IF EXISTS [{tb_name}]"))
Base = declarative_base()
class Parent(Base):
__tablename__ = "tbl_parent"
id1 = Column(Integer, primary_key=True)
id2 = Column(Integer, primary_key=True)
parent_name = Column(String(50))
children = relationship("Child", back_populates="parent")
class Child(Base):
__tablename__ = "tbl_child"
__table_args__ = (
ForeignKeyConstraint(
["parent_id1", "parent_id2"], ["tbl_parent.id1", "tbl_parent.id2"]
),
)
id = Column(Integer, primary_key=True, autoincrement=False)
child_name = Column(String(50))
parent_id1 = Column(Integer)
parent_id2 = Column(Integer)
parent = relationship(
"Parent",
foreign_keys="[Child.parent_id1, Child.parent_id2]",
back_populates="children",
)
Base.metadata.create_all(engine)
""" console output:
CREATE TABLE tbl_parent (
id1 INTEGER NOT NULL,
id2 INTEGER NOT NULL,
parent_name VARCHAR(50) NULL,
PRIMARY KEY (id1, id2)
)
2020-04-30 07:52:43,771 INFO sqlalchemy.engine.Engine ()
2020-04-30 07:52:43,776 INFO sqlalchemy.engine.Engine COMMIT
2020-04-30 07:52:43,778 INFO sqlalchemy.engine.Engine
CREATE TABLE tbl_child (
id INTEGER NOT NULL,
child_name VARCHAR(50) NULL,
parent_id1 INTEGER NULL,
parent_id2 INTEGER NULL,
PRIMARY KEY (id),
FOREIGN KEY(parent_id1, parent_id2) REFERENCES tbl_parent (id1, id2)
)
2020-04-30 07:52:43,778 INFO sqlalchemy.engine.Engine ()
2020-04-30 07:52:43,802 INFO sqlalchemy.engine.Engine COMMIT
"""
Reference:
https://docs.sqlalchemy.org/en/14/orm/declarative_tables.html#orm-declarative-table-configuration
This question already has answers here:
how left outer join in sqlalchemy?
(1 answer)
sqlalchemy filter children in query, but not parent
(2 answers)
Closed 2 years ago.
I'm using sqlalchemy, and I have problem with this specific query.
I have data_template, devices_data, and device. Each device have value for each data in data_template. Those values are stored in devices_data. I want to list data_template for one device with values that this device has. If there is no value for some data_template, show None.
It has something to do with outerjoin. Here is my model:
from sqlalchemy.orm import sessionmaker, relationship
from sqlalchemy import *
from sqlalchemy.ext.declarative import declarative_base
Base = declarative_base()
class DataTemplate(Base):
__tablename__ = 'data_template'
id = Column(Integer, primary_key=True)
name = Column(String)
def __repr__(self):
return f"<DataTemplate(name={self.name})>"
class Device(Base):
__tablename__ = 'device'
id = Column(Integer, primary_key=True)
name = Column(String)
def __repr__(self):
return f"<Device(name={self.name})>"
class DeviceData(Base):
__tablename__ = 'device_data'
id = Column(Integer, primary_key=True)
value = Column(Integer, nullable=False)
data_name_id = Column(Integer, ForeignKey(DataTemplate.id), nullable=False)
device_id = Column(Integer, ForeignKey(Device.id), nullable=False)
data_template = relationship('DataTemplate', backref='device_data')
device = relationship('Device', backref='device_data')
def __repr__(self):
return f"<DeviceData(device={self.device.name}, data_template={self.data_template.name}, value={self.value})>"
engine = create_engine('sqlite://')
Base.metadata.create_all(engine)
Session = sessionmaker(bind=engine)
Session.configure(bind=engine)
session = Session()
dev1 = Device(name='Receiver')
dev2 = Device(name='TV')
dat_temp1 = DataTemplate(name="height")
dat_temp2 = DataTemplate(name="width")
dat_temp3 = DataTemplate(name="length")
session.add_all([dev1, dev2, dat_temp1, dat_temp2, dat_temp3])
dd1 = DeviceData(value=100, data_template=dat_temp1, device=dev1)
dd2 = DeviceData(value=50, data_template=dat_temp2, device=dev1)
dd3 = DeviceData(value=200, data_template=dat_temp1, device=dev2)
dd4 = DeviceData(value=40, data_template=dat_temp2, device=dev2)
dd5 = DeviceData(value=30, data_template=dat_temp3, device=dev2)
session.add_all([dd1, dd2, dd3, dd4, dd5])
s = session.query(DataTemplate, DeviceData).outerjoin(DeviceData).filter(DeviceData.device==dev1)
for x in s:
print(x)
with this outerjoin I'm getting:
(<DataTemplate(name=height)>, <DeviceData(device=Receiver, data_template=height, value=100)>)
(<DataTemplate(name=width)>, <DeviceData(device=Receiver, data_template=width, value=50)>)
and is equal to:
SELECT "d"."id", "val"."id"
FROM "DataTemplate" "d"
LEFT JOIN "DeviceData" "val"
ON "d"."id" = "val"."data_name_id"
WHERE "val"."device_id" = 1
but I want to get:
(<DataTemplate(name=height)>, <DeviceData(device=Receiver, data_template=height, value=100)>)
(<DataTemplate(name=width)>, <DeviceData(device=Receiver, data_template=width, value=50)>)
(<DataTemplate(name=length)>, None)
and that query should be:
SELECT "d"."id", "val"."id"
FROM "DataTemplate" "d"
LEFT JOIN "DeviceData" "val"
ON "d"."id" = "val"."data_name_id" AND "val"."device_id" = 1
how do I write this specific query?
Consider the following model(from pythoncentral tutorials):
class Department(Base):
__tablename__ = 'department'
id = Column(Integer, primary_key=True)
name = Column(String)
employees = relationship(
'Employee',
secondary='department_employee_link'
)
class Employee(Base):
__tablename__ = 'employee'
id = Column(Integer, primary_key=True)
name = Column(String)
hired_on = Column(DateTime, default=func.now())
departments = relationship(
Department,
secondary='department_employee_link'
)
class DepartmentEmployeeLink(Base):
__tablename__ = 'department_employee_link'
department_id = Column(Integer, ForeignKey('department.id'), primary_key=True)
employee_id = Column(Integer, ForeignKey('employee.id'), primary_key=True)
extra_data = Column(String(256))
department = relationship(Department, backref=backref("employee_assoc"))
employee = relationship(Employee, backref=backref("department_assoc"))
I understand that this piece of code establishes a many-many relationship between employees and depts.Suppose I have to insert the department_id and employee_id into the DepartmentEmployee link table, how do I do it?? The tutorial says:
>>> IT = Department(name="IT")
>>> John = Employee(name="John")
>>> John_working_part_time_at_IT = DepartmentEmployeeLink(department=IT, employee=John, extra_data='part-time')
>>> s = session()
>>> s.add(John_working_part_time_at_IT)
>>> s.commit()
But I want to do it separately. first, I want to add details into the department table, then the employee table. Finally, I need to populate the Dept-employee link where the user enters the extra_data column alone... How do I do this?
I tried doing something like this
def mapper:
que=DBSession.query(Department)
que2=DBSession.query(Strings)
rel=DepartmentEmployeeLink(extra_data=str(x))//__init__ed this
rel.department=que
rel.employee=que.employees[0].id
DBSession.add(rel)
This is how I want the insertion to happen since I already have data inside department and employee. Can anyone tell me how to accomplish this i.e., inserting into the link table if I have data in the other 2 tables?
I learnt there's a way to do this like "employees.append.xxx" but I dont understand.. Can someone point me in the right direction please? Thanks in advance.
Here is a better way to define Many to Many relationship in SQLAlchemy using association_table.
association_table = Table('department_employee_link', Base.metadata,
Column('departmant_id', Integer, ForeignKey('department.id')),
Column('employee_id', Integer, ForeignKey('employee.id'))
)
class Department(Base):
__tablename__ = 'department'
id = Column(Integer, primary_key=True)
name = Column(String)
employees = relationship(
'Employee',
secondary=association_table
)
class Employee(Base):
__tablename__ = 'employee'
id = Column(Integer, primary_key=True)
name = Column(String)
hired_on = Column(DateTime, default=func.now())
departments = relationship(
Department,
secondary=association_table
)
IT = Department(name="IT")
John = Employee(name="John")
IT.employees.append(John)
s = session()
s.add(IT)
s.commit()
I show here an (artificial) example of three linked tables: ParentA, ChildA, and ChildAA. ChildA is related to the primary key (PK) of ParentA via foreign key, and ChildAA relates to the same key in ChildA. In this way ChildAA links to the primary key of the ParentA. I would expect that when I make a change to the ParentA PK this change propagates back to the corresponding ChildAA's attribute, but it doesn't.
Thanks in advance!
(I apologize if this has been answered or documented before, I really couldn't find anything.)
The Code:
from sqlalchemy import *
from sqlalchemy import orm
from sqlalchemy.ext.declarative import declarative_base
Base = declarative_base()
class ParentA(Base):
__tablename__ = 'tbl_parentA'
pid = Column(Integer, primary_key=True)
childA = orm.relationship("ChildA", passive_updates=False, backref='parentA')
class ChildA(Base):
__tablename__ = 'tbl_childA'
attrib1 = Column(String, nullable=True)
parentA_id = Column(Integer, ForeignKey(ParentA.pid), primary_key=True)
childAA = orm.relationship("ChildAA", passive_updates=False, backref="childA")
# This class is related to Parents through ChildA
class ChildAA(Base):
__tablename__ = 'tbl_childAA'
cid = Column(Integer, primary_key=True)
attrib1 = Column(String, nullable=True)
parentA_id = Column(Integer, ForeignKey(ChildA.parentA_id))
def clear_db(db):
tmp = db.echo
db.echo = False
metadata = MetaData(bind=db)
metadata.reflect(db)
for table in reversed(metadata.sorted_tables):
table.drop(db)
metadata.clear()
db.echo = tmp
if __name__ == '__main__':
# SQLite Connection
db = create_engine('sqlite:///linked_updates.db')
# db.echo = True
# Initalize Objects
pa1 = ParentA()
ca1 = ChildA(attrib1='ca1 str')
caa1= ChildAA(attrib1='caa1 str')
# Assign a parent to ChildA
ca1.parentA = pa1
# Assign a parent to ChildAA
caa1.childA = ca1
# Initialize clean DB & session
clear_db(db)
Base.metadata.create_all(db)
session = orm.create_session(db)
# Write to DB
session.add_all([pa1, ca1, caa1])
session.flush()
print 'After flush, we have: ', caa1.parentA_id, '==', caa1.childA.parentA_id
# Induce change, check propagation
pa1.pid = 2
session.flush()
print 'I expect: ', caa1.parentA_id, '==', caa1.childA.parentA_id
print 'END'