python - 如何更新 JSON 类型列中的特定值
问题描述
我有我的 Web 应用程序,并且在 json 类型列中有关于我的用户的统计信息。例如:{'current': {'friends': 5, 'wins': 2, 'loses': 10}}
。我想只更新特定字段以防出现竞争情况。现在我只是简单地更新整个字典,但是当用户同时玩两个游戏时,可能会发生竞争情况。
现在我正在这样做:
class User:
name = Column(Unicode(1024), nullable=False)
username = Column(Unicode(128), nullable=False, unique=True, default='')
password = Column(Unicode(256), nullable=True, default='')
counters = Column(
MutableDict.as_mutable(JSON), nullable=False,
server_default=text('{}'), default=lambda: copy.deepcopy(DEFAULT_COUNTERS))
def current_counter(self, feature, number):
current = self.counters.get('current', {})[feature]
if current + number < 0:
return
self.counters.get('current', {})[feature] = current + number
self.counters.changed()
但这将在更改值后更新整个计数器列,如果会发生两场比赛,我期待比赛条件。
我在想一些session.query
类似的东西,但我不是那么好:
def update_counter(self, session, feature, number):
current = self.counters.get('current', {})[feature]
if current + number < 0:
return
session.query(User) \
.filter(User.id == self.id) \
.update({
"current": func.jsonb_set(
User.counters['current'][feature],
column(current) + column(number),
'true')
},
synchronize_session=False
)
这段代码产生:NotImplementedError: Operator 'getitem' is not supported on this expression
for Event.counters['current'][feature]
line 但我不知道如何使它工作。
感谢帮助。
解决方案
该错误是由链接项目访问产生的,而不是使用索引元组作为单个操作:
User.counters['current', feature]
这将产生一个路径索引操作。但是,如果您这样做,您将仅在嵌套 JSON 中设置值,而不是在整个值中。此外,您从 JSON 索引的值是一个整数(而不是一个集合),因此jsonb_set()
甚至不知道该做什么。这就是为什么jsonb_set()
接受路径作为其第二个参数的原因,它是一个文本数组,描述了您要在 JSON 中设置的值:
func.jsonb_set(User.counters, ['current', feature], ...)
至于比赛条件,可能还有一个。您首先从当前模型对象中获取计数
current = self.counters.get('current', {})[feature]
然后继续在更新中使用该值,但是如果另一个事务设法在两者之间执行类似的更新怎么办?您可能会覆盖该更新的更改:
select, counter = 42 |
| select, counter = 42
update counter = 52 | # +10
| update counter = 32 # -10
commit |
| commit # 32 instead of 42
然后解决方案是确保您使用 获取当前模型对象FOR UPDATE
,或者您正在使用SERIALIZABLE
事务隔离(准备好在序列化失败时重试),或者忽略获取的值并让数据库计算更新:
# Note that create_missing is true by default
func.jsonb_set(
User.counters,
['current', feature],
func.to_jsonb(
func.coalesce(User.counters['current', feature].astext.cast(Integer), 0) +
number))
如果您想确保在结果为负的情况下不更新值(请记住,您之前读取的值可能已经更改),请使用 DB 计算值作为谓词添加检查:
def update_counter(self, session, feature, number):
current_count = User.counters['current', feature].astext.cast(Integer)
# Coalesce in case the count has not been set yet and is NULL
new_count = func.coalesce(current_count, 0) + number
session.query(User) \
.filter(User.id == self.id, new_count >= 0) \
.update({
User.counters: func.jsonb_set(
func.to_jsonb(User.counters),
['current', feature],
func.to_jsonb(new_count)
)
}, synchronize_session=False)
推荐阅读
- flutter - 计数器不改变其状态
- dataframe - 读取Parquet文件时spark如何决定列顺序
- javascript - 粘性导航栏,滚动到第一个元素不精确
- javascript - 在 webpack 中,CompatibilityPlugin 做了什么?
- c# - 调整网格内的图像大小以始终适合可用空间
- python - Scrapy errback 无法捕获所有错误
- .net - Visual Studio 未加载模块(因此没有要命中的断点)
- php - 按条款显示自定义帖子
- java - Spring 数据 Elasticsearch | 按存储库的全文字段搜索
- discord - 嵌入消息中不带井号标签的消息作者姓名