1

単一のクエリで、固定された一連の行と、サブクエリで見つかった他の行をフェッチしようとしています。私の問題は、SQLAlchemy コードによって生成されたクエリが正しくないことです。

問題は、SQLAlchemy によって生成されたクエリが次のようになることです。

SELECT tbl.id AS tbl_id
FROM tbl
WHERE tbl.id IN
(
SELECT t2.id AS t2_id
FROM tbl AS t2, tbl AS t1
WHERE t2.id =
(
SELECT t3.id AS t3_id
FROM tbl AS t3, tbl AS t1
WHERE t3.id < t1.id ORDER BY t3.id DESC LIMIT 1 OFFSET 0
)
AND t1.id IN (4, 8)
)
OR tbl.id IN (0, 8)

ただし、正しいクエリには 2 番目を含めないでくださいtbl AS t1(このクエリの目標は、ID 0 と 8、および 4 と 8 の直前の ID を選択することです)。

残念ながら、SQLAlchemy で正しいものを生成する方法が見つかりません (以下のコードを参照)。

より単純なクエリで同じ結果を達成するための提案も歓迎します (ただし、効率的である必要があります。いくつかのバリアントを試してみましたが、実際のユース ケースではかなり遅いものもありました)。

クエリを生成するコード:

from sqlalchemy import create_engine, or_
from sqlalchemy import Column, Integer, MetaData, Table
from sqlalchemy.orm import sessionmaker

engine = create_engine('sqlite:///:memory:', echo=True)
meta = MetaData(bind=engine)
table = Table('tbl', meta, Column('id', Integer))
session = sessionmaker(bind=engine)()
meta.create_all()

# Insert IDs 0, 2, 4, 6, 8.
i = table.insert()
i.execute(*[dict(id=i) for i in range(0, 10, 2)])
print session.query(table).all()
# output: [(0,), (2,), (4,), (6,), (8,)]

# Subquery of interest: look for the row just before IDs 4 and 8.
sub_query_txt = (
        'SELECT t2.id '
        'FROM tbl t1, tbl t2 '
        'WHERE t2.id = ( '
        ' SELECT t3.id from tbl t3 '
        ' WHERE t3.id < t1.id '
        ' ORDER BY t3.id DESC '
        ' LIMIT 1) '
        'AND t1.id IN (4, 8)')
print session.execute(sub_query_txt).fetchall()
# output: [(2,), (6,)]

# Full query of interest: get the rows mentioned above, as well as more rows.
query_txt = (
        'SELECT * '
        'FROM tbl '
        'WHERE ( '
        ' id IN (%s) '
        'OR id IN (0, 8))'
        ) % sub_query_txt
print session.execute(query_txt).fetchall()
# output: [(0,), (2,), (6,), (8,)]

# Attempt at an SQLAlchemy translation (from innermost sub-query to full query).
t1 = table.alias('t1')
t2 = table.alias('t2')
t3 = table.alias('t3')
q1 = session.query(t3.c.id).filter(t3.c.id < t1.c.id).order_by(t3.c.id.desc()).\
             limit(1)
q2 = session.query(t2.c.id).filter(t2.c.id == q1, t1.c.id.in_([4, 8]))
q3 = session.query(table).filter(
                               or_(table.c.id.in_(q2), table.c.id.in_([0, 8])))
print list(q3)
# output: [(0,), (6,), (8,)]
4

3 に答える 3

2

あなたが見逃しているのは、最も内側のサブクエリと次のレベルアップの間の相関関係です。相関がない場合、SQLAlchemy はt1最も内側のサブクエリにエイリアスを含めます。

>>> print str(q1)
SELECT t3.id AS t3_id 
FROM tbl AS t3, tbl AS t1 
WHERE t3.id < t1.id ORDER BY t3.id DESC
 LIMIT ? OFFSET ?
>>> print str(q1.correlate(t1))
SELECT t3.id AS t3_id 
FROM tbl AS t3 
WHERE t3.id < t1.id ORDER BY t3.id DESC
 LIMIT ? OFFSET ?

tbl AS t1がクエリから欠落していることに注意してください。.correlate()メソッドのドキュメントから:

指定された FROM 句をそれを囲んでいる Query または select() の句に関連付ける Query 構造を返します。

したがって、t1は囲んでいるクエリの一部であると見なされ、クエリ自体にはリストされません。

これで、クエリが機能します。

>>> q1 = session.query(t3.c.id).filter(t3.c.id < t1.c.id).order_by(t3.c.id.desc()).\
...              limit(1).correlate(t1)
>>> q2 = session.query(t2.c.id).filter(t2.c.id == q1, t1.c.id.in_([4, 8]))
>>> q3 = session.query(table).filter(
...                                or_(table.c.id.in_(q2), table.c.id.in_([0, 8])))
>>> print list(q3)
2012-10-24 22:16:22,239 INFO sqlalchemy.engine.base.Engine SELECT tbl.id AS tbl_id 
FROM tbl 
WHERE tbl.id IN (SELECT t2.id AS t2_id 
FROM tbl AS t2, tbl AS t1 
WHERE t2.id = (SELECT t3.id AS t3_id 
FROM tbl AS t3 
WHERE t3.id < t1.id ORDER BY t3.id DESC
 LIMIT ? OFFSET ?) AND t1.id IN (?, ?)) OR tbl.id IN (?, ?)
2012-10-24 22:16:22,239 INFO sqlalchemy.engine.base.Engine (1, 0, 4, 8, 0, 8)
[(0,), (2,), (6,), (8,)]
于 2012-10-24T20:16:48.420 に答える
1

私はあなたが求めているクエリを理解していると確信しています。しかし、それを分解しましょう:

このクエリの目標は、ID 0 と 8、および 4 と 8 の直前の ID を選択することです。

2 種類のものを照会してから、それらを結合したいようです。そのための適切な演算子はunion. 簡単なクエリを実行し、最後にそれらを追加します。2 番目のビット「X の直前の ID」から始めます。

で開始する; 特定の値の前にあるすべての ID を見てみましょう。このために、テーブル自体を次のように結合します<

# select t1.id t1_id, t2.id t2_id from tbl t1 join tbl t2 on t1.id < t2.id;
 t1_id | t2_id 
-------+-------
     0 |     2
     0 |     4
     0 |     6
     0 |     8
     2 |     4
     2 |     6
     2 |     8
     4 |     6
     4 |     8
     6 |     8
(10 rows)

これにより、左が右よりも小さい行のペアがすべて得られます。それらすべての中で、可能な限り高い特定の t2_id の行が必要です。t2_id でグループ化し、最大の t1_id を選択します

# select max(t1.id), t2.id from tbl t1 join tbl t2 on t1.id < t2.id group by t2.id;
 max | id 
-----+-------
   0 |     2
   2 |     4
   4 |     6
   6 |     8
(4 rows)

を使用したクエリでlimitこれを実現できますが、通常は、代替手段が存在する場合は、この手法の使用を避けることをお勧めします。これは、データベースの実装間でパーティショニングが適切に移植可能なサポートを提供しないためです。Sqlite はこの手法を使用できますが、postgresql は気に入らず、「分析クエリ」と呼ばれる手法を使用します (標準化され、より一般的です)。MySQL はどちらもできません。ただし、上記のクエリは、すべての SQL データベース エンジンで一貫して機能します。

残りの作業は、inまたは他の同等のフィルタリング クエリを使用するだけであり、sqlalchemy で表現するのは難しくありません。ボイラープレート...

>>> import sqlalchemy as sa
>>> from sqlalchemy.orm import Query
>>> engine = sa.create_engine('sqlite:///:memory:')
>>> meta = sa.MetaData(bind=engine)
>>> table = sa.Table('tbl', meta, sa.Column('id', sa.Integer))
>>> meta.create_all()

>>> table.insert().execute([{'id':i} for i in range(0, 10, 2)])

>>> t1 = table.alias()
>>> t2 = table.alias()

>>> before_filter = [4, 8]

最初の興味深い点は、'max(id)' 式に名前を付けることです。これは、複数回参照し、サブクエリから取り出すために必要です。

>>> c1 = sa.func.max(t1.c.id).label('max_id')
>>> #                                ^^^^^^

クエリの「重労働」部分は、上記のエイリアスを結合し、グループ化して最大数を選択します

>>> q1 = Query([c1, t2.c.id]) \
...      .join((t2, t1.c.id < t2.c.id)) \
...      .group_by(t2.c.id) \
...      .filter(t2.c.id.in_(before_filter))

ユニオンを使用するため、適切な数のフィールドを生成するためにこれが必要です。これをサブクエリでラップし、関心のある唯一の列に射影します。これには、上記で付けた名前が付けられます。label()電話。

>>> q2 = Query(q1.subquery().c.max_id)
>>> #                          ^^^^^^

結合の残りの半分は、はるかに単純です。

>>> t3 = table.alias()
>>> exact_filter = [0, 8]
>>> q3 = Query(t3).filter(t3.c.id.in_(exact_filter))

あとは、それらを組み合わせるだけです。

>>> q4 = q2.union(q3)
>>> engine.execute(q4.statement).fetchall()
[(0,), (2,), (6,), (8,)]
于 2012-10-24T20:45:19.277 に答える
0

ここでの回答は私の問題を解決するのに役立ちましたが、私の場合は と の両方を使用する必要がcorrelate()ありましたsubquery():

# ...
subquery = subquery.correlate(OuterCorrelationTable).subquery()
filter_query = db.session.query(func.sum(subquery.c.some_count_column))
filter = filter_query.as_scalar() == as_many_as_some_param
# ...
final_query = db.session.query(OuterCorrelationTable).filter(filter)
于 2020-02-17T10:51:39.543 に答える