Column with default checking same table - python

Using SQLAlchemy, I'd like to autogenerate an identifier for a model:
class Foo(Model):
__tablename__ = 'foo'
id = Column(Integer, primary_key=True)
date = Column(Date, default=datetime.now)
reference = Column(Unicode, default=generate_reference)
Basically, I want generate_reference returning a field like:
FOO201410-001
where 2014 is current's year, 10 current's month and 001 the next id for current month, calculated using a query such as:
SELECT COUNT(*)
FROM foo
WHERE strftime('%m', datetime(date, 'unixepoch')) == strftime('%m', date('now'))
AND strftime('%y', datetime(date, 'unixepoch')) == strftime('%y', date('now'))
I've taken the syntax form from what I've read from SQLite3, though it's just an example. Basically, I want to know how many other records have the same month/year, and assign the last part with that count.
I've tried doing defaults with select expressions, but as my table is not yet created, it looks like I cannot create a select expression from it yet.
Thanks!

Here's a way to do it with default instead of event:
def foo_reference_default(context):
now = datetime.now()
month, year = now.month, now.year
ref = context.connection.execute(db.select([Foo.__table__]).where(
db.and_(db.extract('month', Foo.date)==month,
db.extract('year', Foo.date)==year)
).count()).scalar() + 1
return u'FOO{year}{month}-{ref:03}'.format(year=year, month=month, ref=ref)
class Foo(Model):
__tablename__ = 'foo'
id = Column(Integer, primary_key=True)
date = Column(Date, default=datetime.now)
reference = Column(Unicode, default=foo_reference_default)
This is effectively the same as your answer: A prior SELECT is done in order to populate the INSERT. (Note that I added +1 so it would start at 001 instead of 000.)
You could, of course, use a lambda to embed the function into default, but I don't recommend it -- you only want to call now() once. Calling it multiple times introduces a slim but real chance of getting inconsistent data on month and year edges.

I finally found a solution that works fine, thanks to other answers:
class Foo(Model):
__tablename__ = 'foo'
id = Column(Integer, primary_key=True)
date = Column(Date, default=datetime.now)
reference = Column(Unicode)
#db.event.listens_for(Foo, 'before_insert')
def receive_before_insert(mapper, connection, foo):
ref = foo.query.filter(db.and_(db.extract('month', Foo.date)==datetime.now().month,
db.extract('year', Foo.date)==datetime.now().year)
).count()
foo.reference = 'FOO{year}{month}-{ref:03}'.format(year=datetime.now().year,
month=datetime.now().month,
ref=ref)
though, I'm leaving the question open, in case someone suggests something that could
be directly embedded within the default key.

Related

SqlAlchemy auto increment composite key for every new combination

I have a scenario to iterate up session_number column for related user_name. If a user created a session before I'll iterate up the last session_number but if a user created session for the first time session_number should start from 1. I tried to illustrate on below. Right now I handle this by using logic but try to find more elegant way to do that in SqlAlchemy.
id - user_name - session_number
1 user_1 1
2 user_1 2
3 user_2 1
4 user_1 3
5 user_2 2
Here is my python code of the table. My database is PostgreSQL and I'm using alembic to upgrade tables. Right now it continues to iterate up the session_number regardless user_name.
class UserSessions(db.Model):
__tablename__ = 'user_sessions'
id = db.Column(db.Integer, primary_key=True, unique=True)
username = db.Column(db.String, nullable=False)
session_number = db.Column(db.Integer, Sequence('session_number_seq', start=0, increment=1))
created_at = db.Column(db.DateTime)
last_edit = db.Column(db.DateTime)
__table_args__ = (
db.UniqueConstraint('username', 'session_number', name='_username_session_number_idx_'),
)
I've searched on the internet for this situation but those were not like my problem. Is it possible to achieve this with SqlAlchemy/PostgreSQL actions?
First, I do not know of any "pure" solution for this situation by using either SqlAlchemy or Postgresql or a combination of the two.
Although it might not be exactly the solution you are looking for, I hope it will give you some ideas.
If you wanted to calculate the session_number for the whole table without it being stored, i would use the following query or a variation of thereof:
def get_user_sessions_with_rank():
expr = (
db.func.rank()
.over(partition_by=UserSessions.username, order_by=[UserSessions.id])
.label("session_number")
)
subq = db.session.query(UserSessions.id, expr).subquery("subq")
q = (
db.session.query(UserSessions, subq.c.session_number)
.join(subq, UserSessions.id == subq.c.id)
.order_by(UserSessions.id)
)
return q.all()
Alternatively, I would actually add a column_property to the model compute it on the fly for each instance of UserSessions. it is not as efficient in calculation, but for queries filtering by specific user it should be good enough:
class UserSessions(db.Model):
__tablename__ = "user_sessions"
id = db.Column(db.Integer, primary_key=True, unique=True)
username = db.Column(db.String, nullable=False)
created_at = db.Column(db.DateTime)
last_edit = db.Column(db.DateTime)
# must define this outside of the model definition because of need for aliased
US2 = db.aliased(UserSessions)
UserSessions.session_number = db.column_property(
db.select(db.func.count(US2.id))
.where(US2.username == UserSessions.username)
.where(US2.id <= UserSessions.id)
.scalar_subquery()
)
In this case, when you query for UserSessions, the session_number will be fetched from the database, while being None for newly created instances.

SqlAlchemy difference of timestamps

I have a table to log user actions. I dont have different columns for different action types yet for my newest task, I have to find the average time difference inbetween some actions.
class UserAction(db.Model):
__tablename__ = "user_action"
id = Column(Integer, nullable=False, primary_key=True)
user_id = Column(Integer, ForeignKey('user.id))
case_id = Column(Integer, ForeignKey('case.id'))
category_id = Column(Integer)
action_time = Column(DateTime(), server_default=func.now())
So I want to do something like,
x = session.query(func.avg(UserAction.action_time)).filter(UserAction.category_id == 1).all()
y = session.query(func.avg(UserAction.action_time)).filter(UserAction.category_id == 2).all()
dif = x - y
or
session.query(func.avg(func.justify_hours(timestamp_column1) - func.justify_hours(timestamp_column1))).all()
I can create a new table to log each case's actions' in different columns to do it like thw first way but that wont be efficient. But if i do it like the second way, the output won't be correct becasue not all actions are done by all cases. So im a little bit stuck. How can i achieve this?
Thanks

Using SQLAlchemy, Accessing Latest Value for Foreign Key as Attribute

I'm sure I'm wording this improperly, mainly because I'm not quite sure how I'm looking to achieve my goal here...
I've got an SQL Alchemy db class that, among other things, stores a value for the price of a commodity. I need to do two things with this price:
Access the latest price of a Commodity at any given moment
Access historical Commodity.price changes
Currently, my models look like this:
class Commodity(Base):
# Create Tablename
__tablename__ = "commodities"
# Defaults
id = Column(Integer, primary_key=True, autoincrement=True)
created = Column(DateTime, default=datetime.now)
updated = Column(DateTime, onupdate=datetime.now)
# Uniques
name = Column(String(), unique=True)
price = ???
class Price(Base):
# Create Tablename
__tablename__ = "prices"
# Defaults
id = Column(Integer, primary_key=True, autoincrement=True)
# Uniques
date = Column(DateTime, default=datetime.now)
commodity = Column(ForeignKey(Commodity))
price = Column(Integer())
In the Commodity class you'll note I have a ??? value as a placeholder for the price. I'd like to be able to access the price as Commodity.price in a way that would give me the latest value for that Commodity, identical to the following query:
Price.query.order_by('updated desc').limit(1)
I'm very new to SQL Alchemy, and appreciate that my approach may be wrong in general. Any help would be much-appreciated.
With my current understanding, I am planning to create an entry in the prices table for each update and simply update the Commodity.price value during the process. It seems a bit more elegant to use a function to get the Commodity.price via the prices table though. Is such an approach possible/recommended?
EDIT 1:
Essentially, I'm wondering how to do something like this:
class Commodity(Base):
...
price = Column(ForeignKey(Price.query.order_by('updated desc').limit(1)))
Just to clarify, this isn't (to my understanding) a one-to-many relationship but, rather, a many-to-many.

Cannot access column in PostgreSQL table from Python model

I have defined a python class "Students", like this:
class Students(DeclarativeBase):
__tablename__ = 'students'
id_ = Column('id', Integer, primary_key=True)
name = Column('nombre', Unicode(50))
date_of_birth = Column(Date)
If I do select * from students, I can see all of these columns plus a few more, namely: _created and _updated.
I need to use the values stored in the columns _created and _updated. So I try to access them like this:
#get student with id = 1
>>> s = dbs.query(Students).get(1)
# print its name
>>> print(s.name)
Richard
# try to print when it was created
>>> print (s._created)
AttributeError: 'Students' object has no attribute '_created'
Of course I get that message because the attribute _created is not defined in the model.
How can I access the value stored in the table Students even though it is not an attribute of the class Student?
SQLAlchemy needs the definition of each column it will access. (There are ways to auto-discover by reflecting the database, but explicit is better than implicit.) Add the column definitions to the model. I'm assuming they're DateTimes. You can use default= and onupdate= to provide new values when a row is inserted or updated.
class Student(Base):
__tablename__ = 'student'
id = Column('id_', Integer, primary_key=True)
# other columns...
created = Column('_created', DateTime, nullable=False, default=datetime.utcnow)
updated = Column('_updated', DateTime, onupdate=datetime.utcnow)

Fastest way to insert object if it doesn't exist with SQLAlchemy

So I'm quite new to SQLAlchemy.
I have a model Showing which has about 10,000 rows in the table. Here is the class:
class Showing(Base):
__tablename__ = "showings"
id = Column(Integer, primary_key=True)
time = Column(DateTime)
link = Column(String)
film_id = Column(Integer, ForeignKey('films.id'))
cinema_id = Column(Integer, ForeignKey('cinemas.id'))
def __eq__(self, other):
if self.time == other.time and self.cinema == other.cinema and self.film == other.film:
return True
else:
return False
Could anyone give me some guidance on the fastest way to insert a new showing if it doesn't exist already. I think it is slightly more complicated because a showing is only unique if the time, cinmea, and film are unique on a showing.
I currently have this code:
def AddShowings(self, showing_times, cinema, film):
all_showings = self.session.query(Showing).options(joinedload(Showing.cinema), joinedload(Showing.film)).all()
for showing_time in showing_times:
tmp_showing = Showing(time=showing_time[0], film=film, cinema=cinema, link=showing_time[1])
if tmp_showing not in all_showings:
self.session.add(tmp_showing)
self.session.commit()
all_showings.append(tmp_showing)
which works, but seems to be very slow. Any help is much appreciated.
If any such object is unique based on a combination of columns, you need to mark these as a composite primary key. Add the primary_key=True keyword parameter to each of these columns, dropping your id column altogether:
class Showing(Base):
__tablename__ = "showings"
time = Column(DateTime, primary_key=True)
link = Column(String)
film_id = Column(Integer, ForeignKey('films.id'), primary_key=True)
cinema_id = Column(Integer, ForeignKey('cinemas.id'), primary_key=True)
That way your database can handle these rows more efficiently (no need for an incrementing column), and SQLAlchemy now automatically knows if two instances of Showing are the same thing.
I believe you can then just merge your new Showing back into the session:
def AddShowings(self, showing_times, cinema, film):
for showing_time in showing_times:
self.session.merge(
Showing(time=showing_time[0], link=showing_time[1],
film=film, cinema=cinema)
)

Categories

Resources