Hello,

Here's my simple model (For simplification, consider Face as a Billboard) :

+-----+          +-----------+       +----------+
|Face |<......   |Campaign   |   ...>|TimePeriod|
+-----+      .   +-----------+   .   +----------+
|code |      .   |time_period|....   |start_time|
+-----+      .   +-----------+       +----------+
             ....|faces      |       |end_time  |
                 +-----------+       +----------+

One way to read this model is : A campaign can book multiple faces during a 
certain period of time.

What I want to do is get all the available Faces for a given period of time, to 
see what faces can I book for a new campaign that longs for that particular 
period of time. I would typically have a Face.get_available(time_period) class 
method that does the job. This method would look for all the faces that don't 
have an ongoing booking. My question is : how to write such a method ? 

Here's how I figured it out (couldn't get it to work) :

class Face(Entity):
    using_options(tablename="faces")
    @classmethod
    def get_available(self,time_period):
        """
        Return faces that are not booked (not in any campaign) during the given 
time_period.
        """
        # start_date_cond     = TimePeriod.start_date > time_period.end_date
        # end_date_cond       = TimePeriod.end_date   < time_period.start_date
        # available_periods   = 
Campaign.time_period.has(or_(start_date_cond,end_date_cond))
        # unavailable_periods = not(available_periods)

        # I am pretty sure that the time conditions are good.
        # Here's a good way to convince yourself (read from bottom to top) :

        # L1                      
0-----------------------------------------------
        # L2 ------------------------------------------|
        # L3 
0--------------------[--------------------]-------------------------->
        
        # L3 represents the desired period (passed as argument) going from "[" 
to "]"
        # place the start date of the booked face anywhere on L2
        # place the end date of the booked face anywhere on L1
        # of course, end date must be after start date...
        # Anyway you do it, your face isn't available for the period of time in 
L3.     

        start_date_cond     = TimePeriod.start_date <= time_period.end_date
        end_date_cond       = TimePeriod.end_date   >= time_period.start_date
        unavailable_periods = 
Campaign.time_period.has(and_(start_date_cond,end_date_cond))
        # I am not sure about what follows...
        filter_cond         = not_(unavailable_periods)
        join_clause         = Campaign.faces

        return Face.query.filter(filter_cond).outerjoin(join_clause).all()

        
This code returns only faces that have already been booked before or have a 
future booking, and are free for the moment. But faces with no bookings at all 
are not returned. This may be due to an incorrect outerjoin ? (I also tried a 
simple join with no success)

Here's the generated sql for one query : 

2010-10-12 19:34:22,837 INFO sqlalchemy.engine.base.Engine.0x...f4ec 
SELECT faces.id AS faces_id 
FROM campaigns LEFT OUTER JOIN campaigns_faces__faces AS 
campaigns_faces__faces_1 ON campaigns.id = 
campaigns_faces__faces_1.campaigns_id 
LEFT OUTER JOIN faces ON faces.id = campaigns_faces__faces_1.faces_id 
WHERE NOT (EXISTS (SELECT 1 
                  FROM time_periods 
                  WHERE campaigns.time_period_id = time_periods.id 
                  AND time_periods.start_date <= %(start_date_1)s 
                  AND time_periods.end_date >= %(end_date_1)s))

2010-10-12 19:34:22,837 INFO sqlalchemy.engine.base.Engine.0x...f4ec 
{'start_date_1': datetime.date(2010, 10, 30), 'end_date_1': datetime.date(2010, 
10, 20)}
[<Face id=1 at 0x932218c  >]


Any help would be very appreciated.

Y.Chaouche

PS : and please, don't give me that lame "it's elixir" excuse. The question is 
about how to construct the proper query for the desired operation in a 
sqlalchemy way. Elixir is only another Declarative approach + mapping, that's 
it.


      

-- 
You received this message because you are subscribed to the Google Groups 
"sqlalchemy" group.
To post to this group, send email to sqlalch...@googlegroups.com.
To unsubscribe from this group, send email to 
sqlalchemy+unsubscr...@googlegroups.com.
For more options, visit this group at 
http://groups.google.com/group/sqlalchemy?hl=en.

# ORM
from sqlalchemy.sql.expression import * 
from sqlalchemy.orm            import reconstructor
from elixir                    import *
# stdlib
from datetime                  import date
import sys

class TimePeriod(Entity):
    """
    """
    using_options(tablename="time_periods")

    start_date = Field(Date())
    end_date   = Field(Date())
    name       = Field(Unicode())

class Campaign(Entity):
    using_options(tablename="campaigns")
    time_period = ManyToOne("TimePeriod")
    faces       = ManyToMany("Face")

class Face(Entity):
    using_options(tablename="faces")
    @classmethod
    def get_available_faces(self,time_period):
        """
        Return faces that are not booked during the given time_period.
        """
        # start_date_cond     = TimePeriod.start_date > time_period.end_date
        # end_date_cond       = TimePeriod.end_date   < time_period.start_date
        # available_periods   = Campaign.time_period.has(or_(start_date_cond,end_date_cond))

        start_date_cond     = TimePeriod.start_date <= time_period.end_date
        end_date_cond       = TimePeriod.end_date   >= time_period.start_date
        unavailable_periods = Campaign.time_period.has(and_(start_date_cond,end_date_cond))
        filter_cond         = not_(unavailable_periods)
        join_clause         = Campaign.faces

        return Face.query.filter(filter_cond).outerjoin(join_clause).all()


class DB:
    def __init__(self,echo=False):
        self.metadata           = metadata
        self.metadata.bind      = "postgresql engine's url"
        self.metadata.bind.echo = echo
        self.session            = session
        self.session.bind       = metadata.bind
        # Use elixir's entities collection
        setup_all()
        self.drop_all()
        create_all()
        self.metadata.create_all()
        self.session.commit()

    def drop_all(self):
        """
        """
        for table_name in metadata.tables.keys():
            self.session.execute("DROP TABLE IF EXISTS %s CASCADE" % table_name)
        self.commit()

    def commit(self):
        self.session.commit()

    def flush(self):
        self.session.flush()

echo        = len(sys.argv) > 1 and sys.argv[1] != "False" or False
db          = DB(echo=echo)

start_date1 = date(year=2010,month=10,day=1)
end_date1   = date(year=2010,month=10,day=15)

start_date2 = date(year=2010,month=10,day=10)
end_date2   = date(year=2010,month=10,day=19)

start_date3 = date(year=2010,month=10,day=20)
end_date3   = date(year=2010,month=10,day=30)

face1       = Face()
face2       = Face()
face3       = Face()

tp1         = TimePeriod(start_date = start_date1,end_date = end_date1)
tp2         = TimePeriod(start_date = start_date2,end_date = end_date2)
tp3         = TimePeriod(start_date = start_date3,end_date = end_date3)

campaign1   = Campaign(time_period=tp1)
campaign1.faces.append(face1)
db.commit()

print Face.query.all()
print Campaign.query.all()
print TimePeriod.query.all()
print "*"*40
print Face.get_available_faces(tp2)
print Face.get_available_faces(tp3)

Reply via email to