SQLAlchemy: hybrid_property expression and subquery - python

I am trying to do a complex hybrid_property using SQLAlchemy: my model is
class Consultation(Table):
patient_id = Column(Integer)
patient = relationship('Patient', backref=backref('consultations', lazy='dynamic'))
class Exam(Table):
consultation_id = Column(Integer)
consultation = relationship('Consultation', backref=backref('exams', lazy='dynamic'))
class VitalSign(Table):
exam_id = Column(Integer)
exam = relationship('Exam', backref=backref('vital', lazy='dynamic'))
vital_type = Column(String)
value = Column(String)
class Patient(Table):
patient_data = Column(String)
#hybrid_property
def last_consultation_validity(self):
last_consultation = self.consultations.order_by(Consultation.created_at.desc()).first()
if last_consultation:
last_consultation_conclusions = last_consultation.exams.filter_by(exam_type='conclusions').first()
if last_consultation_conclusions:
last_consultation_validity = last_consultation_conclusions.vital_signs.filter_by(sign_type='validity_date').first()
if last_consultation_validity:
return last_consultation_validity
return None
#last_consultation_validity.expression
def last_consultation_validity(cls):
subquery = select([Consultation.id.label('last_consultation_id')]).\
where(Consultation.patient_id == cls.id).\
order_by(Consultation.created_at.desc()).limit(1)
j = join(VitalSign, Exam).join(Consultation)
return select([VitalSign.value]).select_from(j).select_from(subquery).\
where(and_(Consultation.id == subquery.c.last_consultation_id, VitalSign.sign_type == 'validity_date'))
As you can see my model is quite complicated.
Patients get Consultations. Exams and VitalSigns are cascading data for the Consultations. The idea is that all consultations do not get a validity but that new consultations make the previous consultations validity not interesting: I only want the validity from the last consultation; if a patient has a validity in previous consultations, I'm not interested.
What I would like to do is to be able to order by the hybrid_property last_consultation_validity.
The output SQL looks ok to me:
SELECT vital_sign.value
FROM (SELECT consultation.id AS last_consultation_id
FROM consultation, patient
WHERE consultation.patient_id = patient.id ORDER BY consultation.created_at DESC
LIMIT ? OFFSET ?), vital_sign JOIN exam ON exam.id = vital_sign.exam_id JOIN consultation ON consultation.id = exam.consultation_id
WHERE consultation.id = last_consultation_id AND vital_sign.sign_type = ?
But when I order the patients by last_consultation_validity, the rows do not get ordered ...
When I execute the same select outside of the hybrid_property, to retrieve the date for each patient (just setting the patient.id), I get the good values. Surprising is that the SQL is slightly different, removing patient in the FROMin the SELECT.
So I'm actually wondering if this is a bug in SQLAlchemy or if I'm doing something wrong ... Any help would be greatly appreciated.

Related

Insert new rows if values from TWO columns doens't already exists (peewee with PostgreSQL db)

I'm trying to insert new rows if values from user_id AND answered_at doesn't already exists in my table.
Here's my model:
class NPS(BaseModel):
class Meta:
schema = os.getenv('SCHEMANAME')
table_name = os.getenv('TABLENAME')
user_id = pw.IntegerField()
answered_at = pw.DateField()
score = pw.IntegerField()
motivation = pw.TextField()
comment = pw.TextField(null=True)
attempts = pw.DateField()
scores = pw.TextField()
And here's the insert part, where dados_nps is a list of dicts in right format:
cursor = (NPS
.insert_many(dados_nps)
.on_conflict(conflict_target=[NPS.user_id, NPS.answered_at], action='ignore')
.returning())
inserted_rows = cursor.execute()
print(inserted_rows)
As you can see, I'm passing a list of (two) fields in conflict_target: [NPS.user_id, NPS.answered_at]. But this is not working, because the rows are always inserted.
The action argument is right? There's something else I'm missing in my class model definition or inside the on_conflict() method?
Maybe should I set something in constrainst using Check() like this example?
class Meta:
constraints = [Check('user_id AND answered_at NOT EXISTS')]
http://docs.peewee-orm.com/en/latest/peewee/models.html?highlight=constraint#indexes-and-constraints
Obs: I'm using returning() to after this count the number of rows inserted.
As #AdamKG pointed out, I needed to pass unique indexes to my class model.
This solves the problem, thanks!
If someone else is still getting this problem, maybe this can be the error:
I was trying to do this, but not working yet
class NPS(BaseModel):
user_id = pw.IntegerField()
answered_at = pw.DateField()
score = pw.IntegerField()
motivation = pw.TextField()
comment = pw.TextField(null=True)
attempts = pw.DateField()
scores = pw.TextField()
class Meta:
schema = os.getenv('SCHEMANAME')
table_name = os.getenv('TABLENAME')
indexes = (
(('user_id', 'answered_at'), True)
)
But the documentation says that we need to keep the trailing comma in the 2-tuple, so this works:
class NPS(BaseModel):
user_id = pw.IntegerField()
answered_at = pw.DateField()
score = pw.IntegerField()
motivation = pw.TextField()
comment = pw.TextField(null=True)
attempts = pw.DateField()
scores = pw.TextField()
class Meta:
schema = os.getenv('SCHEMANAME')
table_name = os.getenv('TABLENAME')
# need to keep the trailing comma
indexes = (
(('user_id', 'answered_at'), True),
)
Note the trailing coma in indexes.

Sort parent model by multiple child properties SQLAlchemy

I'm using Python/Flask/SQLAlchemy. I have a class Contest which I want to sort by rating which is (tricky) the sum of its child (Side) properties. The formula for the rating is
leftside.score + len(leftside.votes) + rightside.score + len(leftside.votes)
Models:
class Contest(db.Model):
leftside_id = db.Column(db.Text, db.ForeignKey('sides.id'))
rightside_id = db.Column(db.Text, db.ForeignKey('sides.id'))
leftside = db.relationship("Side", foreign_keys=[leftside_id])
rightside = db.relationship("Side", foreign_keys=[rightside_id])
rating = #??? leftside.score + len(leftside.votes) + rightside.score + len(leftside.votes)
class Side(db.Model):
score = db.Column(db.Integer, default=0)
votes = db.relationship('SideVote')
class SideVote(db.Model):
side_id = db.Column(db.Text, db.ForeignKey('sides.id'))
side = db.relationship('Side')
I can write a raw SQL, but it will return simple list, but I need SQLAlchemy query
SELECT *, (
score
+ (SELECT COUNT(*) FROM sidevotes WHERE side_id = contests.leftside_id or side_id = contests.rightside_id)
) as Field
FROM contests, sides
WHERE contests.leftside_id = sides.id or contests.rightside_id = sides.id
ORDER BY Field DESC
So once again, I need to sort Contests by the formula written above, here I see 2 possible solutions:
Either create some hybrid_property/column_property
Or execute SQL and map it SQLAlchemy query so I can use this results

SQLAlchemy: How to keep the record ID and its relationship record in sync for new records (pre-commit)?

When creating new records, I'd expect that foreign key fields, and their relationship object would stay in sync (if I change one the other would change to reflect), but this doesn't seem to be the case. Is this possible to do?
Given the following:
Base = declarative_base();
class User(Base):
__tablename__ = 'user';
id = Column(Integer, primary_key=True);
name = Column(String);
fullname = Column(String);
password = Column(String);
equipment = relationship('Equipment', backref='user');
class Equipment(Base):
__tablename__ = 'equipment';
id = Column(Integer, primary_key=True);
user_id = Column(Integer, ForeignKey('user.id'), nullable=False);
name = Column(String);
engine = create_engine('sqlite:///:memory:', echo=True);
Base.metadata.create_all(engine);
session = sessionmaker(bind=engine);
conn = session();
conn.add_all([
User(name='bill', fullname='Bill W.', password='rlrrlrll'), # id=1
User(name='tony', fullname='Tony I.', password='EADGBe'), # id=2
User(name='ozzy', fullname='Ozzy O.', password='durrrr'), # id=3
User(name='geezer', fullname='Terence B.', password='password'), # id=4
]);
I can create related records in either of the two ways:
guitar = Equipment(
user = conn.query(User).filter(User.name == 'tony').one(),
name = 'Gibson SG');
drums = Equipment(
user_id = 1,
name = 'Ludwigs');
Following these lines I'd expect guitar.user_id to be 2, and drums.user to be the 'bill' object, but in both cases they're None. After I conn.add()/conn.commit() then it starts working a little more like I'd expect (both complementary fields return non-None values).
Is there any way for this to work pre-commit? I'd like to be able to construct new records either way (by ID or by object), and in library functions be able to reliably access the ID or object.
You can do this by flushing:
conn.add(guitar)
conn.add(name)
conn.flush()
Flushing emits the INSERT queries but does not COMMIT, meaning you can ROLLBACK later if you need to.

Sqlalchemy ID field isn't populated when relationship with another table is set up

I'm trying to set up Sqlalchemy and am running into problems with setting up relationships between tables. Most likely it's misunderstanding on my part.
A table is set up like so. The important line is the one with two asterisks one either side, setting up the relationship to table "jobs."
class Clocktime(Base):
"""Table for clockin/clockout values
ForeignKeys exist for Job and Employee
many to one -> employee
many to one -> job
"""
__tablename__ = "clocktimes"
id = Column(Integer, primary_key=True)
time_in = Column(DateTime)
time_out = Column(DateTime)
employee_id = Column(Integer, ForeignKey('employees.id'))
**job_id = Column(Integer, ForeignKey('jobs.id'))**
# employee = many to one relationship with Employee
# job = many to one relationship with Job
#property
def timeworked(self):
return self.time_out - self.time_in
#property
def __str__(self):
formatter="Employee: {employee.name}, "\
"Job: {job.abbr}, "\
"Start: {self.time_in}, "\
"End: {self.time_out}, "\
"Hours Worked: {self.timeworked}, "\
"ID# {self.id}"
return formatter.format(employee=self.employee, job=self.job, self=self)
Now, the jobs table follows. Check the asterisked line:
class Job(Base):
"""Table for jobs
one to many -> clocktimes
note that rate is cents/hr"""
__tablename__ = "jobs"
id = Column(Integer, primary_key=True)
name = Column(String(50))
abbr = Column(String(16))
rate = Column(Integer) # cents/hr
**clocktimes = relationship('Clocktime', backref='job', order_by=id)**
def __str__(self):
formatter = "Name: {name:<50} {abbr:>23}\n" \
"Rate: ${rate:<7.2f}/hr {id:>62}"
return formatter.format(name=self.name,
abbr="Abbr: " + str(self.abbr),
rate=self.rate/100.0,
id="ID# " + str(self.id))
When a user starts a new task, the following code is executed in order to write the relevant data to tables jobs and clocktimes:
new_task_job = [Job(abbr=abbrev, name=project_name, rate=p_rate), Clocktime(time_in=datetime.datetime.now())]
for i in new_task_job:
session.add(i)
session.commit()
start_time = datetime.datetime.now()
status = 1
Then, when the user takes a break...
new_break = Clocktime(time_out=datetime.datetime.now())
session.add(new_break)
session.commit()
If you look in the screenshot, the job_id field isn't being populated. Shouldn't it be populated with the primary key (id) from the jobs table, per
job_id = Column(Integer, ForeignKey('jobs.id'))
or am I missing something? I'm assuming that I'm to write code to do that, but I don't want to break anything that Sqlalchemy is trying to do in the backend. This should be a one job to many clocktimes, since a person can spend several days per task.
Checking out the docs it
looks like you've set up a collection of ClockTime objects on Job called clocktimes and a .job attribute on ClockTime that will refer to the parent Job object.
The expected behaviour is,
c1 = ClockTime()
j1 = Job()
>>> j1.clocktimes
[]
>>> print c1.job
None
When you populate j1.clocktimes with an object, you should also see c1.job get a non None value.
j1.clocktimes.append(c1)
>>> j1.clocktimes
[an instance of `ClockTime`]
>>> c1.job
[an instance of `Job`]
Do you find that behaviour? I don't see in your code where you populate clocktimes so the population of job is never triggered.
I think you are expecting the addition of ForeignKey to the column definition to do something it doesn't do. The ForeignKey constraint you put on job_id simply means that it is constrained to be among the values that exist in the id column of the Jobs table. Check here for more details

Generating Boolean Expressions from Subqueries in SQLAlchemy

I have the following SQLAlchemy models:
PENDING_STATE = 'pending'
COMPLETE_STATE = 'success'
ERROR_STATE = 'error'
class Assessment(db.Model):
__tablename__ = 'assessments'
id = db.Column(db.Integer, primary_key=True)
state = db.Column(
db.Enum(PENDING_STATE, COMPLETE_STATE, ERROR_STATE,
name='assessment_state'),
default=PENDING_STATE,
nullable=False,
index=True)
test_results = db.relationship("TestResult")
class TestResult(db.Model):
__tablename__ = 'test_results'
name = db.Column(db.String, primary_key=True)
state = db.Column(
db.Enum(PENDING_STATE, COMPLETE_STATE, ERROR_STATE,
name='test_result_state_state'),
default=PENDING_STATE,
nullable=False,
index=True)
assessment_id = db.Column(
db.Integer,
db.ForeignKey(
'assessments.id', onupdate='CASCADE', ondelete='CASCADE'),
primary_key=True)
And I am trying to implement logic to update an assessment to the error state if any of its test results are in the error state and update the assessment to the success state if all of its test results are in the success state.
I can write raw SQL like this:
SELECT 'error'
FROM assessments
WHERE assessments.state = 'error' OR 'error' IN (
SELECT test_results.state
FROM test_results
WHERE test_results.assessment_id = 1);
But I don't know how to translate that into SQLAlchemy. I'd think that subquery would be something like:
(select([test_results.state]).where(test_results.assessment_id == 1)).in_('error')
but I can't find any way to compare query results against literals like I'm doing in the raw SQL. I swear I must be missing something, but I'm just not seeing a way to write queries which return boolean expressions, which I think is fundamentally what I'm butting up against. Just something as simple as:
SELECT 'a' = 'b'
Seems to be absent from the documentation.
Any ideas on how to express this state change in SQLAlchemy? I'd also be perfectly open to rethinking my schemas if it looks like I'm going about this in a silly way.
Thanks!
Query below should do it for error check. Keep in mind that no rows will be returned in case it is not an eror.
q = (db.session.query(literal_column("'error'"))
.select_from(Assessment)
.filter(Assessment.id == sid)
.filter(or_(
Assessment.state == ERROR_STATE,
Assessment.test_results.any(TestResult.state == ERROR_STATE),
)))
If you wish to do similar check for success, you could find if there is any TestResult which is not a success and negate boolean result.
I actually ended up doing this with postgres triggers, which is probably the better way to handle state updates. So for the error case, I've got:
sqlalchemy.event.listen(TestResult.__table__, 'after_create', sqlalchemy.DDL("""
CREATE OR REPLACE FUNCTION set_assessment_failure() RETURNS trigger AS $$
BEGIN
UPDATE assessments
SET state='error'
WHERE id=NEW.assessment_id;
RETURN NEW;
END;
$$ LANGUAGE 'plpgsql';
CREATE TRIGGER assessment_failure
AFTER INSERT OR UPDATE OF state ON test_results
FOR EACH ROW
WHEN (NEW.state = 'error')
EXECUTE PROCEDURE set_assessment_failure();"""))
And something similar for the 'success' case where I count the number of test results vs the number of successful test results.
Credit to van for answering my question as I asked it, though! Thanks, I hadn't bumped into relationship.any before.

Categories