Итак, допустим, у меня есть таблица с двумя полями: first_col
— это int
, а second_col
— это array[int]
:
from sqlalchemy import (
Table,
select,
Integer,
)
from sqlalchemy.dialects.postgresql import ARRAY
some_table = Table(
"some_table",
Column("id", Integer, primary_key=True),
Column("first_col", Integer),
Column("second_col", ARRAY(Integer)),
)
И я хочу взять все объекты из db
как с first_col
, так и с second_col
и, например, объединить их, я могу показать то, что хочу, в коде Python:
def get_some_stuff_and_aggregate() -> dict:
query = (
select(
[
some_table.c.first_col,
some_table.c.second_col,
]
)
.select_from(some_table)
)
# execute query here... it's not important here
query_res = ...
res_dict = {}
for some_obj in query_res:
for some_ids in (
[some_obj.first_col],
some_obj.second_col,
):
for some_id in some_ids:
if some_id in res_dict:
res_dict[some_id] += 1
else:
res_dict[some_id] = 1
return res_dict
Но, думаю, я смогу сделать это на уровне SQL (ORM) без этой грязной и медленной агрегации Python. Не могли бы вы мне помочь? Как это сделать лучше, используя ORM
?
Да, именно то, что вы говорите, верно
Вероятно, существует лучший SQL, но, похоже, он делает то, о чем вы просите.
from sqlalchemy import Column, Integer, MetaData, Table, create_engine, func, insert, select
from sqlalchemy.dialects.postgresql import ARRAY, array
from sqlalchemy.orm import Session
engine = create_engine("postgresql+psycopg://")
metadata = MetaData()
some_table = Table(
"some_table",
metadata,
Column("id", Integer, primary_key=True),
Column("first_col", Integer),
Column("second_col", ARRAY(Integer)),
)
metadata.create_all(engine)
dummy_data = [
{"first_col": 1, "second_col": [1, 2, 1, 4, 5]},
{"first_col": 3, "second_col": [2, 4, 6, 2, 10]},
{"first_col": 6, "second_col": [3, 6, 9, 3, 15]},
{"first_col": 4, "second_col": [4, 8, 4, 16, 20]},
{"first_col": 2, "second_col": [5, 10, 15, 5, 25]},
]
with Session(engine) as session:
session.execute(insert(some_table).values(dummy_data))
session.commit()
with Session(engine) as session:
subq = select(
func.unnest(
some_table.c.second_col.op("||")(array([some_table.c.first_col]))
).label("element")
).subquery()
statement = select(subq.c.element, func.count("*")).group_by(subq.c.element)
for i in session.execute(statement):
print(i)
Это генерирует следующий sql и выходные данные.
SELECT
anon_1.element,
count(% (count_2)
s::VARCHAR) AS count_1
FROM (
SELECT
unnest(some_table.second_col || ARRAY [some_table.first_col]) AS element
FROM
some_table) AS anon_1
GROUP BY
anon_1.element
(9, 1)
(15, 2)
(3, 3)
(5, 3)
(4, 5)
(10, 2)
(6, 3)
(2, 4)
(16, 1)
(25, 1)
(20, 1)
(1, 3)
(8, 1)
Итак, вам нужно подсчитать, сколько раз каждое значение появляется в первом или втором столбце?