How to select a parameter from json-like structure in Postgresql? - json

My database has a column that contains json-like data structure (but no actual json markup) It looks like this:
---
ID: "-2293428132623007080"
RES_ID: '0'
NUMBER: "ДЕП002М Москва-Черновцы"
TRIP_SEATS_MAP: 'true'
SHOW_PICKUP_DROPOFF: 'false'
HAS_TRANSFER: 'false'
PLACES_COUNT: '49'...
I need to select HAS_TRANSFER value from it. Parameters are separated as rows, there is no delimiter. Is there a way to select a separate parameter value?

One way is just extracting data between HAS_TRANSFER: and line break \r
with t(col) as(
select 'ID: "-2293428132623007080"
RES_ID: ''0''
NUMBER: "ДЕП002М Москва-Черновцы"
TRIP_SEATS_MAP: ''true''
SHOW_PICKUP_DROPOFF: ''false''
HAS_TRANSFER: ''false''
PLACES_COUNT: ''49'''::text
union all
select 'ID: "-2293428132623007080"
RES_ID: ''0''
NUMBER: "ДЕП002М Москва-Черновцы"
TRIP_SEATS_MAP: ''true''
SHOW_PICKUP_DROPOFF: ''false''
HAS_TRANSFER:''bla''
PLACES_COUNT: ''49'''::text
)
-- below is actual query:
select substring(col from 'HAS_TRANSFER:\s*([^\r]+)') from t

Related

Oracle: JSON object members inside CASE expression?

I'm trying to assemble a JSON document with mixed types like [["array"],{"data":"object"},"string"] from a conditional expression.
If I put only JSON types inside, Oracle somehow surmises that I'm composing them inside the outer document:
with t as (
select 'array' data from dual union ALL
select 'object' from dual union all
select 'string' from dual
)
select json_arrayagg(
case data
when 'array' then json_array(data)
when 'object' then json_object('key' value data)
end json
)
from t
/
JSON
------------------------------------------------
[["array"],{"data":"object"}]
As soon as I add a string to the mix, the results of the CASE expression are all treated as strings and encoded accordingly:
with t as (
select 'array' data from dual union ALL
select 'object' from dual union all
select 'string' from dual
)
select json_arrayagg(
case data
when 'array' then json_array(data)
when 'object' then json_object('key' value data)
else data
end
)
from t
/
JSON
------------------------------------------------
["[\"array\"]","{\"data\":\"object\"}","string"]
But if I tell json_arrayagg to treat it as JSON, the last string member is not handled, resulting in an invalid document:
with t as (
select 'array' data from dual union ALL
select 'object' from dual union all
select 'string' from dual
)
select json_arrayagg(
case data
when 'array' then json_array(data)
when 'object' then json_object('data' value data)
else data
end FORMAT JSON
) json
from t
JSON
------------------------------------------------
[["array"],{"data":"object"},string]
I can't find any function like json_string to JSONify varchar2, and I'd prefer not to do it by hand.
SQL> select banner_full from v$version ;
BANNER_FULL
--------------------------------------------------------------------------
Oracle Database 19c Enterprise Edition Release 19.0.0.0.0 - Production
Version 19.13.0.0.0

Is there an elegant way to turn a BQ nested field in to a key:value JSON?

I would love the option to turn the event_params nested BQ field into a JSON field?
My desired output should look like this:
{"sessionId":123456789,"version":"1.005"}
Consider below
select *, (
select '{' || string_agg(format('%s:%s',
json_extract(kv, '$.key'),
json_extract(kv, '$.string_value')
)) || '}'
from unnest(json_extract_array(to_json_string(event_params))) kv
) json
from `project.dataset.table`
if applied to sample data in your question - output is
Update: I realized you changed/fixed data sample - so see updated query below
select *, (
select '{' || string_agg(format('%s:%s',
json_extract(kv, '$.key'),
json_extract(kv, '$.value.string_value')
)) || '}'
from unnest(json_extract_array(to_json_string(event_params))) kv
) json
from `project.dataset.table`
with output
I made a version where you can define number fields in the JSON object with proper format, and you can filter for certain keys to end up in the JSON object:
with t as (
-- fake example data with same format
select * from unnest([
struct([
struct('session_id' as key, struct('123' as string_value) as value),
('timestamp', struct('1234567')),
('version', struct('2.23.65'))
] as event_params)
,struct([struct('session_id',struct('645')),('timestamp',struct('7653365')),('version',struct('3.675.34'))])
])
)
-- actual query
select
event_params, -- original data for comparison
format('{ %s }', -- for each row create one json object:
(select -- string_agg will return one string with all key-value pairs comma-separated
string_agg( -- within aggregation create key-value pairs
if(key in ('timestamp','session_id'), -- if number fields
format('"%s" : %s',key,value.string_value), -- then number format
format('"%s" : "%s"',key,value.string_value)) -- else string format
, ', ')
from unnest(event_params) -- unnest turns array into a little table per row, so we can run SQL on it
where key in ('session_id','version') -- filter for certain keys
) -- subquery end
) as json
from t

MySql - Query Error: Error: ER_OPERAND_COLUMNS: Operand should contain 1 column(s)

I have tbl which include 2 columns: title and params, the values are like the following:
- title: {"Teaching"}
- params:
{ "ufield926":"34",
"ufield927":"Sud",
"ufield928":"Ara",
"ufield929":"Mecca",
"ufield930":"1\/1\/1983",
"ufield933":"011",
"ufield934":"Mub",
"ufield943":"SU\/HI\/14",
"ufield944":"Average",
"ufield946":"Female"
}
I want to extract the code after "ufield943": which is SU/HI/14 only and concatenate it with the value in title column to be like the following:
--> Teaching (SU/HI/14)
Here is the query I have tried:
SELECT title, CONCAT(title, "(", (select *,
substring(
params,
locate('ufield943', params) + 12,
locate('ufield944', params) - locate('ufield943', params) - 15
) FROM tbl), ")") AS title
FROM tbl;
I get the following error everytime I run the query
"Query Error: Error: ER_OPERAND_COLUMNS: Operand should contain 1 column(s)"
.
Mysql expects the exact field or value as SELECT statement fields. So, When you use an inner SELECT result as a field or value on outer SELECT, you have to return a specific value as a result.
So, You have to remove *, from your inner SELECT as the first step.
Another thing that you must do is removing title from outer SELECT. As your query CONCAT your title with the expected result, so there is no need of selecting title again.
So your query should like:
SELECT CONCAT(title, "(", (select
substring(
params,
locate('ufield943', params) + 12,
locate('ufield944', params) - locate('ufield943', params) - 21
) FROM master_code), ")") AS title
FROM master_code;
As a final note, This is not a good approach to saving data as JSON in MySQL and accessing data like this.
Thanks to MySQL (5.7.8 or upper), you can use MySQL JSON type features or handle this using a programing language.
Demo

Teradata Masking - Retain all chararcters at position 1,4,8,12,16 .... in a string and mask remaining characters with 'X'

I have a requirement where I need to mask all but characters in position 1,4,8,12,16.. for a variable length string with 'X'
For example:
Input string - 'John Doe'
Output String - 'JXXn xxE'
SPACE between the two strings must be retained.
Kindly help or reach out for more details if required.
I think maybe an external function would be best here, but if that's too much to bite off, you can get crafty with strtok_split_to_table, xml_agg and regexp_replace to rip the string apart, replace out characters using your criteria, and stitch it back together:
WITH cte AS (SELECT REGEXP_REPLACE('this is a test of this functionality', '(.)', '\1,') AS fullname FROM Sys_Calendar.calendar WHERE calendar_date = CURRENT_DATE)
SELECT
REGEXP_REPLACE(REGEXP_REPLACE((XMLAGG(tokenout ORDER BY tokennum) (VARCHAR(200))), '(.) (.)', '\1\2') , '(.) (.)', '\1\2')
FROM
(
SELECT
tokennum,
outkey,
CASE WHEN tokennum = 1 OR tokennum mod 4 = 0 OR token = ' ' THEN token ELSE 'X' END AS tokenout
FROM TABLE (strtok_split_to_table(cte.fullname, cte.fullname, ',')
RETURNS (outkey VARCHAR(200), tokennum integer, token VARCHAR(200) CHARACTER SET UNICODE)) AS d
) stringshred
GROUP BY outkey
This won't be fast on a large data set, but it might suffice depending on how much data you have to process.
Breaking this down:
WITH cte AS (SELECT REGEXP_REPLACE('this is a test of this functionality', '(.)', '\1,') AS fullname FROM Sys_Calendar.calendar WHERE calendar_date = CURRENT_DATE)
This CTE is just adding a comma between every character of our incoming string using that regexp_replace function. Your name will come out like J,o,h,n, ,D,o,e. You can ignore the sys_calendar part, I just put that in so it would spit out exactly 1 record for testing.
SELECT
tokennum,
outkey,
CASE WHEN tokennum = 1 OR tokennum mod 4 = 0 OR token = ' ' THEN token ELSE 'X' END AS tokenout
FROM TABLE (strtok_split_to_table(cte.fullname, cte.fullname, ',')
RETURNS (outkey VARCHAR(200), tokennum integer, token VARCHAR(200) CHARACTER SET UNICODE)) AS d
This subquery is the important bit. Here we create a record for every character in your incoming name. strtok_split_to_table is doing the work here splitting that incoming name by comma (which we added in the CTE)
The Case statement just runs your criteria swapping out 'X' in the correct positions (record 1, or a multiple of 4, and not a space).
SELECT
REGEXP_REPLACE(REGEXP_REPLACE((XMLAGG(tokenout ORDER BY tokennum) (VARCHAR(200))), '(.) (.)', '\1\2') , '(.) (.)', '\1\2')
Finally we use XMLAGG to combine the many records back into one string in a single record. Because XMLAGG adds a space in between each character we have to hit it a couple of times with regexp_replace to flip those spaces back to nothing.
So... it's ugly, but it does the job.
The code above spits out:
tXXs XX X XeXX oX XhXX fXXXtXXXaXXXy
I couldn't think of a solution, but then #JNevill inspired me with his idea to add a comma to each character :-)
SELECT
RegExp_Replace(
RegExp_Replace(
RegExp_Replace(inputString, '(.)(.)?(.)?(.)?', '(\1(\2[\3(\4', 2)
,'(\([^ ])', 'X')
,'(\(|\[)')
,'this is a test of this functionality' AS inputString
tXXs XX X XeXX oX XhXX fXXXtXXXaXXXy
The 1st RegExp_Replace starts at the 2nd character (keep the 1st character as-is) and processes groups of (up to) 4 characters adding either a ( (characters #1,#2,#4, to be replaced by X unless it's a space) or [ (character #3, no replacement), which results in :
t(h(i[s( (i(s[ (a( (t[e(s(t( [o(f( (t[h(i(s( [f(u(n(c[t(i(o(n[a(l(i(t[y(
Of course this assumes that both characters don't exists in your input data, otherwise you have to choose different ones.
The 2nd RegExp_Replace replaces the ( and the following character with X unless it's a space, which results in:
tXX[s( XX[ X( X[eXX( [oX( X[hXX( [fXXX[tXXX[aXXX[y(
Now there are some (& [ left which are removed by the 3rd RegExp_Replace.
As I still consider me as a beginner in Regular Expressions, there will be better solutions :-)
Edit:
In older Teradata versions not all parameters were optional, then you might have to add values for those:
RegExp_Replace(
RegExp_Replace(
RegExp_Replace(inputString, '(.)(.)?(.)?(.)?', '(\1(\2[\3(\4', 2, 0 'c')
,'(\([^ ])', 'X', 1, 0 'c')
,'(\(|\[)', '', 1, 0 'c')

Can Sybase CASE expressions have a default column name for their result?

I have a sybase query that is structured like this:
SELECT
case
when isnull(a,'') <> '' then a
else convert(varchar(20), b)
end
FROM table_name
WHERE b=123
It used to return the results of the 'case' in a column named 'converted'. It now returns the results of the 'case' in a column with an empty string name ''.
How could this be? Could there be some database configuration that defaults the results of a 'case' with no name?
(I've fixed the broken query by adding " as computed" after 'end' but now I'd like to know how it used to return as 'computed' before I added the fix?)
Is this what you want?
SELECT (case when isnull(a, '') <> '' then a
else convert(varchar(20), b)
end) as converted
-------------^
FROM table_name
WHERE b = 123;
By the way, you could write the select more succinctly as:
SELECT coalesce(nullif(a, ''), b) as converted