So very similar to Lukasz solution, but OBJECT_AGG will filter out any NULL values, so if you cast to ::text the nulls become real nulls, and are filtered out, so the WHERE clause is not required:
WITH data_table AS(
SELECT parse_json('[ { "after": "2021-10-14T00:00:00", "column": "createddate", "from": null, "gt": null, "gte": null, "id": "928d57b0", "lt": null, "lte": null, "not": null, "prior": null, "to": null, "value": null }, { "after": null, "column": "last_coach_date", "from": "", "gt": null, "gte": null, "id": "01704cdd", "lt": null, "lte": null, "not": null, "prior": null, "to": "", "value": null } ]') as json
)
SELECT f.index as batch_id,
OBJECT_AGG(p.key, NULLIF(p.value::text,'')::variant) as obj
FROM data_table t,
TABLE(FLATTEN(input=>t.json)) as f,
TABLE(FLATTEN(input=>f.value)) as p
GROUP BY 1
ORDER BY 1
;
gives:
| BATCH_ID |
OBJ |
| 0 |
{ "after": "2021-10-14T00:00:00", "column": "createddate", "id": "928d57b0" } |
| 1 |
{ "column": "last_coach_date", "id": "01704cdd" } |
It's not clear from the question if you want 2 rows, or a single value, the latter can be had by adding another layer of aggregation via ARRAY_AGG:
WITH data_table AS(
SELECT parse_json('[ { "after": "2021-10-14T00:00:00", "column": "createddate", "from": null, "gt": null, "gte": null, "id": "928d57b0", "lt": null, "lte": null, "not": null, "prior": null, "to": null, "value": null }, { "after": null, "column": "last_coach_date", "from": "", "gt": null, "gte": null, "id": "01704cdd", "lt": null, "lte": null, "not": null, "prior": null, "to": "", "value": null } ]') as json
)
SELECT array_agg(obj) as single_answer
FROM (
SELECT f.index as batch_id,
OBJECT_AGG(p.key, NULLIF(p.value::text,'')::variant) as obj
FROM data_table t,
TABLE(FLATTEN(input=>t.json)) as f,
TABLE(FLATTEN(input=>f.value)) as p
GROUP BY 1
)
;
gives:
| SINGLE_ANSWER |
| [ { "after": "2021-10-14T00:00:00", "column": "createddate", "id": "928d57b0" }, { "column": "last_coach_date", "id": "01704cdd" } ] |