On 06/15/2016 09:53 PM, Kevin Murphy wrote:
Hi,

Is it possible to repeatedly add column expressions to a select object,
where the column expression involves outer joins? Ideally I'd like there
to be a several transformation functions that operate on the same select
object in a chain.

So far, the closest I've come is for the transformation functions to
accept and return both a select object (to which columns are appended)
and a join object (to which joins are appended), such that the joins are
added to the select at the end. It would be nice to be able to operate
on just the select object.

I'd like to mention another attempt at the original goal, which did not
raise an exception but generated invalid SQL (using SQLAlchemy 1.0.13).
 The relevant code snippet from the runnable example below is this:

            select_obj = select_obj.column(new_col).select_from(Join(
                table_obj, concept_alias,
                col == concept_alias.c.concept_id, isouter=True))

The resulting SQL contains a spurious `, main_table`fragment:

SELECT main_table.foo_concept_id, main_table.bar_concept_id,
concept_1.concept_name AS foo_concept_name, concept_2.concept_name AS
bar_concept_name
FROM main_table LEFT OUTER JOIN concept AS concept_1 ON foo_concept_id =
concept_1.concept_id*, main_table* LEFT OUTER JOIN concept AS concept_2
ON bar_concept_id = concept_2.concept_id

that SQL will occur if you use two JOIN objects separately in a query.




The full code follows. Note that this is a prototype of just one of a
series of transformation functions that would operate on the same select
object.

the issue is that your transform() does a select_from() which only adds another FROM clause to the query, in this case a second JOIN so we get the form of "SELECT .. FROM x JOIN y, x JOIN y2". For core select(), we don't have a generative join() method like ORM Query does, so you need to give it a finished join() object that includes everything at once:

def transform(metadata, table_name, select_obj):
"""Transform and return a Select object query by adding column expressions
    populated using outer joins. This Select object may be later passed to
    different varieties of transformation function.
    """
    table_obj = metadata.tables[table_name]
    concept_table_obj = metadata.tables['concept']
    j = None
    for col in select_obj.c:
        if col.name.endswith('_concept_id'):
            new_name = col.name.replace('_concept_id', '_concept_name')
            concept_alias = concept_table_obj.alias()
            new_col = concept_alias.c.concept_name.label(new_name)
            if j is None:
j = table_obj.outerjoin(concept_alias, col == concept_alias.c.concept_id)
            else:
j = j.outerjoin(concept_alias, col == concept_alias.c.concept_id)
            select_obj = select_obj.column(new_col)
    if j is not None:
        select_obj = select_obj.select_from(j)
    return select_obj







from sqlalchemy import MetaData, Table, Column, Integer, String, select,
ForeignKey
from sqlalchemy.dialects import postgresql
from sqlalchemy.sql.expression import Join

def setup():
    """ Create a `main_table` table, a query on it, and a related table that
    will later be joined to.
    """
    metadata = MetaData()
    concept = Table('concept', metadata,
                    Column('concept_id', Integer),
                    Column('concept_name', String(512)))
    main_table = Table('main_table', metadata,
                 Column('foo_concept_id', Integer,
ForeignKey('concept.concept_id')),
                 Column('bar_concept_id', Integer,
ForeignKey('concept.concept_id')))
    select_obj =
select().column(main_table.c.foo_concept_id).column(main_table.c.bar_concept_id).select_from(main_table)
    return metadata, select_obj

def transform(metadata, table_name, select_obj):
    """Transform and return a Select object query by adding column
expressions
    populated using outer joins. This Select object may be later passed to
    different varieties of transformation function.
    """
    table_obj = metadata.tables[table_name]
    concept_table_obj = metadata.tables['concept']
    for col in select_obj.c:
        if col.name.endswith('_concept_id'):
            new_name = col.name.replace('_concept_id', '_concept_name')
            concept_alias = concept_table_obj.alias()
            new_col = concept_alias.c.concept_name.label(new_name)
            select_obj = select_obj.column(new_col).select_from(Join(
                table_obj, concept_alias,
                col == concept_alias.c.concept_id, isouter=True))
    return select_obj

metadata, select_obj = setup()
select_obj = transform(metadata, 'main_table', select_obj)
print("select_obj.compile(dialect=postgresql.dialect()))

Thanks,
Kevin

--
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
<mailto:sqlalchemy+unsubscr...@googlegroups.com>.
To post to this group, send email to sqlalchemy@googlegroups.com
<mailto:sqlalchemy@googlegroups.com>.
Visit this group at https://groups.google.com/group/sqlalchemy.
For more options, visit https://groups.google.com/d/optout.

--
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.

Reply via email to