I'm very sorry but I absolutely forgot to mention a problem with primary keys.
As i said, all objects in the database have id, start and end primary keys. CREATE TABLE parent ( id SERIAL NOT NULL, start TIMESTAMP WITHOUT TIME ZONE NOT NULL, end TIMESTAMP WITHOUT TIME ZONE NOT NULL, data VARCHAR, child_id INTEGER, PRIMARY KEY (id, start, end) ) CREATE TABLE child ( id SERIAL NOT NULL, start TIMESTAMP WITHOUT TIME ZONE NOT NULL, end TIMESTAMP WITHOUT TIME ZONE NOT NULL, data VARCHAR, PRIMARY KEY (id, start, end) ) Using single table versioning pattern i'm stuck with relationship definition: 1. Relation always should relate to only "current" rows 2. Relation is represented as a single scalar column (child_id in this example), without extra start and end foreign key columns. This is my models: class Parent(Base): __tablename__ = 'parent' id = Column(Integer, primary_key=True, autoincrement=True) start = Column(DateTime, primary_key=True) end = Column(DateTime, primary_key=True) data = Column(String) child_n = Column(Integer, ForeignKey('child.n')) child = relationship("Child", backref=backref('parent', uselist=False)) class Child(Versioned, Base): __tablename__ = 'child' id = Column(Integer, primary_key=True, autoincrement=True) start = Column(DateTime, primary_key=True) end = Column(DateTime, primary_key=True) data = Column(String) Of course this models definition gives me various errors about primary and foreign keys, because foreign key must consist of (id, start, end). But, as i said, we consider that relation always targets to current rows only. We are avoiding cascade re-assigning of changed children to parents. "primaryjoin" can be used for that: relation(..., primaryjoin="and_(Parent.child_id=Child.id, func.now().between(Child.start, Child.end))" Something like that. I tried that approach and it did not work for me (maybe i did not configured mappers or relations correctly — i was able to read, but not write.) So, the point is that i do not know how to make it work in a way that: 1. when reading, relation is joined on func.now() between start and end 2. when writing to this relation new version of child is created with new start and end, *preserving id.* Let me show data in the tables to explain myself better: Parent id | start | end | data | child_id 1 | 2018-01-01 | 2018-01-11 | c1 | 1 # -> this should reference to child with id=1 and func.now() between start and end Child id | start | end | data | 1 | 2018-01-01 | 2018-01-02 | child 1 | 1 | 2018-01-02 | 2018-01-11 | child 1.1 | # -> this row considered current if func.now() is 2018-01-05 for example I want to avoid declaring complex code/join conditions, that is why i thought about using views or dual-tables-versioning. среда, 5 декабря 2018 г., 18:13:11 UTC+3 пользователь Mike Bayer написал: > > On Wed, Dec 5, 2018 at 2:42 AM Stanislav Lobanov <n101...@gmail.com > <javascript:>> wrote: > > > > First of all, thank you for such a detailed answer. Thank you for > sharing your experience and continued support. > > > > Now i understand that using database views is not the best approach. In > the previous project we used an approach with Query objects that were using > WHERE statements to filter needed objects and it worked well. > > > > The problem i want to workaround is that using primary key of (id, > start, end) prevents me from using (lazy loaded) relations out of the box. > > > > Using examples from official sqlalchemy documentation, please think of > Parent and Child as a related business entities: > > > > class Parent(Base): > > __tablename__ = 'parent' > > id = Column(Integer, primary_key=True) > > child_id = Column(Integer, ForeignKey('child.id')) > > child = relationship("Child", back_populates="parents") > > > > class Child(Base): > > __tablename__ = 'child' > > id = Column(Integer, primary_key=True) > > parents = relationship("Parent", back_populates="child") > > > > > > > > There are object relational impedance mismatch: one Parent business > entity can have two Childs. Three business objects. But the database can > have multiple versions of parent or any of children via compound primary > key of (id, start, end) where start and end is a date range. > > OK so just to confirm the first assumption, in your code you only want > to see one Child at a time, that is parent.child is scalar and has > just one date range, right? > > > > > > > My problem is that Children with pk (1, 2018-01-01, 2018-01-02) and (1, > 2018-01-02, 2018-01-03) is in fact one business entity represented as two > model instances in sqlalchemy, so i can non figure out how can i force > relationships between "current" objects to work. > > > > I tried to specify additional conditions on relationships and it worked > for read operations, but failed for write operations. > > With the filtered approach, whether you do it in a view or in the > query, there's just one parent.child object, you just write to it. if > you're talking about the process by which when you write to > parent.child, a new row in the DB replaces it and the row you just saw > goes into "history" , we have a lot of examples of how to do that too > as I had another job for a long time *after* the previous job where we > did everything that way :). Otherwise please be more specific what > "failed for write operations" means. > > > > > > > So i thought i can overcome this situation by using database views and > force sqlalchemy to think that there is only one "current" object at a > time, also for relationships. > > > > Can you give me advice of how to configure sqlalchemy the right way for > this situation? > > > > Is it good idea to use second versioning example, where two tables > created per object (primary and historical table)? > > the tradeoffs with single versioned table vs. table plus historical > have to do with how you want to query the data. when you use a > single table, you have the advantage that you can change the date > range you are injecting into the WHERE clause and you can then load up > a full model in Python that represents a historical snapshot. OTOH > if you just need the "historical" data for an occasional "archive" > view then it may be more convenient to dump changes into a separate > table, and here in Openstack I argue they shouldn't even be doing > that, just dump archive stuff to a file (because we never use our > historical data and it fills up databases with useless crap). in any > case, if you want to really be thorough, you can disable UPDATE > statements on the table in question either through grants or triggers, > since you want to only be doing INSERTs to the table. > > > > > P.S.: sorry for my bad english, it is not on my tongue. > > > > вторник, 4 декабря 2018 г., 15:36:42 UTC+3 пользователь Stanislav > Lobanov написал: > >> > >> Hello. > >> > >> I have a table with schema: > >> > >> name: users > >> fields: id int, name text, start datetime, end datetime > >> primary key: id, start, end > >> > >> This is kind of a historical table, where each row defines separate > "historical version" of an object. There are a single business User entity > (model) with possibly many historical versions. > >> > >> Such table structure makes it very hard to define relationships and > work with related objects. Also it is hard to work with "current" version > of User entity, because to retreive it we need to query it with "now() > between start and end" constraint. > >> > >> So i thought that maybe i can create a view for that table that will > hold only current versions and map that view onto User entity to hide all > historical complexities and compound PK from sqlalchemy. > >> > >> The question: is it possible to implement a mapping that will read from > view but write into real table? > >> > >> For example, view can have fields id (pk) and name. > >> > >> I know that there are great examples of versioning with sqlalchemy but > i want to hide non-functional implementation details from my business > code/entities with view. > >> > >> Thanks! > > > > -- > > 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 sqlalchemy+...@googlegroups.com <javascript:>. > > To post to this group, send email to sqlal...@googlegroups.com > <javascript:>. > > Visit this group at https://groups.google.com/group/sqlalchemy. > > For more options, visit https://groups.google.com/d/optout. > -- 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 sqlalchemy+unsubscr...@googlegroups.com. To post to this group, send email to sqlalchemy@googlegroups.com. Visit this group at https://groups.google.com/group/sqlalchemy. For more options, visit https://groups.google.com/d/optout.