I have 3 tables:
"tags"
id serial
name text
"questions"
id serial
content jsonb
"questions_tags" (many-to-many)
question_id integer
tag_id integer
I can select all data from questions:
SELECT * FROM questions;
I can select tags of specific question:
SELECT t.*
FROM tags t
JOIN questions_tags qt ON t.id = qt.tag_id
JOIN questions q ON qt.question_id = q.id
WHERE q.id = 157;
I need to select all questions and add to this selection tags for every question.
I'm using nodejs and node-postgres and I need to get something like:
[
{
id: 1,
content: { someProp: 'someValue' },
tags: [
{ id: 58, name: 'sometag58' },
{ id: 216, name: 'sometag216' }
]
},
...
]
How can I do this without ORM, only with the help of vanilla SQL?
I would let PostgreSQL handle it for me:
with tagjson as (
select q.id, q.content, jsonb_agg(to_jsonb(tags)) as tags
from questions q
left join questions_tags qt on qt.question_id = q.id
left join tags on tags.id = qt.tag_id
group by q.id, q.content
)
select jsonb_pretty(jsonb_agg(to_jsonb(tagjson))) as result
from tagjson;
Result:
[
{
"id": 1,
"tags": [
{
"id": 58,
"name": "sometag58"
},
{
"id": 216,
"name": "sometag216"
}
],
"content": "some question 1 content"
}
]
db<>fiddle here
EDIT TO ADD:
Based on a conversation in the comments, the final query should be modified to:
select * from tagjson;
Or the CTE could be promoted to be a stand-alone query.
This allowed the conventional node syntax while preserving the aggregated tags array under the tags key:
const questions: Question[] = result.rows;
Related
My Postgres DB Table BookingDetails has columns - id(bigint), info(jsonb)
My Json structure:
"bookingType": “special”,
“travellers”: [
{
"id": 1,
"nationality": "SomeValue",
}
],
“someTag”: “abc”
}
The travellers here is an array (and in this example, there’s only one element)
I want to:
Select records where nationality=‘CertainValue’
Update records set nationality=‘AnotherValue’ where nationality=‘CertainValue’
I could retrieve the array using:
select CAST (info->'travellers' AS TEXT) from BookingDetails
[
{
"id": 1,
"nationality": "SomeValue",
}
]
I could retrieve the array first element using:
select CAST (info->'travellers'->>0 AS TEXT) from BookingDetails
{
"id": 1,
"nationality": "SomeValue"
}
But how to do a select based on nationality=‘CertainValue’?
try this :
SELECT jsonb_set(info, array['travellers', (a.id - 1) :: text, 'nationality'], to_jsonb('AnotherValue' :: text))
FROM BookingDetails
CROSS JOIN LATERAL jsonb_array_elements(info->'travellers') WITH ORDINALITY AS a(content, id)
WHERE a.content->>'nationality' = 'CertainValue'
see the test result in dbfiddle
I am new to SQL previously we were using MongoDB and now we have shifted to Postgres. basically, we have 2 tables of Questions and Options. Each question can have multiple option so I design the database just like below table.
Table : dialogues
dialogue_id
dialogue_text
...
Table : options
option_id
option_name
dialogue_id ( which is FK for table question)
Now I am trying to get all the questions with their options. I have tried inner join like this
SELECT options.option_name, dialogues.*
FROM options INNER JOIN dialogues ON dialogues.dialogue_id = options.dialogue_id)
But it's not what I wanted.
Just to give you an example in Mongo we have used this query
const aggregateQuery = [
{
$lookup: {
from: "options", // The collection you want to join to
localField: "_id", // The field in orders you want to join on
foreignField: "question_id", // The field in products you want to join on
as: "options" // What you want to call the array that stores the joined documents from products
}
},
}
]
const questionList = await questionModel.aggregate(aggregateQuery)
In result I wanted all the dialogue along with field called "options" which contains all the relevant options from table "options".
let me share the final JSON that I get from mongo.
[
{
"_id": "yyyyy",
"question": "What is your gender?",
"options": [
{
"_id": "xxxxx",
"option": "Male",
"question_id": "yyyyy",
"created_at": "2020-07-04T05:57:00.293Z",
"updated_at": "2020-07-04T05:57:00.293Z"
},
{
"_id": "xxxx",
"option": "Female",
"question_id": "yyyyy",
"created_at": "2020-07-04T05:57:00.293Z",
"updated_at": "2020-07-04T05:57:00.293Z"
}
]
}
]
can anybody help ?
This may be your solution.
select jsonb_agg(to_jsonb(t)) from
(
select
d.dialogue_id "_id",
d.dialogue_text "question",
(
select
jsonb_agg(jsonb_build_object('_id', o.option_id, 'option', o.option_name))
from options o
where o.dialogue_id = d.dialogue_id
) "options"
from dialogues d
) t;
Here is a JOIN/GROUP BY version
select jsonb_agg(to_jsonb(t)) from
(
select
d.dialogue_id "_id",
d.dialogue_text "question",
jsonb_agg(jsonb_build_object('_id', o.option_id, 'option', o.option_name)) "options"
from dialogues d inner join options o using (option_id)
group by d.dialogue_id, d.dialogue_text
) t;
After reading through documentation I can't find anything about how to make this simple query:
there is two tables:
first one is "PACKAGES"
{
id: 1,
name: 'package_1',
sender_id: 1,
type: 'shipping'
}
second one is "USERS"
{
id: 1,
name: 'user_1'
}
When I perform Left, Right Join, or simply JOIN, its just merges this two tables into one. And id, name just overwrites with each other
What I need is:
{
id: 1,
name: 'package_1',
sender_id: 1
type: 'shipping',
user: { // second table as nested object
id: 1,
name: 'user_1'
}
}
I tried everything that I can find, for example:
SELECT * FROM packages JOIN users AS user ON packages.sender_id=user.id;"
How I can put right table as an object inside left table?
Use to_json to present the query result as a nested JSON object:
select to_json(res) from
(
select p.*, to_json(u) "user"
from packages p
inner join users u
on p.sender_id = u.id
) res;
Result:
{
"id": 1,
"name": "package_1",
"sender_id": 1,
"type": "shipping",
"user": {
"id": 1,
"name": "user_1"
}
}
I want to select articles containing certain category I'm using typeorm ( SQL )
let's imagine i have
articleTable: [
{
id: 1,
title: 'string'
},
{
id: 2,
title: 'string'
}
];
categoryTable: [
{
id: 1,
title: 'string'
}, {
id: 2,
title: 'string'
}
]
article_categories: [
{
categoryId: 1,
articleId: 2
},{
categoryId: 1,
articleId: 1
},{
categoryId: 2,
articleId: 2
}
]
so the question is I want to select all articles that have exactly category = [1,2] and more
that means if an article has only category 1 I don't want to select it.
using this query I'm getting results but its not exact match of ids but instead it returns articles that have id 1 and 2 separately
async filterData(filterArticleDto: FilterArticlesDto, categoryIds: number[]) {
const qb = this.createQueryBuilder('article');
qb.distinct(true);
if (categoryIds.length) {
qb.innerJoin(
'article.categories',
'categories',
`categories.category IN (:...ids)`,
{
ids: categoryIds,
},
);
qb.leftJoinAndSelect('categories.category', 'catsData');
}
qb.take(filterArticleDto.limit);
return await qb.getMany();
}
raw sql query
SELECT article.*
FROM articles AS article
WHERE EXISTS
(SELECT count(DISTINCT category_id)
FROM article_categories AS subcts
WHERE subcts.article_id = article.id
AND subcts.category_id IN (1,
2)
HAVING count(DISTINCT category_id) = 2)
https://dbfiddle.uk/?rdbms=postgres_12&fiddle=86f8d3a8bdc9248a918a4c37d8da5ea0
OR
SELECT article.*
FROM articles AS article
INNER JOIN
(SELECT article_id,
array_agg(category_id) AS cts
FROM article_categories
GROUP BY article_id) AS category ON article.id = category.article_id
WHERE ARRAY[1,
2::integer] = category.cts
https://dbfiddle.uk/?rdbms=postgres_12&fiddle=fdde52c1d11d69b690f9fe9345272786
imagine you have website where people post their ads. So, each ad has some selected properties, for example cars has different engine types, gears, colors and etc. Those properties user selects before submiting a listing.
I store selected properties in a jsonb format in listings table, look at the data column:
.
So, each listing contains data like this:
{
"properties":[
{
"id":"1",
"value_id":"1"
},
{
"id":"2",
"value_id":"5"
},
{
"id":"3",
"value_id":"9"
},
{
"id":"4",
"value":"2.0"
},
{
"id":"7",
"value":"2017"
},
{
"id":"6",
"value":"180.000"
}
]
}
Now, the question is:
1) How to filter listings by those id's and value's which are in json? For example, show listings where id = 2 and it's value = 5 AND id = 3 and it's value = 9 and so on. I dont need OR, i need AND. So, filter data by multiple id's and value's.
2) First point + ability to compare id's and value's (greater or lower than).
answering first point, it's probably the first time when I find use for jsonb[]:
t=# with c(a,j) as (values(18,'{
"properties":[
{
"id":"1",
"value_id":"1"
},
{
"id":"2",
"value_id":"5"
},
{
"id":"3",
"value_id":"9"
},
{
"id":"4",
"value":"2.0"
},
{
"id":"7",
"value":"2017"
},
{
"id":"6",
"value":"180.000"
}
]
}'::jsonb), (19,'{"properties":[{"id": "1", "value_id": "1"}]}'))
, m as (select a, array_agg(jb.value)::jsonb[] ar from c, jsonb_array_elements(j->'properties') jb group by a)
select a
from m
where '{"id": "1", "value_id": "1"}'::jsonb = any(ar)
and '{"id": "3", "value_id": "9"}'::jsonb = any(ar);
a
----
18
(1 row)
and for the second requirement - it won't be that short, as you need to compare (and thus parse json):
t=# with c(a,j) as (values(18,'{
"properties":[
{
"id":"1",
"value_id":"1"
},
{
"id":"2",
"value_id":"5"
},
{
"id":"3",
"value_id":"9"
},
{
"id":"4",
"value":"2.0"
},
{
"id":"7",
"value":"2017"
},
{
"id":"6",
"value":"180.000"
}
]
}'::jsonb), (19,'{"properties":[{"id": "1", "value_id": "1"}]}'))
, m as (select a, jb.value->>'id' id,jb.value->>'value_id' value_id from c, jsonb_array_elements(j->'properties') jb)
, n as (select m.*, count(1) over (partition by m.a)
from m
join c on c.a = m.a and ((id::int >= 1 and value_id::int <2) or (id::int >2 and value_id::int <= 9)))
select distinct a from n
where count > 1;
a
----
18
(1 row)
with basic idea to use OR to get possible rows and then check if ALL of OR conditions were met