Search code examples
sqljsonpostgresqlpostgresql-json

PostgreSQL: Remove attribute from JSON column


I need to remove some attributes from a json type column.

The Table:

CREATE TABLE my_table( id VARCHAR(80), data json);
INSERT INTO my_table (id, data) VALUES (
  'A', 
  '{"attrA":1,"attrB":true,"attrC":["a", "b", "c"]}'
);

Now, I need to remove attrB from column data.

Something like alter table my_table drop column data->'attrB'; would be nice. But a way with a temporary table would be enough, too.


Solution

  • Update: for 9.5+, there are explicit operators you can use with jsonb (if you have a json typed column, you can use casts to apply a modification):

    Deleting a key (or an index) from a JSON object (or, from an array) can be done with the - operator:

    SELECT jsonb '{"a":1,"b":2}' - 'a', -- will yield jsonb '{"b":2}'
           jsonb '["a",1,"b",2]' - 1    -- will yield jsonb '["a","b",2]'
    

    Deleting, from deep in a JSON hierarchy can be done with the #- operator:

    SELECT '{"a":[null,{"b":[3.14]}]}' #- '{a,1,b,0}'
    -- will yield jsonb '{"a":[null,{"b":[]}]}'
    

    For 9.4, you can use a modified version of the original answer (below), but instead of aggregating a JSON string, you can aggregate into a json object directly with json_object_agg().

    Related: other JSON manipulations whithin PostgreSQL:

    Original answer (applies to PostgreSQL 9.3):

    If you have at least PostgreSQL 9.3, you can split your object into pairs with json_each() and filter your unwanted fields, then build up the json again manually. Something like:

    SELECT data::text::json AS before,
           ('{' || array_to_string(array_agg(to_json(l.key) || ':' || l.value), ',') || '}')::json AS after
    FROM (VALUES ('{"attrA":1,"attrB":true,"attrC":["a","b","c"]}'::json)) AS v(data),
    LATERAL (SELECT * FROM json_each(data) WHERE "key" <> 'attrB') AS l
    GROUP BY data::text
    

    With 9.2 (or lower) it is not possible.

    Edit:

    A more convenient form is to create a function, which can remove any number of attributes in a json field:

    Edit 2: string_agg() is less expensive than array_to_string(array_agg())

    CREATE OR REPLACE FUNCTION "json_object_delete_keys"("json" json, VARIADIC "keys_to_delete" TEXT[])
      RETURNS json
      LANGUAGE sql
      IMMUTABLE
      STRICT
    AS $function$
    SELECT COALESCE(
      (SELECT ('{' || string_agg(to_json("key") || ':' || "value", ',') || '}')
       FROM json_each("json")
       WHERE "key" <> ALL ("keys_to_delete")),
      '{}'
    )::json
    $function$;
    

    With this function, all you need to do is to run the query below:

    UPDATE my_table
    SET data = json_object_delete_keys(data, 'attrB');