Knex.js Getting values from comma-separated - sql

I have two SQlite3 tables task and tags
task is my master table and tags is storing tag names
I store comma-separated values in task
Now I want to get Tag names with use of a knex.js
table task
id task tags
---------------------
1 abc 1,2,3
2 xyz 3,1
3 apple 2
table tags
id tag
------------
1 cold
2 hot
3 normal
Now i want output as below
OUTPUT:
id task tags
---------------------
1 abc cold,hot,normal
2 xyz normal,cold
3 apple hot
I know i will have to use joins but not sure how to actually use it in knex.js. Please do help me.

Part of the problem is that your database is not properly normalised. Instead of having two tables task and tabs, with table tasks containing multiple tag IDs in the column 'tags' you should have three tables; 'tasks', 'tags' and the 'joining' table 'task_tags'. They would store the following data...
Tasks
id task
----------
1 abc
2 xyz
3 apple
Tags
id tag
------------
1 cold
2 hot
3 normal
task_tags
task_id tag_id
1 1
1 2
1 3
2 1
2 3
3 2
Now you can have as many tags as you like (whether or not any tasks use them) and as many tasks as you like (whether or not they use any tags) and you associate a task with it's tags via the task_tags table.
Then to get the result you want you would use the select
SELECT
tasks.id,
tasks.task,
GROUP_CONCAT(tags.tag) -- this gives you the csv line eg cold,hot,normal
from tasks
left join task_tags
ON tasks.id = task_tags.task_id
left join tags
on tags.id = task_tags.tag_id
GROUP BY task.id, tags.id
see https://www.sqlite.org/lang_aggfunc.html for explanation of GROUP_CONCAT

Your task table should be redesigned to hold one tag per row, not multiple tags in a single row:
id task tag
---------- ---------- ----------
1 abc 1
1 abc 2
1 abc 3
2 xyz 3
2 xyz 1
3 apple 2
Then it's easy:
SELECT task.id, task.task, group_concat(tags.tag, ',') AS tags
FROM task
JOIN tags ON task.tag = tags.id
GROUP BY task.id, task.task
ORDER BY task.id;
which gives
id task tags
---------- ---------- ---------------
1 abc cold,hot,normal
2 xyz normal,cold
3 apple hot
A design that follows the rules of relational databases makes life much easier (And the above can be normalized further; see the other answer); while some databases do support array types, sqlite is not one of them. If you insist on keeping your current design, though, there's an ugly hack involving the JSON1 extension and turning your CSV list of numbers into a JSON array:
SELECT task.id, task.task, group_concat(tags.tag, ',') AS tags
FROM task
JOIN json_each('[' || task.tags || ']') AS j
JOIN tags ON tags.id = j.value
GROUP BY task.id, task.task
ORDER BY task.id;

Related

Recursive tree search using postgresql join table

I have a table stories and a table blockings which has the columns story_id (referencing a story), and a blocked_story_id (also referencing a story, which is blocked by the story_id)
I'm trying to construct a query to return all the stories in order of precedence based on their blockers - so blockers first, traversing down the tree.
One story can be blocked by many stories, and can itself be a blocker for many stories.
I've been reading and re-reading the PostgreSQL docs on WITH RECURSIVE but I'm a little lost on where I should be going with this, and how to construct the relevant query.
I have got as far as:
select s.id, b.story_id as blocker_id
from stories s
left outer join blockings b on s.id = b.blocked_story_id
where s.deleted_at is null
as for getting a list of stories and their blockers, but some pointers as to what I need to join/union to get the desired result would be helpful.
Context
I want to know which stories I can work on first. So I want an output that contains all stories in an order that allows me to work top down and never hit a blocked story.
The content of the blockings table gives me a simple join table between stories that block one another. The story_id being the blocker, the blocked_story_id being the one being blocked.
Sample Data
Stories
id | title
------------------
1 | Story title 1
2 | Story title 2
3 | Story title 3
4 | Story title 4
5 | Story title 5
Blockings
story_id | blocked_story_id
---------------------------
4 | 2
4 | 3
3 | 1
3 | 5
I would expect to see the following result:
id | title
------------------
4 | Story title 4
2 | Story title 2
3 | Story title 3
1 | Story title 1
5 | Story title 5
Disclaimer: Because it is not clear to me why you need a recursion for finding the blocked stories (Which can be achieved easily by SELECT blocked_story_id FROM blocking) I would ask you for further information. A real recursion case could be: "All blocking that are reachable from story 4" or something like that.
Here's what I've done so far as I understood your problem:
Your blocking table says: story 4 blocks stories 2 and 3. Story 3 blocks stories 1 and 5. So there are blocked stories 1, 2, 3, 5. Because of the recursion, story 4 can block 1 and 5 via 3. So there a two ways of blocking them (directly with starting point 3 and and from starting point 4 via 3). I gave out all possible paths with this query:
WITH RECURSIVE blocks AS (
SELECT blocked_story_id, ARRAY[story_id]::int[] as path FROM blockings
UNION
SELECT bk.blocked_story_id, b.path || bk.story_id
FROM blockings bk INNER JOIN blocks b ON b.blocked_story_id = bk.story_id
)
SELECT b.blocked_story_id, s.title, b.path
FROM blocks b INNER JOIN stories s ON s.id = b.blocked_story_id;
Result:
blocked_story_id title path
2 Title 2 {4}
3 Title 3 {4}
1 Title 1 {3}
5 Title 5 {3}
1 Title 1 {4,3}
5 Title 5 {4,3}
demo: db<>fiddle
#S-Man I figured it out thanks to your help pointing me in the right direction.
WITH recursive blockings_tree(id, title, path) AS (
SELECT stories.id, title, ARRAY[blockings.blocked_story_id, blockings.story_id]
FROM stories
LEFT OUTER JOIN blockings ON blockings.story_id = stories.id
UNION ALL
SELECT stories.id, stories.title, path || stories.id
FROM blockings_tree
JOIN blockings ON blockings.story_id = blockings_tree.id
JOIN stories ON blockings.blocked_story_id = stories.id
WHERE NOT blockings.blocked_story_id = any(path)
)
SELECT stories.*
FROM stories
JOIN (SELECT id, MAX(path) AS path FROM blockings_tree GROUP BY id) bt ON bt.id = stories.id
ORDER BY path

Hibernate criteria left join with query

I have two classes Apartment and AdditionalSpace representing tables as below.
Apartment table
ID AREA SOLD
---- ------ ----
1 100 1
2 200 0
AdditionalSpace table
ID AREA APARTMENTID
---- ------ -----------
10 10 1
11 10 1
12 10 1
20 20 2
21 20 2
As you can see Apartment's table has a one-to-many relation with AdditionalSpace table, i.e. Apartment.ID=AdditionalSpace.APARTMENTID.
Question:- How to retrieve total area of a sold apartment including its additional space area.
The SQL which I have used so far to retrieve similar result is :-
select sum(apt.area + ads.adsarea) from apartment apt left outer join (select sum(area) as adsarea, apartmentid from additionalspace group by apartmentid) ads on ads.apartmentid=apt.id where apt.sold=1
I am struggling to find a way in order to implement the above scenario via criteria instead of SQL/HQL. Please suggest. Thanks.
I don't think this is possible in criteria. The closest I can see is to simply get the size of the apartment and the sum of the additional areas as two columns in your result, like this:
Criteria criteria = session.createCriteria(Apartment.class,"a");
criteria.createAlias("additionalSpaces", "ads");
criteria.setProjection(Projections.projectionList()
.add(Projections.property("area"))
.add(Projections.groupProperty("a.id"))
.add(Projections.sum("ads.area")));
Alternatively, if you still want to use Hibernate but are happy to write it in HQL, you can do the following:
select ads.apartment.id,max(a.area)+sum(ads.area)
from Apartment a
join a.additionalSpaces ads
group by ads.apartment.id
This works because HQL allows you to write the + to add together the two projections, but I don't know that an analogous method exists on the projections api.

SQL Two SELECT vs. JOIN best performance?

I wonder which has better performance in this case. First of all, I want to show to the user his medical information. I have two tables
user
-----
id_user | type_blood | number | ...
1 O 123
2 A+ 442
user_allergies
-----------
id_user | name
1 name1
1 name2
I want to return:
JSON {id_user=1, type_blood=0, allergies=(name1,name2)}
So, Its better do a JOIN for user and user_allergies and iterate, or maybe two SELECT?
But if then I have another table like user_allergies, that the result can be:
user_another_table
-----------
id_user | name
1 namet1
1 namet2
1 namet3
JSON {id_user=1, type_blood=0, allergies=(name1,name2), table=(namet1,namet2,namet3)}
It's better three SELECT or a JOIN, but then I have to iterate on the results and I can't imagine a esay way. A JOIN can give me a result like:
id_user | type_blood | allergy_name | another_table_name
1 O name1 namet1
1 O name1 namet2
1 O name1 namet3
1 O name2 namet1
1 O name2 namet2
1 O name2 namet3
Is there any way to extract:
id_user | type_blood | allergy_name | another_table_name
1 O name1 namet1
1 O name2 namet2
1 O namet3
Thanks community, I'm newbie in SQL
Depending on the data - there is no way to get the 2nd set of results you've shown, if the 1st set of results shows the values. The 2nd one is throwing data away - in this case allergy 'name2' for another_table_name 'namet3'. This is why you get many rows back with repeated data.
You can use the group by clause to restrict this in some cases, but again - it won't let you throw away data like that.
You could try using the COALESCE clause, if your DB supports it.
If not, I think you're going to have to construct your JSON in some business logic, in which case its fine to read the data in a 3-way join. You order by the user id and either create or append the row data to the JSON document depending if a user record is present or not (if you order by user id, you only need to keep track of when the user id value changes).
Alternatively, you can read a list of users and single-item data in one query, and then ht the DB again for the repeating data.

Selecting rows using multiple LIKE conditions from a table field

I created a table out of a CSV file which is produced by an external software.
Amongst the other fields, this table contains one field called "CustomID".
Each row on this table must be linked to a customer using the content of that field.
Every customer may have one or more set of customIDs at their own discretion, as long as each sequence starts with the same prefix.
So for example:
Customer 1 may use "cust1_n" and "cstm01_n" (where n is a number)
Customer 2 may use "customer2_n"
ImportedRows
PKID CustomID Description
---- --------------- --------------------------
1 cust1_001 Something
2 cust1_002 ...
3 cstm01_000001 ...
4 customer2_00001 ...
5 cstm01_000232 ...
..
Now I have created 2 support tables as follows:
Customers
PKID Name
---- --------------------
1 Customer 1
2 Customer 2
and
CustomIDs
PKID FKCustomerID SearchPattern
---- ------------ -------------
1 1 cust1_*
2 1 cstm01_*
3 2 customer2_*
What I need to achieve is the retrieval of all rows for a given customer using all the LIKE conditions found on the CustomIDs tables for that customer.
I have failed miserably so far.
Any clues, please?
Thanks in advance.
Silver.
To use LIKE you must replace the * with % in the pattern. Different dbms use different functions for string manipulation. Let's assume there is a REPLACE function available:
SELECT ir.*
FROM ImportedRows ir
JOIN CustomIDs c ON ir.CustomID LIKE REPLACE(c.SearchPattern, '*', '%')
WHERE c.FKCustomerID = 1;

Join in SQLite with per-row selection of join table based on value in link table

I have tables as follows:
muscles
id primary_synonym_id source
---------- ------------------ ----------
1 1 1
2 2 2
3 3 3
muscle_synonyms
id synonym
---------- ---------------
1 Gluteus maximus
2 Soleus
3 Infraspinatus
sources (As you can probably tell, sources is intended as a link table.)
id type sub_id
---------- ---------- ----------
1 url 1
2 url 2
3 book 1
source_urls
id url
---------- ------------------
1 http://v.gd/3NCOMC
2 http://v.gd/fWdonY
source_books
id isbn
---------- ----------
1 1405006692
From the above, which query would you recommend, to generate the following output?
id synonym ref
---------- --------------- ------------------
1 Gluteus maximus http://v.gd/3NCOMC
2 Soleus http://v.gd/fWdonY
3 Infraspinatus 1405006692
Please mention worthwhile alternatives - if any - to such a query, that you think would promote good database practice. (For example, a different way to structure the data and the use of a simpler query.)
I was unfamiliar with the coalesce() function. The following query was inspired by this, and it works:
select muscles.id, synonym, coalesce(url, isbn) as ref
from muscles
join muscle_synonyms on muscles.primary_synonym_id = muscle_synonyms.id
join sources on muscles.source = sources.id
left join source_urls on
sources.type = 'url' and
sources.sub_id = source_urls.id
left join source_books on
sources.type = 'book' and
sources.sub_id = source_books.id
where ref not null;
i dont exactly understand what is sub_id in sources and how its linked to source_books
sql will almost looks like this
select m.id,ms.synonym,su.url from muscles m
join muscle_synonym ms on ms.id=m.primary_synonym_id
join sources s on s.id=m.source
join source_url su on s.sub_id=su.id
I can't see any advantage in having 2 different tables for source_*.
I would merge source_url and source_book into source_ref so:
id type ref
---------- ---------- ------------------
1 url http://v.gd/3NCOMC
2 url http://v.gd/fWdonY
3 book 1405006692
And sources will become a two table join:
id ref_id
---------- ----------
1 1
2 2
3 3
So you query will become simple joins as:
SELECT muscles.id, muscle_synonyms.synonym, source_ref.ref FROM muscles
LEFT JOIN muscle_synonyms ON muscles.id=muscle_synonyms.id
LEFT JOIN source ON muscles.source=source.id
LEFT JOIN source_ref ON source.ref_id=source_ref.id;
This will include all muscles, including those without synonym neither sources, neither any ref for source.