Вставьте ключ в поле MySQL JSON через сопоставленный класс sqlachemy без выполнения запроса - PullRequest
0 голосов
/ 10 октября 2019

Давайте начнем с определения моего сопоставленного класса

from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.types import JSON
from sqlalchemy.ext.mutable import MutableDict
from sqlalchemy import Column, Integer

base = declarative_base()

class MinimalExample(Base):
    __tablename__ = 'users'
    id = Column(Integer)
    data = Column(MutableDict.as_mutable(JSON))

Допустим, у меня в таблице есть строка с id=1 и data={'key_0': 1}

Я могу запросить ее через session объект довольно легко

first_row = session.query(MinimalExample)\
                   .filter(MinimalExample.id==1)\
                   .first()

И оттуда я могу изменить поле JSON любым способом и обновить базу данных:

first_row.data['key_2'] = 1
session.commit()

Приведенный выше код работает (потому что мы пометили JSONполе как изменяемое условие, нам не нужно помечать его как грязное вручную)

session.query(MinimalExample.data)\
                   .filter(MinimalExample.id==1)\
                   .first()

>>> {'key_0': 1, 'key_2': 1}

Однако это означает, что мне нужно запросить все строки, которые я хочу изменить, на моем локальном компьютере, что простослишком медленно / у меня недостаточно памяти.

Я ищу способ использования функции sqlalchemy.update для получения того же результата

Вот что я уже пробовал:

from sqlalchemy import update

stmt = update(MinimalExample)
           .where(MinimalExample.id=1)\
           .values(MinimalExample.data={'key_1': 1})

session.execute(stmt)
session.commit()

, который просто собирается заменить поле data в моей строке словарем {'key_1': 1}

Что-то похожее на это похоже на путь:

(РЕДАКТИРОВАТЬ: нашел способ исправить мой предыдущий код, чтобы он больше не выдавал ошибку, просто поставив* объект в словарь)

stmt = update(MinimalExample)
           .where(MinimalExample.id=1)\
           .values({MinimalExample.data['key_1']:1})

, глядя на строку этого оператора, кажется довольно хорошим

str(stmt)

>>> 'UPDATE users SET data[:data_1]=:param_1 WHERE users.id = :id_1'

Однако выполнение этого оператора приводит к ProgrammingError: (MySQLdb._exceptions.ProgrammingError) (1064, 'You have an error in your SQL syntax ...

Есть мысли по этому поводу? Бонусные баллы, если я могу обновить несколько строк одновременно

1 Ответ

1 голос
/ 11 октября 2019

Для обновления столбца JSON на MySQL необходимо использовать функцию JSON_SET() (подробности см. здесь ), например, рабочий запрос выглядит так:

UPDATE users SET data = JSON_SET(data, "$.key_1", 1) WHERE id = 1;

Для эмуляции в SQLAlchemy:

qry = (
    update(MinimalExample)
    .where(MinimalExample.id == 1)
    .values(
        {"data": func.JSON_SET(MinimalExample.data, "$.key_1", 1)}
    )
)
s.execute(qry)
s.commit()

Вот журналы от создания таблицы до фиксации, и, как вы можете видеть, SELECT не выдается:

2019-10-11 09:37:01,672 INFO sqlalchemy.engine.base.Engine
CREATE TABLE users (
        id INTEGER NOT NULL AUTO_INCREMENT,
        data JSON,
        PRIMARY KEY (id)
)


2019-10-11 09:37:01,684 INFO sqlalchemy.engine.base.Engine {}
2019-10-11 09:37:01,708 INFO sqlalchemy.engine.base.Engine COMMIT
2019-10-11 09:37:01,716 INFO sqlalchemy.engine.base.Engine BEGIN (implicit)
2019-10-11 09:37:01,718 INFO sqlalchemy.engine.base.Engine INSERT INTO users (id, data) VALUES (%(id)s, %(data)s)
2019-10-11 09:37:01,718 INFO sqlalchemy.engine.base.Engine {'id': 1, 'data': '{"key_0": 1}'}
2019-10-11 09:37:01,720 INFO sqlalchemy.engine.base.Engine COMMIT
2019-10-11 09:37:01,723 INFO sqlalchemy.engine.base.Engine BEGIN (implicit)
2019-10-11 09:37:01,724 INFO sqlalchemy.engine.base.Engine UPDATE users SET data = JSON_SET(data, "$.key_1", 1) WHERE id = 1;
2019-10-11 09:37:01,724 INFO sqlalchemy.engine.base.Engine {}
2019-10-11 09:37:01,725 INFO sqlalchemy.engine.base.Engine COMMIT
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...