Я хочу расширить столбец типа JSON, содержащий массив строк (псевдонимы записи), на несколько строк в sqlalchemy 2.0. Я получаю следующий SQL-запрос для SQL Server, который делает то же самое, но его нужно писать с использованием sqlalchemy.
SELECT * FROM general_info OUTER APPLY OPENJSON(alias)
Из этой таблицы:
Я хотел бы получить:
Конечно, да, но эта операция, похоже, не является обычной операцией. Кроме того, не существует расширенной поддержки операций типа JSON, помимо индексации.
Смоделировать OPENJSON как то, что SQLAlchemy называет функцией табличного значения, тривиально, т.е.:
oj = func.OPENJSON(general_info.alias).table_valued("value")
К сожалению, сложность заключается в OUTER APPLY
поддержке Microsoft SQL Server.
Поиск выявит следующую проблему на Github: Добавьте поддержку оператора APPLY для mssql #5133, на основе которой основано следующее:
import re
from sqlalchemy import String, func, join, lateral, select, text, true
from sqlalchemy.dialects import mssql
from sqlalchemy.ext.compiler import compiles
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column
from sqlalchemy.sql import Join
# Based on REF:
# Add APPLY operator support for mssql #5133
# https://github.com/sqlalchemy/sqlalchemy/issues/5133
@compiles(Join, "mssql")
def _compile_join_lateral(element, compiler, **kw):
statement = compiler.visit_join(element, **kw)
statement = re.sub(" JOIN LATERAL ", " OUTER APPLY ", statement)
statement = re.sub(" ON 1(\s*=\s*1)?", "", statement)
return statement
class Base(DeclarativeBase):
pass
class general_info(Base):
__tablename__ = "general_info"
id: Mapped[int] = mapped_column(primary_key=True)
name: Mapped[str] = mapped_column(String(30))
alias: Mapped[str] = mapped_column(String)
stmt1 = select(general_info.id, general_info.name, text("j1.value as alias")).select_from(
join(general_info, lateral(func.OPENJSON(general_info.alias).table_valued("value"), name = "j1"), true())
)
stmt2 = stmt1.compile(dialect=mssql.dialect())
print(stmt1)
print(stmt2)
print(stmt1)
выведет общий оператор SQL с JOIN LATERAL
:
SELECT general_info.id, general_info.name, j1.value as alias
FROM general_info JOIN LATERAL OPENJSON(general_info.alias) AS j1 ON true
print(stmt2)
выведет оператор T-SQL с OUTER APPLY
:
SELECT general_info.id, general_info.name, j1.value as alias
FROM general_info OUTER APPLY OPENJSON(general_info.alias) AS j1
И выполнение этого на примере ваших данных должно дать результаты:
В противном случае, существует ли более общий или совместимый подход для решения этой проблемы среди других серверных частей базы данных, даже менее эффективный? Может быть, связанный подзапрос на JOIN или что-то подобное?
Вы не можете сделать это коррелированным подзапросом в предложении select на Microsoft SQL Server, потому что openjson()
может возвращать много строк.
На самом деле не существует общего подхода к обработке JSON в обычных СУБД. например: хотя и Oracle, и Microsoft SQL Server имеют функции json_query()
и json_value()
, они достаточно разные, чтобы не быть взаимозаменяемыми. В то время как Oracle рассматривает JSON как собственный тип базы данных, к которому можно запрашивать напрямую (поэтому вы можете делать такие вещи, как select t.jdoc.person.birthdate.date() from person_collection t
), Microsoft SQL Server объединяет его в типы данных varchar(max)
или nvarchar(max)
и запрашивает из него наборы результатов, используя openjson()
.
Вы уже прочитали документацию SQLAlchemy Использование операторов SELECT? Что вы пробовали?