I had a hard time finding the values on a basis, as all the examples cite the same way of finding information in simple jsons.
But a friend from work gave me a solution and I came to share.
the initial question was: How to make a select in a nested json ???
A json like this:
{
"vehicle":[
{
"vehicle_type":"Truck",
"car_make":"Lotus",
"car_model":"Esprit",
"quantity":7,
"seats":7,
"price_hour":16,
"price_day":147,
"color":[
"Purple",
"Pink",
"Blue",
"White"
]
}
]
}
To view the structure, you can use the https://jsoneditoronline.org/
The answer I bring uses select with regex:
select * from example
where example.jsonTest->>'vehicle' ~ 'color"\s*:\s*"?.*?Blue.*"?';
and
select * from example
where example.jsonTest->>'vehicle' ~ 'vehicle_type"\s*:\s*"?.*?SUV.*"?';
To be clear I left a working example in https://rextester.com/BQZP48785
** sorry for my bad english
In order to determine the vehicle with color blue, and vehicle type with SUV, jsonb_array_elements() function might be used to unnest the main array, and then putting (j.elm->>'vehicle_type') = 'SUV' into the WHERE clause would be enough for the second, while (j.elm->>'color')::jsonb ? 'Blue' should be used containing jsonb conversion with ? operator for the first one, since (j.elm->>'color') extracts an array, while (j.elm->>'vehicle_type') does simple string pieces.
Therefore a regular expression is not needed such as below queries :
SELECT e.*
FROM example e
CROSS JOIN jsonb_array_elements(jsonTest->'vehicle') j(elm)
WHERE (j.elm->>'color')::jsonb ? 'Blue'
and
SELECT e.*
FROM example e
CROSS JOIN jsonb_array_elements(jsonTest->'vehicle') j(elm)
WHERE (j.elm->>'vehicle_type') = 'SUV'
Demo
Related
With the following SQL:
SELECT
CAST(JSON_VALUE(d.Data,'lax $.realSKosten') AS DECIMAL(18,5)) as ks,
CONVERT(float ,JSON_VALUE(d.Data,'lax $.realAKosten')) as ka
FROM UserEntityData as d
I get this result:
[{"ks":1.23000,"ka":1.230000000000000e+000},
{"ks":2.34500,"ka":2.345000000000000e+000}]
With huge resultsets, a big part of the result consists of zeros. Therefore it would be much better, if the resultset would look like this:
[{"ks":1.23,"ka":1.23},
{"ks":2.345,"ka":2.345}]
As shown, with "AS DECIMAL(18,5)" I can shorten it. But the number of decimals is fixed.
Is there a performant way to tell the SQL-Server to remove all zeros at the end, if there are any? Of course without converting it to varchar and doing any text-manipulation.
Not if you want the value to be treated as a numerical value, no. If you're happy with the values being quoted, however, you could use TRIM.
SELECT TRIM('0' FROM CONVERT(varchar(20),ks)) AS ks,
TRIM('0' FROM CONVERT(varchar(20),ka)) AS ka
FROM (VALUES(1.23000,1.23000),
(2.34500,2.34500))V(ks,ka)
FOR JSON AUTO;
Which results in:
[
{
"ks": "1.23",
"ka": "1.23"
},
{
"ks": "2.345",
"ka": "2.345"
}
]
I have a SQL request that is almost perfect (for what I want to do):
WITH liste_fichiers_joints AS (
SELECT
id_dans_table,
ARRAY_AGG (row_to_json(f)) ids_fichier
FROM
fichiers_joints fj
LEFT JOIN fichiers f ON f.id = fj.id_fichier
WHERE
nom_table = 'taches'
GROUP BY
id_dans_table
)
SELECT t.id, t.nom, lfj.ids_fichier
FROM taches t
JOIN liste_fichiers_joints lfj ON lfj.id_dans_table = t.id
As you may have guessed, I'd like to get in the same request getting all the tasks: the id of a task, the name of the task but also in an array all the ids and names of the attached files if there are any.
The result is nearly what I want, but the last column displays this:
{"{\"uuid\":\"fd809b1f-6849-4322-a654-67f70c46a435\",\"nom\":\"test.png\",\"date\":\"2020-11-17T01:21:24.223354\",\"status\":\"TMP\",\"id\":185}"}
I'd like to remove the uuid and status parts, I tried some subrequests, up to no avail.
Also, I'd like to remove the backslashes \, because otherwise it will be complicated to use this column as a JSON in my Javascript.
Does anybody has a clue?
Thanks in advance.
You can use json[b]_build_object() instead of row_to_json[b](): it accepts a list of key/value pairs, so you have fine-grained control about what is going into your objects.
Also, you most likely want a JSON array, rather than a Postgres array of JSON objects.
I would recommend changing this:
ARRAY_AGG (row_to_json(f)) ids_fichier
To:
jsonb_agg(
jsonb_build_object('nom', f.nom, 'date', f.date, 'id', f.id)
) as ids_fichier
My project is a Latin language learning app. My DB has all the words I'm teaching, in the table 'words'. It has the lemma (the main form of the word), along with the definition and other information the user needs to learn.
I show one word at a time for them to guess/remember what it means. The correct word is shown along with some wrong words, like:
What does Romanus mean? Greek - /Roman/ - Phoenician - barbarian
What does domus mean? /house/ - horse - wall - senator
The wrong options are randomly drawn from the same table, and must be from the same part of speech (adjective, noun...) as the correct word; but I am only interested in their lemma. My return value looks like this (some properties omitted):
[
{ lemma: 'Romanus', definition: 'Roman', options: ['Greek', 'Phoenician', 'barbarian'] },
{ lemma: 'domus', definition: 'house', options: ['horse', 'wall', 'senator'] }
]
What I am looking for is a more efficient way of doing it than my current approach, which runs a new query for each word:
// All the necessary requires are here
class Word extends Model {
static async fetch() {
const words = await this.findAll({
limit: 10,
order: [Sequelize.literal('RANDOM()')],
attributes: ['lemma', 'definition'], // also a few other columns I need
});
const wordsWithOptions = await Promise.all(words.map(this.addOptions.bind(this)));
return wordsWithOptions;
}
static async addOptions(word) {
const options = await this.findAll({
order: [Sequelize.literal('RANDOM()')],
limit: 3,
attributes: ['lemma'],
where: {
partOfSpeech: word.dataValues.partOfSpeech,
lemma: { [Op.not]: word.dataValues.lemma },
},
});
return { ...word.dataValues, options: options.map((row) => row.dataValues.lemma) };
}
}
So, is there a way I can do this with raw SQL? How about Sequelize? One thing that still helps me is to give a name to what I'm trying to do, so that I can Google it.
EDIT: I have tried the following and at least got somewhere:
const words = await this.findAll({
limit: 10,
order: [Sequelize.literal('RANDOM()')],
attributes: {
include: [[sequelize.literal(`(
SELECT lemma FROM words AS options
WHERE "partOfSpeech" = "options"."partOfSpeech"
ORDER BY RANDOM() LIMIT 1
)`), 'options']],
},
});
Now, there are two problems with this. First, I only get one option, when I need three; but if the query has LIMIT 3, I get: SequelizeDatabaseError: more than one row returned by a subquery used as an expression.
The second error is that while the code above does return something, it always gives the same word as an option! I thought to remedy that with WHERE "partOfSpeech" = "options"."partOfSpeech", but then I get SequelizeDatabaseError: invalid reference to FROM-clause entry for table "words".
So, how do I tell PostgreSQL "for each row in the result, add a column with an array of three lemmas, WHERE existingRow.partOfSpeech = wordToGoInTheArray.partOfSpeech?"
Revised
Well that seems like a different question and perhaps should be posted that way, but...
The main technique remains the same. JOIN instead of sub-select. The difference being generating the list of lemmas for then piping then into the initial query. In a single this can get nasty.
As single statement (actually this turned out not to be too bad):
select w.lemma, w.defination, string_to_array(string_agg(o.defination,','), ',') as options
from words w
join lateral
(select defination
from words o
where o.part_of_speech = w.part_of_speech
and o.lemma != w.lemma
order by random()
limit 3
) o on 1=1
where w.lemma in( select lemma
from words
order by random()
limit 4 --<<< replace with parameter
)
group by w.lemma, w.defination;
The other approach build a small SQL function to randomly select a specified number of lemmas. This selection is the piped into the (renamed) function previous fiddle.
create or replace
function exam_lemma_definition_options(lemma_array_in text[])
returns table (lemma text
,definition text
,option text[]
)
language sql strict
as $$
select w.lemma, w.definition, string_to_array(string_agg(o.definition,','), ',') as options
from words w
join lateral
(select definition
from words o
where o.part_of_speech = w.part_of_speech
and o.lemma != w.lemma
order by random()
limit 3
) o on 1=1
where w.lemma = any(lemma_array_in)
group by w.lemma, w.definition;
$$;
create or replace
function exam_lemmas(num_of_lemmas integer)
returns text[]
language sql
strict
as $$
select string_to_array(string_agg(lemma,','),',')
from (select lemma
from words
order by random()
limit num_of_lemmas
) ll
$$;
Using this approach your calling code reduces to a needs a single SQL statement:
select *
from exam_lemma_definition_options(exam_lemmas(4))
order by lemma;
This permits you to specify the numbers of lemmas to select (in this case 4) limited only by the number of rows in Words table. See revised fiddle.
Original
Instead of using a sub-select to get the option words just JOIN.
select w.lemma, w.definition, string_to_array(string_agg(o.definition,','), ',') as options
from words w
join lateral
(select definition
from words o
where o.part_of_speech = w.part_of_speech
and o.lemma != w.lemma
order by random()
limit 3
) o on 1=1
where w.lemma = any(array['Romanus', 'domus'])
group by w.lemma, w.definition;
See fiddle. Obviously this will not necessary produce the same options as your questions provides due to random() selection. But it will get matching parts of speech. I will leave translation to your source language to you; or you can use the function option and reduce your SQL to a simple "select *".
I have json stored in one of the columns in SQL Server and I need to modify it to remove the square brackets from it. The format is as below. Can't seem to find a good way of doing it.
[ { "Message":"Info: this is some message here.", "Active":true } ]
One way is to do it using below query, but this query is very very slow and I need to run on a very large set of data.
select a.value
from dbo.testjson e
cross apply OPENJSON(e.jsontext) as a
where isjson(e.jsontext) = 1
The only other way I can think of is just doing string manipulation but it can be error prone. Could someone help with this?
Ok, figured it out:
select
json_query(
'[{"Message":"Info: this is some message here.","Active":true}]',
'$[0]'
)
This will return the inner message.
You should add the property name, in this case Message, in order to get only that part. Keep in mind that it's case sensitive. Something like;
select json_value('[{"Message":"Info: this is some message here.","Active":true}]', '$[0].Message')
The question is about selection from JSON in PostgreSQL.
For example, application contains translation data in jsonb:
{
"en":{
"locale":"en",
"title":"Title",
"textShort":"Short text",
"textFull":"Full text"
}
"ru":{
"locale":"ru",
"title":"Заголовок",
"textShort":"Короткий текст",
"textFull":"Подробный текст"
}
}
This query works successfully:
select *
from content_records
where translations::json->'en'->>'title' like '%Title.';
But this query requires information about the locale, but the case is that we don't know anything about locales and search must be done for every locale, for example:
select *
from content_records
where translations::json->'any locale'->>'title' like '%Title.';
In MySQL it works as:
select *
from content_records
where LOWER(JSON_EXTRACT(translations, '$.*.title')) LIKE LOWER(:title);
There is the similar function in PostgreSQL:
json_extract_path, but it requires keywords and you can't miss the key as the symbol * does in MySQL.
The question is - how to do the selection of a nested JSON in this situation?
Unfortunately, in Postgres you have to "unnest" the keys first.
Something like this:
select t.*, cr.translations
from content_records cr
cross join lateral jsonb_object_keys(translations) as t(locale)
where lower(cr.translations -> t.locale ->> 'title') like '%title';
Note that if a title matches in more than one locale, you will get one row for each matching locale. If you don't want that, you can do the following:
select cr.*
from content_records cr
where exists (select *
from jsonb_object_keys(cr.translations) as t(locale)
where lower(cr.translations -> t.locale ->> 'title') like '%title')