Combining JSON_SEARCH and JSON_EXTRACT get me: "Invalid JSON path expression." - mysql

I have a table names "campaigns". One of the columns is named "filter_apps" and his type is JSON
I have file rows and they just contain array of tokens like so:
["be3beb1fe916ee653ab825fd8fe022", "c130b917983c719495042e31306ffb"]
["4fef3f1999c78cf987960492da4d2a"]
["106c274e319bdeae8bcf8daf515b1f"]
["2521f0df6cffb7487d527319674cf3"]
["c130b917983c719495042e31306ffb"]
Examples:
SELECT JSON_SEARCH(filter_apps, 'one', 'c130b917983c719495042e31306ffb') FROM campaigns;
Result:
"$[1]"
null
null
null
"$[0]"
Right now everything is correct, the matched columns come back. If I make a test I can prove it:
SELECT JSON_EXTRACT(filter_apps, '$[1]') FROM campaigns;
Result
"c130b917983c719495042e31306ffb"
null
null
null
null
So at this point I think I can extract the values using JSON_EXTRACT, my query:
SELECT JSON_EXTRACT(filter_apps, JSON_SEARCH(filter_apps, 'one', 'c130b917983c719495042e31306ffb')) FROM campaigns;
That leads me to an error:
"[42000][3143] Invalid JSON path expression. The error is around character position 1."

SOLUTION
Simple as that:
SELECT JSON_EXTRACT(filter_apps, JSON_UNQUOTE(JSON_SEARCH(filter_apps, 'one', 'c130b917983c719495042e31306ffb'))) FROM campaigns;
Problem resolved! I wrap JSON_SEARCH in a JSON_UNQUOTE method!
A little tip, I found the solution here: https://dev.mysql.com/doc/refman/5.7/en/json-function-reference.html

It took me hours, as my JSON object is way more complex, but I found the solution for the 'all' option.
SELECT *,
REPLACE(REPLACE(LTRIM(SUBSTRING_INDEX(SUBSTRING_INDEX(filter_apps, ',', n), ',', -1)), '[', ''), ']', '') AS all_json
FROM (
SELECT *, JSON_EXTRACT(filter_apps, JSON_UNQUOTE(JSON_SEARCH(filter_apps, 'all', 'c130b917983c719495042e31306ffb'))) AS hit
FROM campaigns
) AS t
JOIN (SELECT #N := #N +1 AS n FROM campaigns, (SELECT #N:=0) dum LIMIT 10) numbers
ON CHAR_LENGTH(filter_apps) - CHAR_LENGTH(REPLACE(filter_apps, ',', '')) >= n - 1
WHERE hit IS NOT NULL;
# for the "JOIN-FROM" use a table that has more or equal entries than the length of your longest JSON array
# make sure the "JOIN-LIMIT" is higher or equal than the length of your longest JSON array
Query Explanation:
Inner SELECT:
Main Select as asked in question with JSON_SEARCH Option 'all'
JOIN:
a) SELECT table 'numbers':
create a table which contains the numbers from 1 to user defined LIMIT.
compare SQL SELECT to get the first N positive integers
b) JOIN ON combined with Outer SELECT SUBSTRING_INDEX:
splits the defined array column 'filter_apps' to the number of element of the array. Note user defined limit of 2)a) must be equal or greater than the longest array to split. compare SQL split values to multiple rows
REPLACE and LTRIM of Outer SELECT:
used to remove remaining brackets and spaces of previous array
WHERE clause:
to show only matching results of Inner SELECT

Related

BigQuery: Count non-null values across all columns with REGEX

I have the following query that helps me count how many null values were reported in each column across all columns of a table in BQ:
SELECT col_name, COUNT(1) nulls_count
FROM table t,
UNNEST(REGEXP_EXTRACT_ALL(TO_JSON_STRING(t), r'"(\w+)":null')) col_name
GROUP BY col_name
;
I need to adjust it so it counts the non-null values. I tried to use negative lookahead but it doesn't seem to work.
My end goal is to indicate wether a certain column reports at least 1 non-null value.
Input example (the table):
Output example:
column_c is not present since all of its values are nulls.
You can try this, (without REGEX) solution
select * from (select column, countif(val!= 'null') non_null
from `dataset.table` table1
,unnest(array(
select as struct trim(ar[offset(0)], '"') column, trim(ar[offset(1)], '"') val
from unnest(split(trim(to_json_string(table1), '{}'))) pb,
unnest([struct(split(pb, ':') as ar)])
)) record
group by column) where non_null!=0
output:

How to extract a value from JSON that repeats multiple times?

I have the following table:
I need to create a select that returns me something like this:
I have tried this code:
SELECT Code, json_extract_path(Registers::json,'sales', 'name')
FROM tbl_registers
The previous code returns me a NULL in json_extract_path, I have tried the operator ::json->'sales'->>'name', but doesn't work too.
You need to unnest the array, and the aggregate the names back. This can be done using json_array_elements with a scalar sub-query:
select code,
(select string_agg(e ->> 'name', ',')
from json_array_elements(t.products) as x(e)) as products
from tbl_registers t;
I would also strongly recommend to change your column's type to jsonb
step-by-step demo:db<>fiddle
SELECT
code,
string_agg( -- 3
elems ->> 'name', -- 2
','
) as products
FROM tbl_registers,
json_array_elements(products::json) as elems -- 1
GROUP BY code
If you have type text (strictly not recommended, please use appropriate data type json or jsonb), then you need to cast it into type json (I guess you have type text because you do the cast already in your example code). Afterwards you need to extract the array elements into one row per element
Fetch the name value
Reaggregate by grouping and use string_agg() to create the string list

mysql get max number from a string field

I need to get maximum number from a part of the value that generally start with year followed by slash(/). So I need a maximum number after the slash(/) but year should be 2016
2016/422
2016/423
2016/469
2016/0470
2014/777
2015/123
2015/989
I tried this query
SELECT columname FROM tablename WHERE columname LIKE '2016/%' ORDER BY id DESC
the above query always giving '2016/469' as first record, how to get '2016/0470' as the maximum number?
any help will be much appreciated.
Thank you.
If columname follows that pattern YEAR/0000, you can use SUBSTRING function from MySQL to remove the part of the string you don't want.
SELECT value FROM (
SELECT CAST(SUBSTRING(columname, 0, 4) AS UNSIGNED) as year, CAST(SUBSTRING(columname FROM 6) AS UNSIGNED) as value FROM tablename
) total
ORDER BY year DESC, value DESC
LIMIT 1;
You need to split the string into 2 parts and evaluate them as numbers, instead of strings. The following formula will return the number after the / in the fieldname. All functions used below are described in the string functions section of the MySQL documentation. This way you can get the number after the / character, even if it is not year before the /, but sg else. The + 0 converts the string to a number, eliminating any leading 0.
select right(columnname, char_length(columnname)-locate('/',columnname)) + 0
from tablename
Just take the max() of the above expression to get the expected results.
UPDATE:
If you need the original number and the result has to be restricted to a specific year, then you need to join back the results to the original table:
select columnname
from tablename t1
inner join (select max(right(t.columnname, char_length(t.columnname)-locate('/',t.columnname)) + 0) as max_num
from tablename t
where left(t.columnname,4)='2016'
) t2
on right(t1.columnname, char_length(1t.columnname)-locate('/',t1.columnname)) + 0 = t2.max_num
where left(t1.columnname,4)='2016'
There are lots of suggestions given as answers already. But some of those seem overkill to me.
Seems like the only change needed to the OP query is the expression in the ORDER BY clause.
Instead of:
ORDER BY id
We just need to order by the numeric value following the slash. And there are several approaches, several expressions, that will get that from the example data.
Since the query already includes a condition columname LIKE '2016/%'
We can get the characters after the first five characters, and then convert that string to a numeric value by adding zero.
ORDER BY SUBSTRING(columname,6) + 0 DESC
If we only want to return one row, add
LIMIT 1
http://dev.mysql.com/doc/refman/5.7/en/string-functions.html#function_substring
If we only want to return the numeric value, we could use the same expression in the SELECT list, in addition columnname.
This isn't the only approach. There are lots of other approaches that will work, and don't use SUBSTRING.
Try like this:
SELECT
MAX(CAST(SUBSTRING(t.name,
LOCATE('/', t.name) + 1)
AS UNSIGNED)) AS max_value
FROM
tablename AS t;
You can try with this little uggly approach:
SELECT t.id, t2.secondNumber FROM table AS t
JOIN (SELECT id,
CONCAT(SUBSTRING(field,1,5),
if(SUBSTRING(SUBSTRING(field, 6),1,1)='0',
SUBSTRING(field, 6),
SUBSTRING(field,7)
)
) as secondNumber FROM table ) AS t2 ON t2.id=t.id
ORDER BY t2.secondNumber DESC
Would be valid only if the 0 (zeroes) before the second number (after the slash) are no more than 1.
Or if the year doesn`t matter you can try to order them only by the second number if it is ok:
SELECT t.id, t2.secondNumber FROM table AS t
JOIN (SELECT id,
if(SUBSTRING(SUBSTRING(field, 6),1,1)='0',
SUBSTRING(field, 6),
SUBSTRING(field,7)
) as secondNumber FROM table ) AS t2 ON t2.id=t.id
ORDER BY t2.secondNumber DESC

Creating Temp Variables within Queries

I would like to be able to create a temp variable within a query--not a stored proc nor function-- which will not need to be declared and set so that I don't need to pass the query parameters when I call it.
Trying to work toward this:
Select field1,
tempvariable=2+2,
newlycreatedfield=tempvariable*existingfield
From
table
Away from this:
DECLARE #tempvariable
SET #tempvariable = 2+2
Select field1,
newlycreatedfield=#tempvariable*existingfield
From
table
Thank you for your time
I may have overcomplicated the example; more simply, the following gives the Invalid Column Name QID
Select
QID = 1+1
THN = QID + 1
If this is housed in a query, is there a workaround?
You can avoid derived tables and subqueries if you do a "hidden" assignment as a part of a complex concat_ws expression
Since the assignment is part of the expression of the ultimate desired value for the column, as opposed to sitting in its own column, you don't have to worry about whether MySQL will evaluate it in the correct order. Needless to say, if you want to use the temp var in multiple columns, then all bets are off :-/
caveat: I did this in MySQL 5.1.73; things might have changed in later versions
I wrap everything in concat_ws because it coalesces null args to empty strings, whereas concat does not.
I wrap the assignment to the var #stamp in an if so that it is "consumed" instead of becoming an arg to be concatenated. As a side note, I have guaranteed elsewhere that u.status_timestamp is populated when the user record is first created. Then #stamp is used in two places in date_format, both as the date to be formatted and in the nested if to select which format to use. The final concat is an hour range "h-h" which I have guaranteed elsewhere to exist if the c record exists, otherwise its null return is coalesced by the outer concat_ws as mentioned above.
SELECT
concat_ws( '', if( #stamp := ifnull( cs.checkin_stamp, u.status_timestamp ), '', '' ),
date_format( #stamp, if( timestampdiff( day, #stamp, now() )<120, '%a %b %e', "%b %e %Y" )),
concat( ' ', time_format( cs.start, '%l' ), '-', time_format( cs.end, '%l' ))
) AS as_of
FROM dbi_user AS u LEFT JOIN
(SELECT c.u_id, c.checkin_stamp, s.start, s.end FROM dbi_claim AS c LEFT JOIN
dbi_shift AS s ON(c.shift_id=s.id) ORDER BY c.u_id, c.checkin_stamp DESC) AS cs
ON (cs.u_id=u.id) WHERE u.status='active' GROUP BY u.id ;
A final note: while I happen to be using a derived table in this example, it is only because of the requirement to get the latest claim record and its associated shift record for each user. You probably won't need a derived table if a complex join is not involved in the computation of your temp var. This can be demonstrated by going to the first fiddle in #Fabien TheSolution's answer and changing the right hand query to
Select field1, concat_ws( '', if(#tempvariable := 2+2,'','') ,
#tempvariable*existingfield ) as newlycreatedfield
from table1
Likewise the second fiddle (which appears to be broken) would have a right hand side of
SELECT concat_ws( '', if(#QID := 2+2,'',''), #QID + 1) AS THN
You can do this with subqueries:
Select field1, tempvariable,
(tempvariable*existingfield) as newlycreatedfield
from (select t.*, (2+2) as tempvariable
from table t
) t;
Unfortunately, MySQL has a tendency to actually instantiate (i.e. create) a derived table for the subquery. Most other databases are smart enough to avoid this.
You can gamble that the following will work:
Select field1, (#tempvariable := 2+2) as tempvariable,
(#tempvariable*existingfield) as newlycreatedfield
From table t;
This is a gamble, because MySQL does not guarantee that the second argument is evaluated before the third. It seems to work in practice, but it is not guaranteed.
Why not just:
SET #sum = 4 + 7;
SELECT #sum;
Output:
+------+
| #sum |
+------+
| 11 |
+------+
source
You can do something like this :
SELECT field1, tv.tempvariable,
(tv.tempvariable*existingfield) AS newlycreatedfield
FROM table1
INNER JOIN (SELECT 2+2 AS tempvariable) AS tv
See SQLFIDDLE : http://www.sqlfiddle.com/#!2/8b0724/8/0
And to refer at your simplified example :
SELECT var.QID,
(var.QID + 1) AS THN
FROM (SELECT 1+1 as QID) AS var
See SQLFIDDLE : http://www.sqlfiddle.com/#!2/d41d8/19140/0

Map integer(id) to text string?

In my database, I'm storing a field called "type" as tinyInt, since I don't have that many types I decided not to create a table for storing the corresponding names of all these types.
When I query my table I want the types to be replaced by their corresponding names which I'm storing in php arrays. I wonder if there is a way to do this replacement inside the sql statement itself instead of looping through the results and do replacement on the returned result row.
Does something like this even exists:
select *, map(type, {1=>'abc', 2 => 'xyz'}) from orders
The only way (I'm fairly sure) to do this is using a CASE .. WHEN ... construct:
SELECT *, CASE type WHEN 1 THEN 'abc' WHEN 2 THEN 'xyz' END as stringType
FROM orders
You can also use the ELSE clause to specify a default, e.g. CASE type WHEN 1 THEN 'abc' WHEN 2 THEN 'xyz' ELSE 'unknown' END.
Actually, in contrast to the answer by mathematical.coffee, there actually is another way in MySQL to map a number to text - at least if your numbers are contiguous:
The ELT() function returns the N-th element of the list of strings: str1 if N = 1, str2 if N = 2, and so on. Returns NULL if N is less than 1 or greater than the number of arguments.
Example:
mysql> SELECT ELT(1, 'ej', 'Heja', 'hej', 'foo');
-> 'ej'
mysql> SELECT ELT(4, 'ej', 'Heja', 'hej', 'foo');
-> 'foo'
Source:
https://dev.mysql.com/doc/refman/5.0/en/string-functions.html#function_elt