On Friday, November 18, 2016 at 2:08:05 PM UTC-5, Paul Giralt wrote:
>
> I'm having trouble figuring out how to accomplish this task using
> SQLAlchemy. Basically I have a table that maps a user's skill levels as
> follows:
>
> class Skillmap(db.Model):
> __tablename__ = 'skillmap'
> id = db.Column(db.UUID(), primary_key=True)
> user_id = db.Column(db.UUID(), db.ForeignKey('user.id'))
> skill_id = db.Column(db.UUID(), db.ForeignKey('skill.id'))
> level_id = db.Column(db.UUID(), db.ForeignKey('level.id'))
> timestamp = db.Column(db.DateTime, default=datetime.utcnow(),
> onupdate=datetime.utcnow())
>
> A particular user will be associated with a variety of skills and those
> skills are at a certain level (say 1 - 10 for simplicity).
>
> So for example, I could insert into the DB an entry that says:
>
> user1 skill1 level 5 timestamp 1
> user1 skill2 level 4 timestamp 2
> user1 skill3 level 7 timestamp 3
>
> then later on, the user updates their skill:
>
> user1 skill1 level 10 timestamp 4
> user1 skill2 level 10 timestamp 5
>
> When the user updates the skill, I don't update the DB record, Rather, I
> insert a new record into the DB so that I can store the history of the
> skill level over time.
>
> When I want to query for the current skill levels for a user (latest
> timestamp), I want to get back the latest entries for any given skill. In
> other words, for the above example I'd like to get the results:
>
> user1 skill1 level 10 timestamp 4
> user1 skill2 level 10 timestamp 5
> user1 skill3 level 7 timestamp 3
>
> I've tried playing around with func.max() but I haven't been able to get
> it to do what I want.
>
> I found this article that describes a solution for a similar question but
> the solution is just SQL syntax:
>
>
> http://stackoverflow.com/questions/612231/how-can-i-select-rows-with-maxcolumn-value-distinct-by-another-column-in-sql
>
> Is there an easy way to accomplish this using SQLAlchemy? Any help would
> be greatly appreciated.
>
The top answer uses a JOIN to a subquery from the same table, which can be
a bit tricky in SQLAlchemy. Start with "Using Subqueries" in the "Object
Relational Tutorial". Whenever I need to use the same table independently
in different parts of the same query, I define aliases to keep everything
straight. Use the aliased() function on a mapped class.
http://docs.sqlalchemy.org/en/rel_1_0/orm/tutorial.html#using-subqueries
http://docs.sqlalchemy.org/en/rel_1_0/orm/query.html?highlight=aliased#sqlalchemy.orm.aliased
Though your table design will work, there is an easier to query design
called a "temporal state table" in which there are two timestamps instead
of one. The pair of timestamps define the period during which the row
applies. Current rows will have no end timestamp. I use Postgres's range
types to implement this design:
CREATE TABLE skillmap_temporal (
valid_period daterange,
user_id integer,
skill_id integer,
level_id integer
);
To get the current row for user 5:
SELECT * FROM skillmap_temporal WHERE user_id = 5 and
upper_inf(valid_period);
I learned about this approach and various others from the excellent
"Developing Time-Oriented Database Applications in SQL." Reading that book
transformed my thinking use dates and timestamps. What we often need to
model, as in your case, is a dimension of time independent of other column
values. Because the book is out of print, the author has made it available
to download free of cost: http://www.cs.arizona.edu/people/rts/tdbbook.pdf
--
SQLAlchemy -
The Python SQL Toolkit and Object Relational Mapper
http://www.sqlalchemy.org/
To post example code, please provide an MCVE: Minimal, Complete, and Verifiable
Example. See http://stackoverflow.com/help/mcve for a full description.
---
You received this message because you are subscribed to the Google Groups
"sqlalchemy" group.
To unsubscribe from this group and stop receiving emails from it, send an email
to [email protected].
To post to this group, send email to [email protected].
Visit this group at https://groups.google.com/group/sqlalchemy.
For more options, visit https://groups.google.com/d/optout.