从 JSONB 字段中正确提取 JSON 数组
Properly extracting JSON arrays from a JSONB field
从 PostgreSQL 10 中的 table,我试图将同一 jsonb
字段的多个子项中的所有数组元素连接到它们的父行,有点像 or this one。但是我在 JOIN
中犯了一个错误,以至于我没有获取单个数组元素,而是将单个数组元素包裹在一个单项数组中。
这是缩写的 table 定义:
CREATE TABLE public.worker_customformstore (
id integer NOT NULL DEFAULT nextval('worker_customformstore_id_seq'::regclass),
created_on timestamp with time zone NOT NULL,
store jsonb,
schema_id integer NOT NULL,
polymorphic_ctype_id integer,
pdf_key character varying(100) COLLATE pg_catalog."default" NOT NULL,
last_updated timestamp with time zone
)
以及 store
字段的示例值:
'{"Subcontractor Use": {
"labor": [
{
"note": null,
"hours": {
"dt": null,
"ot": null,
"st": 1,
"pdt": null,
"pot": null
},
"employee": {
"id": 456,
"trade": "XXX",
"is_active": true,
"last_name": "Uknow",
"first_name": "Noone",
"company_supplied_id": "456"
},
"external subcontractor": false
},
{
"note": null,
"hours": {
"dt": null,
"ot": null,
"st": 8,
"pdt": null,
"pot": null
},
"employee": {
"id": 123,
"trade": "",
"member": null,
"is_active": true,
"last_name": "Guy",
"user_role": "WORKER",
"first_name": "Some",
"company_supplied_id": "123"
},
"external subcontractor": false
}
],
"Equipment": [
{
"note": null,
"hours": {
"idle": null,
"over": null,
"running": 8
},
"quantity": 1,
"equipment": {
"id": 6243,
"status": "Rented",
"project": "8399",
"category": "XXXXX",
"caltrans_id": "00-20",
"description": "19",
"equipment_id": "Scissor",
"idle_time_price": 0,
"over_time_price": 0,
"running_time_price": 0
}
}
]
}
}'
我的简化查询如下所示:
SELECT
cufstore.id,
CASE
WHEN labor IS NOT DISTINCT FROM NULL THEN
0
WHEN (jsonb_array_elements(labor) -> 'hours' ->> 'st') = '' THEN
0
ELSE
COALESCE((jsonb_array_elements(labor) -> 'hours' ->> 'st')::numeric, 0)
END
-- more stuff here ...
as total_hours,
CASE
WHEN labor IS NOT DISTINCT FROM NULL THEN
0
ELSE
COALESCE(jsonb_array_length(cufstore.store -> 'Subcontractor Use' -> 'labor'), 0)
END as total_workers,
labor, equipment
FROM public.worker_customformstore AS cufstore
...
LEFT OUTER JOIN LATERAL
(SELECT
jsonb_array_elements(jsonb_strip_nulls(cufstore.store -> 'Subcontractor Use' -> 'labor'))
WHERE cufstore.store -> 'Subcontractor Use' ->> 'labor' IS NOT NULL
) labor on true
LEFT OUTER JOIN LATERAL
(SELECT
jsonb_array_elements(jsonb_strip_nulls(cufstore.store -> 'Subcontractor Use' -> 'Equipment'))
WHERE cufstore.store -> 'Subcontractor Use' ->> 'Equipment' IS NOT NULL
) equipment on true
除了结束大量冗余 jsonb_array_elements
调用之外,这些还阻止我将重复的逻辑重构为函数,因为我在 COALESCE
中收到有关设置返回函数的错误在函数定义中(尽管在我的查询主体中发生时没有抱怨)。
我想我想要的更像是:
LEFT OUTER JOIN LATERAL
jsonb_array_elements(jsonb_strip_nulls(cufstore.store -> 'Subcontractor Use' -> 'labor')) labor
ON jsonb_typeof(labor) = 'array'
但是当数据为 NULL
或看起来不正确时,尝试这样做会给我 cannot extract elements from a scalar
。
我可能从根本上误解了我能做什么,但这就是 equipment
列的样子:
("{""hours"": {""running"": 8}, ""quantity"": 1, . . .}")
并且我希望能够询问 equipment -> 'hours' ->> 'running'
而不必将其包装在 jsonb_array_elements(equipment)
中。我需要这样做还是我不小心在列值的开头和结尾添加了括号?
不清楚两个嵌套的 JSON 数组 "labor"
和 "Equipment"
的元素是如何相关的。从你的样本看来 "Equipment"
只有一个元素,数组包装器只是噪音 ...
不幸的是还有一个嵌套键"equipment"
,容易与另一个混淆
我也不知道 objective 到底是什么。
尽管如此,在去除大量噪音和不必要的并发症之后,这可能接近您所追求的:
SELECT s.id
, COALESCE((NULLIF(labor->'hours'->>'st', ''))::numeric, 0) AS total_hours
, CASE WHEN labor IS NULL THEN 0
ELSE COALESCE(jsonb_array_length(s.store->'Subcontractor Use'->'labor'), 0)
END AS total_workers
, s.store #>> '{Subcontractor Use, Equipment, 0, hours, running}' AS equipment_hours
, labor
FROM worker_customformstore s
LEFT JOIN jsonb_array_elements(s.store->'Subcontractor Use'->'labor') labor ON true;
db<>fiddle here
备注
这个冗长的表达式:
CASE
WHEN labor IS NOT DISTINCT FROM NULL THEN
0
WHEN (jsonb_array_elements(labor) -> 'hours' ->> 'st') = '' THEN
0
ELSE
COALESCE((jsonb_array_elements(labor) -> 'hours' ->> 'st')::numeric, 0)
END
归结为:
COALESCE((NULLIF(labor -> 'hours' ->> 'st', ''))::numeric, 0)
不要再次应用jsonb_array_elements()
,这已经在横向子查询中完成了。
labor IS NOT DISTINCT FROM NULL
与 labor IS NULL
相同,但我们不需要任何一个,因为后面的 COALESCE
无论如何都需要它。
使用 NULLIF
我们根本不需要 CASE
和另一个分支。
假设嵌套的JSON数组"Equipment"
中只有一个元素,我们可以访问equipment_hours
直接用
s.store #>> '{Subcontractor Use, Equipment, 0, hours, running}'
。如果假设不成立,你将不得不做更多(并解释更多)。
寻址
如果 store -> 'Subcontractor Use' -> 'labor'
不是嵌套的 JSON 数组,而是标量,例如,您会收到如您评论的错误:
ERROR: cannot extract elements from a scalar
db<>fiddle here
您可以使用嵌套的 CASE
避免异常,例如:
...
LEFT JOIN jsonb_array_elements(
<b>CASE WHEN jsonb_typeof(s.store -> 'Subcontractor Use' -> 'labor') = 'array'
THEN s.store -> 'Subcontractor Use' -> 'labor'
END</b>) labor ON true;
db<>fiddle here
您可能想要对案例的 return 个替代值做更多的事情...
从 PostgreSQL 10 中的 table,我试图将同一 jsonb
字段的多个子项中的所有数组元素连接到它们的父行,有点像 JOIN
中犯了一个错误,以至于我没有获取单个数组元素,而是将单个数组元素包裹在一个单项数组中。
这是缩写的 table 定义:
CREATE TABLE public.worker_customformstore (
id integer NOT NULL DEFAULT nextval('worker_customformstore_id_seq'::regclass),
created_on timestamp with time zone NOT NULL,
store jsonb,
schema_id integer NOT NULL,
polymorphic_ctype_id integer,
pdf_key character varying(100) COLLATE pg_catalog."default" NOT NULL,
last_updated timestamp with time zone
)
以及 store
字段的示例值:
'{"Subcontractor Use": {
"labor": [
{
"note": null,
"hours": {
"dt": null,
"ot": null,
"st": 1,
"pdt": null,
"pot": null
},
"employee": {
"id": 456,
"trade": "XXX",
"is_active": true,
"last_name": "Uknow",
"first_name": "Noone",
"company_supplied_id": "456"
},
"external subcontractor": false
},
{
"note": null,
"hours": {
"dt": null,
"ot": null,
"st": 8,
"pdt": null,
"pot": null
},
"employee": {
"id": 123,
"trade": "",
"member": null,
"is_active": true,
"last_name": "Guy",
"user_role": "WORKER",
"first_name": "Some",
"company_supplied_id": "123"
},
"external subcontractor": false
}
],
"Equipment": [
{
"note": null,
"hours": {
"idle": null,
"over": null,
"running": 8
},
"quantity": 1,
"equipment": {
"id": 6243,
"status": "Rented",
"project": "8399",
"category": "XXXXX",
"caltrans_id": "00-20",
"description": "19",
"equipment_id": "Scissor",
"idle_time_price": 0,
"over_time_price": 0,
"running_time_price": 0
}
}
]
}
}'
我的简化查询如下所示:
SELECT
cufstore.id,
CASE
WHEN labor IS NOT DISTINCT FROM NULL THEN
0
WHEN (jsonb_array_elements(labor) -> 'hours' ->> 'st') = '' THEN
0
ELSE
COALESCE((jsonb_array_elements(labor) -> 'hours' ->> 'st')::numeric, 0)
END
-- more stuff here ...
as total_hours,
CASE
WHEN labor IS NOT DISTINCT FROM NULL THEN
0
ELSE
COALESCE(jsonb_array_length(cufstore.store -> 'Subcontractor Use' -> 'labor'), 0)
END as total_workers,
labor, equipment
FROM public.worker_customformstore AS cufstore
...
LEFT OUTER JOIN LATERAL
(SELECT
jsonb_array_elements(jsonb_strip_nulls(cufstore.store -> 'Subcontractor Use' -> 'labor'))
WHERE cufstore.store -> 'Subcontractor Use' ->> 'labor' IS NOT NULL
) labor on true
LEFT OUTER JOIN LATERAL
(SELECT
jsonb_array_elements(jsonb_strip_nulls(cufstore.store -> 'Subcontractor Use' -> 'Equipment'))
WHERE cufstore.store -> 'Subcontractor Use' ->> 'Equipment' IS NOT NULL
) equipment on true
除了结束大量冗余 jsonb_array_elements
调用之外,这些还阻止我将重复的逻辑重构为函数,因为我在 COALESCE
中收到有关设置返回函数的错误在函数定义中(尽管在我的查询主体中发生时没有抱怨)。
我想我想要的更像是:
LEFT OUTER JOIN LATERAL
jsonb_array_elements(jsonb_strip_nulls(cufstore.store -> 'Subcontractor Use' -> 'labor')) labor
ON jsonb_typeof(labor) = 'array'
但是当数据为 NULL
或看起来不正确时,尝试这样做会给我 cannot extract elements from a scalar
。
我可能从根本上误解了我能做什么,但这就是 equipment
列的样子:
("{""hours"": {""running"": 8}, ""quantity"": 1, . . .}")
并且我希望能够询问 equipment -> 'hours' ->> 'running'
而不必将其包装在 jsonb_array_elements(equipment)
中。我需要这样做还是我不小心在列值的开头和结尾添加了括号?
不清楚两个嵌套的 JSON 数组 "labor"
和 "Equipment"
的元素是如何相关的。从你的样本看来 "Equipment"
只有一个元素,数组包装器只是噪音 ...
不幸的是还有一个嵌套键"equipment"
,容易与另一个混淆
我也不知道 objective 到底是什么。
尽管如此,在去除大量噪音和不必要的并发症之后,这可能接近您所追求的:
SELECT s.id
, COALESCE((NULLIF(labor->'hours'->>'st', ''))::numeric, 0) AS total_hours
, CASE WHEN labor IS NULL THEN 0
ELSE COALESCE(jsonb_array_length(s.store->'Subcontractor Use'->'labor'), 0)
END AS total_workers
, s.store #>> '{Subcontractor Use, Equipment, 0, hours, running}' AS equipment_hours
, labor
FROM worker_customformstore s
LEFT JOIN jsonb_array_elements(s.store->'Subcontractor Use'->'labor') labor ON true;
db<>fiddle here
备注
这个冗长的表达式:
CASE
WHEN labor IS NOT DISTINCT FROM NULL THEN
0
WHEN (jsonb_array_elements(labor) -> 'hours' ->> 'st') = '' THEN
0
ELSE
COALESCE((jsonb_array_elements(labor) -> 'hours' ->> 'st')::numeric, 0)
END
归结为:
COALESCE((NULLIF(labor -> 'hours' ->> 'st', ''))::numeric, 0)
不要再次应用
jsonb_array_elements()
,这已经在横向子查询中完成了。labor IS NOT DISTINCT FROM NULL
与labor IS NULL
相同,但我们不需要任何一个,因为后面的COALESCE
无论如何都需要它。使用
NULLIF
我们根本不需要CASE
和另一个分支。
假设嵌套的JSON数组"Equipment"
中只有一个元素,我们可以访问equipment_hours
直接用
s.store #>> '{Subcontractor Use, Equipment, 0, hours, running}'
。如果假设不成立,你将不得不做更多(并解释更多)。
寻址
如果 store -> 'Subcontractor Use' -> 'labor'
不是嵌套的 JSON 数组,而是标量,例如,您会收到如您评论的错误:
ERROR: cannot extract elements from a scalar
db<>fiddle here
您可以使用嵌套的 CASE
避免异常,例如:
...
LEFT JOIN jsonb_array_elements(
<b>CASE WHEN jsonb_typeof(s.store -> 'Subcontractor Use' -> 'labor') = 'array'
THEN s.store -> 'Subcontractor Use' -> 'labor'
END</b>) labor ON true;
db<>fiddle here
您可能想要对案例的 return 个替代值做更多的事情...