2

I am using mariadb version 10.2.43-MariaDB-1:10.2.43+maria~bionic

Table schema

The table has a columns id attribute and details array with the following two rows of data :

{
  "id": 9,
  "details": [
    {
      "amount": 100,
      "flag": true,
      "fieldA": "abcd"
    },
    {
      "amount": 101,
      "flag": false,
      "fieldB": "bcde"
    },
    {
      "amount": 103,
      "flag": true,
      "fieldA": "abcd"
    }
  ]
},
{
  "id": 10,
  "details": [
    {
      "amount": 110,
      "flag": false,
      "fieldA": "abcd"
    },
    {
      "amount": 102,
      "flag": true,
      "fieldB": "bcde"
    }
  ]
}

I want to calculate the sum of amounts when the flag is true.

  • For id = 9 amounts = [100, 103]
  • For id = 10 amounts = [102]
  • Total = 100 + 103 + 102 = 305

On searching online I found a few answers suggesting json_table but we are using older version of mariadb which does not support json_table.

I have used this way to extract the amounts and flags and then finally handled the sum in code.

SELECT JSON_EXTRACT(features,'$. details[*].amount') as amounts,
       JSON_EXTRACT(features,'$.details[*].flag') as flag 
  FROM table
 WHERE JSON_EXTRACT(features,'$.details[*].flag') != 'NULL';

Output

-------------------------------------------------------------+---------------------------------------------------------------------+
| [500000, 1000000]                                           | [false, false]                                                      |
| [1100000]                                                   | [false]                                                             |
| [1000000]                                                   | [false]                                                             |
| [500000, 1000000]                                           | [false, false]                                                      |
| [100000]                                                    | [false]                                                             |
| [5000000]                                                   | [false]                                                             |                                                       |
| [50000]                                                     | [false]                                                             |
| [500000]                                                    | [false]                                                             |
| [500000]                                                    | [false]                                                             |
| [10000]                                                     | [true]                                                              |
| [49998]                                                     | [true]                                                              |
| [600000, 399980]                                            | [false, true]    

Questions:

  1. I want to know if the order of elements in amounts array and flag array will be the same as that in details. (otherwise the sum I am calculating will be wrong).
  2. Is there a more efficient way to calculate the sum without using code?
Gourav Roy
  • 163
  • 1
  • 2
  • 5

2 Answers2

1
  1. I want to know if the order of elements in amounts array and flag array will be the same as that in details. (otherwise the sum I am calculating will be wrong).

The documentation says:

Extracts data from a JSON document. [...]. Returns all matched values; either as a single matched value, or, if the arguments could return multiple values, a result autowrapped as an array in the matching order.

So yes, both calls to json_extract will give you an ordered slice of the json array. But note that, as far as your query goes, you are still left with the task of unesting each slice (the two arrays that you are getting)... which is the core of the task, and brings us to your second question.


  1. Is there a more efficient way to calculate the sum without using code?

MariaDB does not support json_table, which can expand a json array to rows. But we can emulate it with the help of a table of numbers and of other MariaDB json functions.

There are many different ways that you can create a table of numbers, either inline in the query or permanently stored in a table. Here I am just using a fixed list of a few numbers :

select sum( json_value( t.details, concat('$[', n.i, '].amount') ) ) total_amount
from mytable t
inner join (select 0 as i union all select 1 union all select 2) n 
    on n.i < json_length(t.details)
where json_value( t.details, concat('$[', n.i, '].flag') ) 

The join on n generate one row for each item in the JSON array; given the index of the element, we can generate the proper json path to its amount and flag (that's what the concat()s do), and access the values with json_value().

I don't have a MariaDB 10.2 database at hand to test, but here is a MariaDB 10.3 fiddle. Note the database properly recognizes the boolean value in the flag attribute, which simplifies the where clause.

GMB
  • 216,147
  • 25
  • 84
  • 135
  • mariadb does have json_table, but only in version 10.6+. note that 10.2 reached end of life a year ago and is no longer supported. – ysth May 14 '23 at 05:57
  • Thanks for answer what does this line "select 0 as i union all select 1 union all select 2" mean – Gourav Roy May 14 '23 at 12:08
  • @GouravRoy: that’s one (simple) way to create a table of numbers. – GMB May 14 '23 at 12:30
  • @GMB wanted to know that in query that I have written. if i get a row [1000, 2000] [true, false] then does it imply that 1000 has true flag and 2000 has false flag always. I mean the order is correct – Gourav Roy May 14 '23 at 12:58
  • @GouravRoy: I updated my answer to address that part of your original post – GMB May 15 '23 at 08:21
1

One option is to use JSON_EXTRACT() function while looping through by Recursive CTE which's supported for 10.2.2 + as in the following query

WITH RECURSIVE cte AS
(
 SELECT 0 i
 UNION ALL
 SELECT i + 1 i
   FROM cte
  WHERE i + 1 <= ( SELECT MAX(JSON_LENGTH(features, '$.details')) FROM t ) 
)
SELECT SUM(CASE 
           WHEN JSON_EXTRACT(features, CONCAT('$.details[',i,'].flag')) = 'true' THEN
                CAST(JSON_EXTRACT(features, CONCAT('$.details[',i,'].amount')) AS DOUBLE)
            END) AS sum_amount 
  FROM cte,
       t

Demo

Barbaros Özhan
  • 59,113
  • 10
  • 31
  • 55
  • Thanks for the answer. Can you please answer the first question as well . – Gourav Roy May 14 '23 at 11:26
  • You're welcome @GouravRoy . the order has no significance. – Barbaros Özhan May 14 '23 at 11:55
  • so it's not necessary that in my query result if i get a row [1000, 2000] [true, false] then 1000 has true flag and 2000 has false flag? – Gourav Roy May 14 '23 at 12:04
  • @GouravRoy No, I just mean the ***order*** of **objects within the array** is not important, of course each reciprocally related amount and flag attributes should reside at their own object. – Barbaros Özhan May 14 '23 at 12:56
  • SELECT JSON_EXTRACT(features,'$. details[*].amount') as amounts, JSON_EXTRACT(features,'$.details[*].flag') as flag FROM table WHERE JSON_EXTRACT(features,'$.details[*].flag') != 'NULL'; so in this query if I just add the corresponding amounts index which have true in the flag array it should give the same answer right? – Gourav Roy May 14 '23 at 14:52