Hello Mike,

Thank you for your response! I have currently constructed the ORM 
implementation that you suggested in your response. Here's the code:

def _rebuild_non_interval_query_for_group_by(self, session, query):
    from sqlalchemy import table, column, select
    from sqlalchemy.orm import aliased
    from collections import defaultdict, OrderedDict

    post_metadata = table(
        "post_metadata", column("post_id"), column("metadata_value")
    )
    campaign_metadata = table(
        "campaign_metadata", column("campaign_id"), column("metadata_value")
    )
    asset_metadata = table(
        "asset_metadata", column("asset_id"), column("metadata_value")
    )

    vw_asset = table("vw_asset", column("id"))
    vw_campaign = table("vw_campaign", column("id"))
    vw_post = table("vw_post", column("id"))

    METADATA_PRIMARY_TABLE_MAP = {
        asset_metadata.name: vw_asset,
        campaign_metadata.name: vw_campaign,
        post_metadata.name: vw_post,
    }

    METADATA_NAME_TABLE_MAP = {
        asset_metadata.name: asset_metadata,
        campaign_metadata.name: campaign_metadata,
        post_metadata.name: post_metadata,
    }

    primary_tables = set()
    metadata_columns_count = defaultdict(int)
    metadata_alias = OrderedDict()
    columns = []
    for c in query.c._all_columns:
        if c.name == "metadata_value":
            parent_column = list(c.base_columns)[0]
            table = parent_column.table
            primary_tables.add(METADATA_PRIMARY_TABLE_MAP[table.name])
            metadata_columns_count[METADATA_NAME_TABLE_MAP[table.name]] += 1
            alias_number = metadata_columns_count[METADATA_NAME_TABLE_MAP[
table.name]]
            alias_name = "{}_{}".format(table.name, alias_number)
            alias = aliased(parent_column.table, alias_name)
            metadata_alias[alias_name] = alias
            column = alias.c.metadata_value.label(
                "{}_{}_{}".format(table.name, alias_number, "metadata_value"
)
            )
            columns.append(column)
        else:
            columns.append(c)

    # start constructing query
    non_interval_query = session.query(*columns).select_from(*primary_tables
)

    for alias_name, alias in metadata_alias.items():
        object_type = self._get_object_type_from_metadata_name(alias.
original.name)
        non_interval_query = (
            non_interval_query
            .join(
                alias,
                getattr(alias.c, "{}_id".format(object_type)) == 
METADATA_PRIMARY_TABLE_MAP[alias.original.name].c.id
            )
        )

    non_interval_query = non_interval_query.subquery("non_interval_query")

    return non_interval_query



The "metadata_alias" values are [('post_metadata_1", alias), 
('post_metadata_2', alias)] - the alias correspond to the post_metadata_1 
and post_metadata_2 alias in your example. However, when I reference these 
in the join, the aliased table names are not "post_metadata_1" or 
"post_metadata_2" - they're "post_metadata_3" and "post_metadata_4". I'm 
unable to figure out why there's a new join seemingly created instead of 
referencing the aliased tables that were passed in.

Here's the query that the above generates:

SELECT post_metadata_1.metadata_value AS post_metadata_1_metadata_value,
       post_metadata_2.metadata_value AS post_metadata_2_metadata_value,
       non_interval_query.created_at,
       non_interval_query.coalesce_1 \nFROM
  (SELECT post_metadata_3.metadata_value AS metadata_value, 
post_metadata_4.metadata_value 
AS metadata_value, vw_post.created_at AS created_at, coalesce(count(DISTINCT 
vw_post.id), :coalesce_2) AS coalesce_1 \nFROM vw_post
   JOIN post_metadata AS post_metadata_3 ON post_metadata_3.post_id = 
vw_post.id
   JOIN post_metadata AS post_metadata_4 ON post_metadata_4.post_id = 
vw_post.id \nWHERE post_metadata_3.metadata_value IN (:metadata_value_1, :
metadata_value_2)
   AND post_metadata_4.metadata_value IN (:metadata_value_3, :
metadata_value_4)
   AND vw_post.created_at >= :created_at_1
   AND vw_post.created_at <= :created_at_2
   AND post_metadata_3.schema_uid = :schema_uid_1
   AND post_metadata_3.metadata_name = :metadata_name_1
   AND post_metadata_4.schema_uid = :schema_uid_2
   AND post_metadata_4.metadata_name = :metadata_name_2
   AND vw_post.license_id IN (:license_id_1, :license_id_2)
   GROUP BY vw_post.created_at, post_metadata_3.metadata_value, 
post_metadata_4.metadata_value, vw_post.created_at) AS non_interval_query,
                                      vw_post
JOIN post_metadata AS post_metadata_1 ON post_metadata_1.post_id = vw_post.
id
JOIN post_metadata AS post_metadata_2 ON post_metadata_2.post_id = vw_post.
id;




On Tuesday, February 5, 2019 at 11:51:25 AM UTC-5, Ian Miller wrote:
Hello all - 

I am relatively new to using SQLAlchemy for more complex use cases. I am in 
the process of creating a time series query, but I am unable to reference a 
column by its alias at the top level of the query.

This is the query that I am trying to address that SQLAlchemy is currently 
generating:



SELECT non_interval_query.metadata_value AS 
non_interval_query_metadata_value,
       coalesce(sum(non_interval_query.coalesce_2), 0) AS coalesce_1,
       timestamp
FROM
  (SELECT generate_series(date_trunc('day', 
date('2019-01-06T00:00:00+00:00')), date_trunc('day', 
date('2019-01-12T00:00:00+00:00')), '1 day') AS timestamp) AS time_series
LEFT OUTER JOIN
  (SELECT post_metadata_1.metadata_value AS post_metadata_1_metadata_value,
          post_metadata_2.metadata_value AS post_metadata_2_metadata_value,
          vw_post.created_at AS vw_post_created_at,
          coalesce(count(DISTINCT vw_post.id), 0) AS coalesce_1
   FROM vw_post
   JOIN post_metadata AS post_metadata_1 ON post_metadata_1.post_id = 
vw_post.id
   JOIN post_metadata AS post_metadata_2 ON post_metadata_2.post_id = 
vw_post.id
   WHERE post_metadata_1.metadata_value IN ('<metadata_values>')
     AND post_metadata_2.metadata_value IN ('<metadata_value>')
     AND vw_post.created_at >= '2019-01-06T00:00:00+00:00'
     AND vw_post.created_at <= '2019-01-12T00:00:00+00:00'
     AND post_metadata_1.schema_uid = '<schema_uid>'
     AND post_metadata_1.metadata_name = '<metadata_name>'
     AND post_metadata_2.schema_uid = '<schema_uid>'
     AND post_metadata_2.metadata_name = '<metadata_name>'
     AND vw_post.license_id IN (<license_ids>)
   GROUP BY vw_post.created_at,
            post_metadata_1.metadata_value,
            post_metadata_2.metadata_value,
            vw_post.created_at) AS non_interval_query ON date_trunc('day', 
created_at) = timestamp;
You'll notice that "non_interval_query.metadata_value AS 
non_interval_query_metadata_value" specified at the beginning of the query 
is ambiguous due to the 2 "metadata_value" selects in the 
"non_interval_query" subquery. What I'm trying to do is have 2 selects at 
the top level - one for "non_interval_query.post_metadata_1_metadata_value" 
and one for "non_interval_query.post_metadata_2_metadata_value".


For reference, here is the code used to generate the above query:




def apply_date_group_by(self, session, query, range_gb_params):
    field_name = self.db.get("column")
    model = self._object.get("model")

    if not field_name or not model:
        raise ValueError("Invalid date group by")

    gb_column = self._build_column()
    interval = range_gb_params.get("interval")
    interval_type = range_gb_params.get("interval_type")

    time_series = func.generate_series(
        func.date_trunc(interval_type, func.date(range_gb_params["start"])),
        func.date_trunc(interval_type, func.date(range_gb_params["end"])),
        interval,
    ).label("timestamp")

    ts_column = column("timestamp")

    time_series_query = session.query(time_series).subquery("time_series")
    non_interval_query = query.subquery("non_interval_query")
    # have to replace the original gb_column with the 'timestamp' column
    # in order to properly merge the dataset into the time series dataset
    non_gb_columns, gbs = self._prepare_non_gb_columns(
        ts_column, gb_column, non_interval_query.columns
    )

    # construct query with correct position passed in from `range_gb_params`
    query_position = range_gb_params.get("query_index_position", 0)
    non_gb_columns.insert(query_position, ts_column)

    date_gb_query = session.query(*non_gb_columns).select_from(
        time_series_query.outerjoin(
            non_interval_query,
            func.date_trunc(interval_type, column(field_name)) == ts_column,
        )
    )

    if gbs:
        date_gb_query = date_gb_query.group_by(*gbs)

    return date_gb_query.order_by(ts_column)


Any help on this would be greatly appreciated! 


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

Reply via email to