postgresqljsonb

Postgresql: jsonb_path_query filter based on value from a table column


WITH pets AS (SELECT 'Dog' AS mainpet, '
[
  {
    "petSpecies": "Dog",
    "mainMeal": "Meat"
  },
  {
    "petSpecies": "Cat",
    "mainMeal": "Milk"
  },
  {
    "petSpecies": "Lizard",
    "mainMeal": "Insects"
  }
]'::jsonb AS petfood)
SELECT pets.petfood,
       jsonb_path_query_first(pets.petfood, '$[*]?(@."petSpecies" == "Dog")."mainMeal"') ->> 0 AS mypetmainmeal
FROM pets

Instead of filtering on "petSpecies" == "Dog", I want to filter on "petSpecies" == pets.mainpet (i.e. a value from another column), but I'm struggling with the syntax.

I tried this but it doesn't work:

jsonb_path_query_first(pets.petfood, '$[*]?(@."petSpecies" == "' || pets.mainpet || '")."mainMeal"') ->> 0

Grateful for any help.


Solution

  • The jsonb_path_query_x() functions accept an optional vars parameter. There's no need to concatenate the JSONPath expression, you can pass things in through there.
    demo at db<>fiddle

    select pets.petfood
         , jsonb_path_query_first( pets.petfood
                                  ,'$[*]?(@."petSpecies" == $some_var)."mainMeal"'
                                  ,jsonb_build_object('some_var',pets.mainpet)) ->>0
    from pets;
    
    petfood mypetmainmeal
    [{"mainMeal": "Meat", "petSpecies": "Dog"}, {"mainMeal": "Milk", "petSpecies": "Cat"}, {"mainMeal": "Insects", "petSpecies": "Lizard"}] Meat

    That being said, your idea would've worked if you parenthesised the expression you constructed, then cast it to ::jsonpath

    select pets.petfood
         , jsonb_path_query_first(
              pets.petfood
            ,('$[*]?(@."petSpecies"=='||to_json(pets.mainpet)::text||')."mainMeal"')::jsonpath
           ) ->> 0 as mypetmainmeal
    from pets;
    

    Also note the to_json()::text suggested by @Bergi below, that handles proper quoting and escaping automatically.

    Without that explicit ::jsonpath cast, the concatenation operators || give you a text-type value, leading to function signature mismatch:

    ERROR:  function jsonb_path_query_first(jsonb, text) does not exist
    LINE 2:      , jsonb_path_query_first(
                   ^
    HINT:  No function matches the given name and argument types. You might need to add explicit type casts.
    

    Without the text-type-returning concatenation operators or the cast, that single-quoted parameter was initially just an unknown-type string constant which is subject to automatic type conversion rules, which automatically converted it to jsonpath. Using the || operators in your second example made it a text, disabling that. Explicit cast also disables that auto-conversion, but it isn't needed any longer because that makes it match the function signature directly.