Skip to main content

GridGain JSON basics

Tutorial

Populate a GridGain table with JSON data, query it with JSON_VALUE and JSON_QUERY, and speed up queries by flattening fields into indexed columns.

gridgain8
Beginner|30 min|sql
Tested on

Introduction​

info

JSON functions are available only in GridGain Enterprise and Ultimate editions, version 8.9 and above.

note

To proceed with this tutorial, you need to enable the 'gridgain-sql' module.

For users storing data in the JSON format - for example, in NoSQL document databases - GridGain offers a number of new functions that simplify working with JSON data. This tutorial shows you how to populate a database with sample JSON data and use the JSON functions to improve query performance.

Sample data​

The sample JSON data for this tutorial are as follows:

The datasets are relatively large (~15 Kb minified), with many data rows. You may want to tune the node memory defaults (for example, in /bin/ignite).

Populate the database​

  1. Run a GridGain node.

  2. Connect to the node using the embedded sqlline:

    /bin/sqlline.sh --verbose=true -u jdbc:ignite:thin://127.0.0.1:10800
  3. Create a table to hold the JSON data.

    CREATE TABLE shows(id INT PRIMARY KEY, data VARCHAR);
    note

    Do not forget to escape single quotes ' with '' while using SQL.

  4. For the sake of example comparison, produce duplicates and a few unique rows to be included in the query results.

    INSERT INTO shows(id, data)
    SELECT X, 'put-here-planet-earth-json-sample'
    FROM SYSTEM_RANGE(1, 300000);

    INSERT INTO shows(id, data)
    VALUES (300001, 'put-here-the-blue-planet-json-sample');

Query JSON data​

note

The difference between JSON_QUERY and JSON_VALUE is explained in JSON Functions.

Retrieve a Great Britain (GB) TV show whose first episode aired before 2005:

SELECT JSON_VALUE(data, '$.name') as name, JSON_VALUE(data, '$.network.name') as network, JSON_QUERY(data, '$.rating') as rating
FROM shows
WHERE TRUE
AND JSON_VALUE(data, '$.status') = 'Ended'
AND JSON_VALUE(data, '$.webChannel') is null
AND ARRAY_CONTAINS(ARRAY['US', 'GB'], JSON_VALUE(data, '$.network.country.code'))
AND CAST(JSON_VALUE(data, '$.rating.average') AS DOUBLE) > 9
AND JSON_VALUE(data, '$._embedded.episodes[0].name') = 'Introduction'
AND JSON_VALUE(data, '$._embedded.episodes[0].airstamp') < '2005-01-01'
;

The query plan uses PUBLIC.SHOWS.__SCAN_ and simply scans the entire table:

SELECT
"PUBLIC".JSON_VALUE("__Z0"."DATA", '$.name') AS "__C0_0",
"PUBLIC".JSON_VALUE("__Z0"."DATA", '$.network.name') AS "__C0_1",
"PUBLIC".JSON_QUERY("__Z0"."DATA", '$.rating') AS "__C0_2"
FROM "PUBLIC"."SHOWS" "__Z0"
/* PUBLIC.SHOWS.__SCAN_ */
WHERE ("PUBLI |
| SELECT
"__C0_0" AS "NAME",
"__C0_1" AS "NETWORK",
"__C0_2" AS "RATING"
FROM "PUBLIC"."__T0"
/* PUBLIC.merge_scan */ |

The query returns the following result:

+-------------------+------------+--------------------+
| NAME | NETWORK | RATING |
+-------------------+------------+--------------------+
| The Blue Planet | BBC One | {"average":9.1} |
+-------------------+------------+--------------------+

1 row selected (23.731 seconds)

Improve performance with indexes​

Now put "JSON" and "performance" in the same sentence by preparing your data and adding indexes.

Your query uses a couple of common fields related to the first episode, hence you can flatten these into separate columns and use them as indexes (read the automation section below):

ALTER TABLE shows ADD COLUMN first_episode_name VARCHAR;
UPDATE shows SET first_episode_name = JSON_VALUE(data, '$._embedded.episodes[0].name');

ALTER TABLE shows ADD COLUMN first_episode_airstamp timestamp;
UPDATE shows SET first_episode_airstamp = JSON_VALUE(data, '$._embedded.episodes[0].airstamp');

CREATE INDEX shows_first_episode_name_airstamp on shows(first_episode_name, first_episode_airstamp);

Now, rewrite the initial query to use the new index:

SELECT JSON_VALUE(data, '$.name') as name, JSON_VALUE(data, '$.network.name') as network, JSON_QUERY(data, '$.rating') as rating
FROM shows
WHERE TRUE
AND JSON_VALUE(data, '$.status') = 'Ended'
AND JSON_VALUE(data, '$.webChannel') is null
AND ARRAY_CONTAINS(ARRAY['US', 'GB'], JSON_VALUE(data, '$.network.country.code'))
AND CAST(JSON_VALUE(data, '$.rating.average') AS DOUBLE) > 9
AND first_episode_name = 'Introduction'
AND first_episode_airstamp < '2005-01-01'
;

Note that the query plan uses the created index - PUBLIC.SHOWS_FIRST_EPISODE_NAME_AIRST:

SELECT
"PUBLIC".JSON_VALUE("__Z0"."DATA", '$.name') AS "__C0_0",
"PUBLIC".JSON_VALUE("__Z0"."DATA", '$.network.name') AS "__C0_1",
"PUBLIC".JSON_QUERY("__Z0"."DATA", '$.rating') AS "__C0_2"
FROM "PUBLIC"."SHOWS" "__Z0"
/* PUBLIC.SHOWS_FIRST_EPISODE_NAME_AIRST |
| SELECT
"__C0_0" AS "NAME",
"__C0_1" AS "NETWORK",
"__C0_2" AS "RATING"
FROM "PUBLIC"."__T0"
/* PUBLIC.merge_scan */

The query result is:

+-------------------+------------+--------------------+
| NAME | NETWORK | RATING |
+-------------------+------------+--------------------+
| The Blue Planet | BBC One | {"average":9.1} |
+-------------------+------------+--------------------+

1 row selected (0.007 seconds)

Wasn't that fast? From 23.731 sec to 0.007 sec - more than x3300 improvement!

Now clean up (optional):

DROP TABLE IF EXISTS shows;

Automation​

Flattening using UPDATE is good for this tutorial. To automate this, you can add CacheInterceptor to your CacheConfiguration:

import org.apache.ignite.Ignition;
import org.apache.ignite.cache.CacheInterceptorAdapter;
import org.apache.ignite.cache.query.annotations.QuerySqlField;
import org.apache.ignite.configuration.CacheConfiguration;
import org.gridgain.internal.processors.query.h2.sql.JsonFunctions;
import javax.cache.Cache;
import java.io.Serializable;

public class JsonBasics {

public static void main(String[] args) {
CacheConfiguration cacheCfg = new CacheConfiguration("shows");
// set up cache mode, backup, etc.
cacheCfg.setIndexedTypes(Integer.class, ShowJson.class);
cacheCfg.setInterceptor(new ShowJsonFlattenInterceptor());

IgniteConfiguration cfg = new IgniteConfiguration();
cfg.setCacheConfiguration(cacheCfg);

Ignition.start(cfg);
}
}

class ShowJsonFlattenInterceptor extends CacheInterceptorAdapter<Integer, ShowJson> {

@Override
public ShowJson onBeforePut(Cache.Entry<Integer, ShowJson> entry, ShowJson newVal) {
// This method is called within IgniteCache.put(k, v)
String json = newVal.getData();
String firstEpisodeName = JsonFunctions.jsonValue(json, "$._embedded.episodes[0].name");
String firstEpisodeAirstamp = JsonFunctions.jsonValue(json, "$._embedded.episodes[0].airstamp");

ShowJson result = new ShowJson(json);
result.setFirstEpisodeName(firstEpisodeName);
result.setFirstEpisodeAirstamp(firstEpisodeAirstamp);
return result;
}
}

class ShowJson implements Serializable {

@QuerySqlField
private String data;

@QuerySqlField(name = "first_episode_name")
private String firstEpisodeName;

@QuerySqlField(name = "first_episode_airstamp")
private String firstEpisodeAirstamp;

public ShowJson(String data) {
this.data = data;
}
// getters and setters
}

Conclusion​

You have successfully completed the tutorial. You have learned how to query semi-structured JSON data, as well as how to improve the JSON query performance by rearranging the data and adding well-chosen indexes.