PostgreSQL
 sql >> Teknologi Basis Data >  >> RDS >> PostgreSQL

SQLAlchemy:mengelompokkan berdasarkan hari di beberapa tabel

SQL bekerja dengan dan mengembalikan data tabular (atau relasi, jika Anda lebih suka menganggapnya seperti itu, tetapi tidak semua tabel SQL adalah relasi). Apa artinya ini adalah bahwa tabel bersarang seperti yang digambarkan dalam pertanyaan bukanlah fitur yang umum. Ada beberapa cara untuk menghasilkan sesuatu dari jenis di Postgresql, misalnya menggunakan array JSON atau komposit, tetapi sangat mungkin untuk hanya mengambil data tabular dan melakukan bersarang dalam aplikasi. Python memiliki itertools.groupby() , yang cukup sesuai dengan tagihan, mengingat data yang diurutkan.

Error column "incoming.id" must appear in the GROUP BY clause... mengatakan bahwa non-agregat dalam daftar pilih, memiliki klausa, dll. harus muncul di GROUP BY klausa atau digunakan secara agregat, agar tidak memiliki nilai tak tentu . Dengan kata lain nilai harus diambil hanya dari beberapa baris dalam grup, karena GROUP BY memadatkan baris yang dikelompokkan menjadi satu baris , dan siapa pun dapat menebak dari baris mana mereka diambil. Implementasinya mungkin mengizinkan ini, seperti yang dilakukan SQLite dan MySQL dulu, tetapi standar SQL melarangnya. Pengecualian untuk aturan ini adalah ketika ada dependensi fungsional ; GROUP BY klausa menentukan non-agregat. Pikirkan gabungan antar tabel A dan B dikelompokkan berdasarkan A kunci utama. Tidak peduli baris mana dalam grup, sistem akan memilih nilai untuk A kolom dari, mereka akan sama karena pengelompokan dilakukan berdasarkan kunci utama.

Untuk mengatasi pendekatan 3 poin yang dimaksudkan secara umum, salah satu caranya adalah dengan memilih gabungan dari masuk dan keluar, yang diurutkan berdasarkan cap waktu mereka. Karena tidak ada hierarki warisan setup––karena mungkin tidak ada, saya tidak terbiasa dengan akuntansi––kembali menggunakan Core dan tupel hasil biasa membuat segalanya lebih mudah dalam kasus ini:

incoming = select([literal('incoming').label('type'), Incoming.__table__]).\
    where(Incoming.accountID == accountID)

outgoing = select([literal('outgoing').label('type'), Outgoing.__table__]).\
    where(Outgoing.accountID == accountID)

all_entries = incoming.union(outgoing)
all_entries = all_entries.order_by(all_entries.c.timestamp)
all_entries = db_session.execute(all_entries)

Kemudian untuk membentuk struktur bersarang itertools.groupby() digunakan:

date_groups = groupby(all_entries, lambda ent: ent.timestamp.date())
date_groups = [(k, [dict(ent) for ent in g]) for k, g in date_groups]

Hasil akhirnya adalah daftar 2-tupel tanggal dan daftar kamus entri dalam urutan menaik. Bukan solusi ORM, tetapi menyelesaikan pekerjaan. Contoh:

In [55]: session.add_all([Incoming(accountID=1, amount=1, description='incoming',
    ...:                           timestamp=datetime.utcnow() - timedelta(days=i))
    ...:                  for i in range(3)])
    ...:                  

In [56]: session.add_all([Outgoing(accountID=1, amount=2, description='outgoing',
    ...:                           timestamp=datetime.utcnow() - timedelta(days=i))
    ...:                  for i in range(3)])
    ...:                  

In [57]: session.commit()

In [58]: incoming = select([literal('incoming').label('type'), Incoming.__table__]).\
    ...:     where(Incoming.accountID == 1)
    ...: 
    ...: outgoing = select([literal('outgoing').label('type'), Outgoing.__table__]).\
    ...:     where(Outgoing.accountID == 1)
    ...: 
    ...: all_entries = incoming.union(outgoing)
    ...: all_entries = all_entries.order_by(all_entries.c.timestamp)
    ...: all_entries = db_session.execute(all_entries)

In [59]: date_groups = groupby(all_entries, lambda ent: ent.timestamp.date())
    ...: [(k, [dict(ent) for ent in g]) for k, g in date_groups]
Out[59]: 
[(datetime.date(2019, 9, 1),
  [{'accountID': 1,
    'amount': 1.0,
    'description': 'incoming',
    'id': 5,
    'timestamp': datetime.datetime(2019, 9, 1, 20, 33, 6, 101521),
    'type': 'incoming'},
   {'accountID': 1,
    'amount': 2.0,
    'description': 'outgoing',
    'id': 4,
    'timestamp': datetime.datetime(2019, 9, 1, 20, 33, 29, 420446),
    'type': 'outgoing'}]),
 (datetime.date(2019, 9, 2),
  [{'accountID': 1,
    'amount': 1.0,
    'description': 'incoming',
    'id': 4,
    'timestamp': datetime.datetime(2019, 9, 2, 20, 33, 6, 101495),
    'type': 'incoming'},
   {'accountID': 1,
    'amount': 2.0,
    'description': 'outgoing',
    'id': 3,
    'timestamp': datetime.datetime(2019, 9, 2, 20, 33, 29, 420419),
    'type': 'outgoing'}]),
 (datetime.date(2019, 9, 3),
  [{'accountID': 1,
    'amount': 1.0,
    'description': 'incoming',
    'id': 3,
    'timestamp': datetime.datetime(2019, 9, 3, 20, 33, 6, 101428),
    'type': 'incoming'},
   {'accountID': 1,
    'amount': 2.0,
    'description': 'outgoing',
    'id': 2,
    'timestamp': datetime.datetime(2019, 9, 3, 20, 33, 29, 420352),
    'type': 'outgoing'}])]

Seperti disebutkan, Postgresql dapat menghasilkan hasil yang hampir sama seperti menggunakan array JSON:

from sqlalchemy.dialects.postgresql import aggregate_order_by

incoming = select([literal('incoming').label('type'), Incoming.__table__]).\
    where(Incoming.accountID == accountID)

outgoing = select([literal('outgoing').label('type'), Outgoing.__table__]).\
    where(Outgoing.accountID == accountID)

all_entries = incoming.union(outgoing).alias('all_entries')

day = func.date_trunc('day', all_entries.c.timestamp)

stmt = select([day,
               func.array_agg(aggregate_order_by(
                   func.row_to_json(literal_column('all_entries.*')),
                   all_entries.c.timestamp))]).\
    group_by(day).\
    order_by(day)

db_session.execute(stmt).fetchall()

Kalau sebenarnya Incoming dan Outgoing dapat dianggap sebagai anak-anak dari basis yang sama, misalnya Entry , menggunakan serikat dapat agak otomatis dengan pewarisan tabel beton :

from sqlalchemy.ext.declarative import AbstractConcreteBase

class Entry(AbstractConcreteBase, Base):
    pass

class Incoming(Entry):
    __tablename__ = 'incoming'
    id          = Column(Integer,   primary_key=True)
    accountID   = Column(Integer,   ForeignKey('account.id'))
    amount      = Column(Float,     nullable=False)
    description = Column(Text,      nullable=False)
    timestamp   = Column(TIMESTAMP, nullable=False)
    account     = relationship("Account", back_populates="incomings")

    __mapper_args__ = {
        'polymorphic_identity': 'incoming',
        'concrete': True
    }

class Outgoing(Entry):
    __tablename__ = 'outgoing'
    id          = Column(Integer,   primary_key=True)
    accountID   = Column(Integer,   ForeignKey('account.id'))
    amount      = Column(Float,     nullable=False)
    description = Column(Text,      nullable=False)
    timestamp   = Column(TIMESTAMP, nullable=False)
    account     = relationship("Account", back_populates="outgoings")

    __mapper_args__ = {
        'polymorphic_identity': 'outgoing',
        'concrete': True
    }

Sayangnya menggunakan AbstractConcreteBase memerlukan panggilan manual ke configure_mappers() ketika semua kelas yang diperlukan telah ditentukan; dalam hal ini kemungkinan paling awal adalah setelah mendefinisikan User , karena Account bergantung padanya melalui hubungan:

from sqlalchemy.orm import configure_mappers
configure_mappers()

Kemudian untuk mengambil semua Incoming dan Outgoing dalam satu kueri ORM polimorfik gunakan Entry :

session.query(Entry).\
    filter(Entry.accountID == accountID).\
    order_by(Entry.timestamp).\
    all()

dan lanjutkan untuk menggunakan itertools.groupby() seperti di atas pada daftar hasil Incoming dan Outgoing .



  1. Database
  2.   
  3. Mysql
  4.   
  5. Oracle
  6.   
  7. Sqlserver
  8.   
  9. PostgreSQL
  10.   
  11. Access
  12.   
  13. SQLite
  14.   
  15. MariaDB
  1. Adakah yang berhasil menggunakan lokal tertentu untuk database PostgreSQL sehingga perbandingan teks tidak peka huruf besar-kecil?

  2. Ikhtisar Berbagai Metode Pemindaian di PostgreSQL

  3. Bagaimana menghindari rekursi dalam pemicu di PostgreSQL

  4. PostgreSQL:menjalankan hitungan baris untuk kueri 'menurut menit'

  5. Cara efisien untuk menarik data dari database kedua?