1

I have a JSONB data stored in a column called data in table nftdata. Inside this JSON data (as shown below) there is a JSON array at result.data.items. This is an array of 2400 JSON objects but I have shown two here as an example:

{
    "api_version": "0.1",
    "result": {
        "api_code": 200,
        "api_response": "Success",
        "data": {
            "collection": "XXX NFT",
            "ranking_url": "https://nft",
            "twitter": "https://twitter.com/nft/",
            "discord": "https://discord.com/invite/",
            "website": "https://nft.io/",
            "description": "2",
            "logo": "https://icons/f245c3aea5e279691f5f460d9d499afe.jpg",
            "items": [
                {
                    "id": 2414,
                    "link": "/2414",
                    "mint": "FHAJS21yJBtYDuix1x7woZPUo",
                    "name": "NFT #2414",
                    "description": "-",
                    "image": "https://arweave.net/mKbawCJP4dX9_IZg",
                    "attributes": ["Deleted to shorten post"],
                    "rank": 1,
                    "rank_algo": "h",
                    "all_ranks": {
                        "is": 1,
                        "trait_normalized": 1,
                        "statistical_rarity": 1876
                    }
                },
                {
                    "id": 198,
                    "link": "/198",
                    "mint": "EiGbBm7CsB8ZeZF5Hg",
                    "name": "NFT #198",
                    "description": "-",
                    "image": "https://arweave.net/fAVzxoqcsracjf",
                    "attributes": ["Deleted to shorten post"],
                    "rank": 2,
                    "rank_algo": "h",
                    "all_ranks": {
                        "is": 2,
                        "trait_normalized": 2,
                        "statistical_rarity": 2246
                    }
                }
            ]
        }
    }
}

The table this data is in is held like this:

collection_ID (text) data (jsonb)
collection1 JSONB data blob (as above)
collection2 JSONB data blob (as above)

I would like to use a Postgres query to first find the right table row based on its primary key (e.g. collection1), then search through the JSON in the data column of that particular table row for one of the 2400 objects (stored at result.data.items) with a particular ID (in nodejs it would be something like result.data.items[i].id) and if found, return the particular 'items' object (e.g. result.data.items[i]).

For example, I would like to search collection1 for id 2414 and return:

{
 "id": 2414,
 "link": "/2414",
 "mint": "FHAJS21yJBtYDuix1x7woZPUo",
 "name": "NFT #2414",
 "description": "-",
 "image": "https://arweave.net/mKbawCJP4dX9_IZg",
 "attributes": [ 'Deleted to shorten post'
],
 "rank": 1,
 "rank_algo": "h",
 "all_ranks": {
     "is": 1,
     "trait_normalized": 1,
     "statistical_rarity": 1876
     }
}

Edit: thanks for the answer below which solved the problem. For future readers I implemented the query in nodejs like this:

//pass nftid as string and collectionstring (collection_ID) as string into function

var querystring = "SELECT jsonb_path_query_first(data #> '{result,data,items}', '$[*] ? (@.id == " + nftid + " || @.id == \"" + nftid + "\")') AS result FROM howraredata WHERE  collection_id = '" + collectionstring + "' "

pgclient.query(querystring, (err, res) => {
      if (err) throw err
      //do stuff
      })
Laniakea
  • 25
  • 4

2 Answers2

3

Using a SQL/JSON path expression with the function jsonb_path_query_first() this gets remarkably simple:

SELECT jsonb_path_query_first(data, '$.result.data.items[*] ? (@.id == 2414)')
FROM   nftdata
WHERE  collection_id = 'collection1';

Produces the desired result.

db<>fiddle here

Though, understanding SQL/JSON path functionality may not be as simple. It was added to Postgres 12. The manual:

To refer to the JSON data to be queried (the context item), use the $ sign in the path expression. It can be followed by one or more accessor operators, which go down the JSON structure level by level to retrieve the content of context item. Each operator that follows deals with the result of the previous evaluation step.

So:

$.result.data.items[*] ... retrieve nested array as per your definition

When defining a path, you can also use one or more filter expressions that work similarly to the WHERE clause in SQL. A filter expression begins with a question mark and provides a condition in parentheses:

? (condition)

? (@.id == 2414)') ... filter the array element that has the top level key "id" with value 2414.

Related:

Erwin Brandstetter
  • 605,456
  • 145
  • 1,078
  • 1,228
  • I have use realised that some of the databases are strong the id as integer and some as strings. Is there a way to modify the query to find the id as either? – Laniakea Aug 10 '22 at 16:15
  • I was able to get it with (@.id == 2414 || @.id == "2414") – Laniakea Aug 10 '22 at 18:23
1

You can use built-in JSON operator and functions for that. You gave a picture instead of text, thus I am giving a simple sample based on that picture but with only two columns:

select *
from jsonb_to_recordset('{
  "result": {
    "data": {
      "items": [
        {
          "id": 1234,
          "description": "d1"
        },
        {
          "id": 1235,
          "description": "d2"
        }
      ]
    }
  }
}'::jsonb #> '{result, data, items}') as x(id int, description text);

DBFiddle demo

EDIT: As per new data in your question, you can use jsonb_array_elements (create GIN index if you haven't already did):

select item
from nftdata nd,
     jsonb_array_elements(nd.data #> '{result, data, items}') item
where (item->>'id')::int = 2414;

DBFiddle demo

EDIT: When you are sure that the Id is int:

select item
from nftdata nd,
     jsonb_array_elements(nd.data #> '{result, data, items}') item
where item->>'id' = '2414';
Cetin Basoz
  • 22,495
  • 3
  • 31
  • 39
  • Thanks for your reply. I realise I perhaps wasn't clear enough with my initial post, so have edited it above. Would you mind taking another look? – Laniakea Aug 09 '22 at 18:48
  • @Laniakea, edited for your case. – Cetin Basoz Aug 09 '22 at 20:52
  • @Laniakea, I am not sure if you already know that you are searching for collection1. I got that as if you were searching for the item with id = 2414. It should be easier to add collection_id = 'collection1' to where. – Cetin Basoz Aug 09 '22 at 21:02
  • Thanks. Your example is a useful reference as it is different. I have also just realised that sometimes the id is stored as an integer and sometimes as a string! Is there a way to modify the query to find both? – Laniakea Aug 10 '22 at 16:17
  • @Laniakea, edited to show that. Simply you remove casting to int and pass as '2414'. – Cetin Basoz Aug 11 '22 at 11:23