Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Generate sql with subquery as a column in select statement using SQLAlchemy

Is there a way to make SQLAlchemy generate a query with a custom column that is a subquery that correlates with current row:

SELECT
 tab1.id,
 tab1.col1, 
 ...,
 (
     SELECT count(1) FROM tab2 
     WHERE tab2.tab1_id = tab1.id
     GROUP BY tab2.col1
 ) as cnt
FROM tab1
WHERE ...
LIMIT 100

using the ORM API?

session.query(Tab1, ?(subquery for additional column)?).filter(...).limit(100)

I'm using PostgreSQL 9.3 and old version of SQLAlchemy 0.9.8

like image 207
Antigluk Avatar asked Apr 27 '17 09:04

Antigluk


People also ask

Can a subquery have a column name in SQL?

The select list of a subquery introduced with a comparison operator can include only one expression or column name (except that EXISTS and IN operate on SELECT * or a list, respectively). If the WHERE clause of an outer query includes a column name, it must be join-compatible with the column in the subquery select list.

How to use a subquery in the FROM clause of the select?

You can use a subquery in the FROM clause of the SELECT statement as follows: SELECT * FROM (subquery) AS table_name Code language: SQL (Structured Query Language) (sql) In this syntax, the table alias is mandatory because all tables in the FROM clause must have a name.

How to generate an exists subquery in SQLAlchemy?

The SQL EXISTS keyword is an operator that is used with scalar subqueries to return a boolean true or false depending on if the SELECT statement would return a row. SQLAlchemy includes a variant of the ScalarSelect object called Exists, which will generate an EXISTS subquery and is most conveniently generated using the SelectBase.exists () method.

What are the components of a subquery in SQL?

A subquery nested in the outer SELECT statement has the following components: A regular SELECT query including the regular select list components. A regular FROM clause including one or more table or view names. An optional WHERE clause. An optional GROUP BY clause.


2 Answers

If you need this often, and/or the count is an integral part of your Tab1 model, you should use a hybrid property such as described in the other answer. If on the other hand you need this just for a single query, then you could just create the scalar subquery using Query.label(), or Query.as_scalar():

count_stmt = session.query(func.count(1)).\
    filter(Tab2.tab1_id == Tab1.id).\
    group_by(Tab2.col1).\
    label('cnt')

session.query(Tab1, count_stmt).filter(...).limit(100)

The subquery will automatically correlate what it can from the enclosing query.

like image 162
Ilja Everilä Avatar answered Oct 28 '22 13:10

Ilja Everilä


You can do this, but it works in a quite different way to how you have written it. You can create a property of Tab1 which depends on the relationship to tab2 (assuming that tab2.tab1_id is a foreign key, which it should be.

Your models look like this:

class Parent(Base):
    __tablename__ = 'parent'
    id = Column(Integer, primary_key=True)
    children = relationship("Child")

class Child(Base):
    __tablename__ = 'child'
    id = Column(Integer, primary_key=True)
    parent_id = Column(Integer, ForeignKey('parent.id'))

as per the docs on relationships

Then you can add something like

@hybrid_property
def number_of_children(self):
    if self.children:
        return len(self.children)
    return 0

@number_of_children.expression
def number_of_children(cls):
    return (select([func.count(Child.id)])
            .where(Child.cover_id == cls.id))

to the Parent model, as per this answer and more docs.

Once you've done this, you can filter on this property the same as any other column-based one.

like image 3
jwg Avatar answered Oct 28 '22 13:10

jwg