JSON extract multiple columns PostgreSQL - json

I had a question earlier: PostgreSQL trim text field with regex (or else) And I got a wonderful answer by a_horse_with_no_name. Now I have an additional question regarding this issue.
So here it is this rextester https://rextester.com/SUWG96428 and the goal is to have all the ids in a separate column. Is it possible at all?
Like this:
+---+----+-------+-------+
| | id | ids_1 | ids_2 |
+---+----+-------+-------+
| 1 | 1 | 4202 | 4203 |
| 2 | 2 | 4204 | |
| 3 | 3 | 4201 | |
+---+----+-------+-------+

Yep, you can modify your query like:
select
t.id,
right(((the_column::json->'itemID')->>0)::varchar, 4) as col1,
right(((the_column::json->'itemID')->>1)::varchar, 4) as col2,
right(((the_column::json->'itemID')->>2)::varchar, 4) as col3
from the_table t;
DB Fiddle

Related

Transitive, Directed Graph in SQL

I am trying to make a graph transitive using SQL.
I do not see, why this should not work:
with recursive recursive_table(from, to) as (
SELECT * FROM Graph
UNION ALL
SELECT r1.to, r2.from FROM recursive_table r1, recursive_table r2
WHERE r1.from = r2.to
UNION ALL
SELECT * FROM recursive_table
)
SELECT * FROM recursive_table;
In every recursion, I take the elements specified in the not transitive Graph (1), everything which is the result of the next recursion (3) and everything which results out of the next recursion (2).
However, SQL says:
[2021-02-12 10:36:05] [HY000][3577] In recursive query block of Recursive Common Table Expression 'recursive_table', the recursive table must be referenced only once, and not in any subquery
A sample output would be the following:
Input:
+------+------+--+
| Col1 | Col2 | |
+------+------+--+
| 1 | 2 | |
| 2 | 3 | |
| 1 | 4 | |
| 4 | 5 | |
+------+------+--+
Output:
+------+------+--+
| Col1 | Col2 | |
+------+------+--+
| 1 | 2 | |
| 2 | 3 | |
| 1 | 4 | |
| 4 | 5 | |
| 1 | 3 | |
| 1 | 5 | |
+------+------+--+
So, mathematically speaking,
If you can go from a to b in a finite amount of steps > 0, add (a,b) to the graph.
For example, you can go from 1 to 2 and from 2 to 3 on the input data, therefore you can go from 1 to 3.
Another example is a circle with n - knots.
This means, the input would be something like this...
+------+------+--+
| Col1 | Col2 | |
+------+------+--+
| 1 | 2 | |
| 2 | 3 | |
| 3 | ... | |
| ... | n | |
| n | 1 | |
+------+------+--+
The correct output would be [n] X [n]
It is a little hard to say exactly why your code doesn't work. There are multiple potential issues:
from is not a valid column name.
Recursive CTEs rarely have two union alls.
Recursive CTEs do not usually reference the recursive CTE multiple times.
In any case, correct code is simpler:
with recursive recursive_table(col1, col2) as (
SELECT col1, col2
FROM graph
UNION ALL
SELECT r1.col1, g.col2
FROM recursive_table r1 JOIN
graph g
ON r1.col2 = g.col1
)
SELECT *
FROM recursive_table;
Here is a db<>fiddle.
Note that both this code and your code assume that the graph has no cycles. That is not part of your question, but if it is an issue, ask a new question.

How can I select tags for autocomplete box?

Here is my table structure:
// tags
+----+------------+----------------------------------+----------+----------+------------+
| id | name | description | related | used_num | date_time |
+----+------------+----------------------------------+----------+----------+------------+
| 1 | PHP | some explanations for PHP | 1 | 4234 | 1475028896 |
| 2 | SQL | some explanations for SQL | 2 | 734 | 1475048601 |
| 3 | jQuery | some explanations for jQuery | 3 | 434 | 1475068321 |
| 4 | MySQL | some explanations for MySQL | 2 | 657 | 1475068332 |
| 5 | JavaScript | some explanations for JavaScript | 3 | 3325 | 1475071430 |
| 6 | HTML | some explanations for HTML | 6 | 2133 | 1475077842 |
| 7 | postgresql | some explanations for postgresql | 2 | 43 | 1475077851 |
| 8 | script | some explanations for script | 8 | 3 | 1475077935 |
+----+------------+----------------------------------+----------+----------+------------+
Now I need to select tags base on a part of their names, Also I need to select all related tags.
For examples:
String: scr. expected output:
+----+------------+----------------------------------+----------+----------+------------+
| 3 | jQuery | some explanations for jQuery | 3 | 434 | 1475068321 |
| 5 | JavaScript | some explanations for JavaScript | 3 | 3325 | 1475071430 |
| 8 | script | some explanations for script | 8 | 3 | 1475077935 |
+----+------------+----------------------------------+----------+----------+------------+
-- Noted that "jQuery" tag is selected because of its relation with "JavaScript" tag
String: ph. expected output:
+----+------------+----------------------------------+----------+----------+------------+
| 1 | PHP | some explanations for PHP | 1 | 4234 | 1475028896 |
+----+------------+----------------------------------+----------+----------+------------+
String: ys. expected output:
+----+------------+----------------------------------+----------+----------+------------+
| 2 | SQL | some explanations for SQL | 2 | 734 | 1475048601 |
| 4 | MySQL | some explanations for MySQL | 2 | 657 | 1475068332 |
| 7 | postgresql | some explanations for postgresql | 2 | 43 | 1475077851 |
+----+------------+----------------------------------+----------+----------+------------+
-- Noted that both "SQL" and "postgresql" are selected because of their relation with "MySQL" tag
How can I do that?
Actually I can do that like this:
SELECT * FROM tags WHERE name LIKE %:str%
But my query doesn't support related column.
Join the table on itself and look for rows with the occurrence in either of the two name columns. This way, you can search the name of the related elements as well.
SELECT t1.*
FROM tags t1
LEFT JOIN tags t2 ON t1.id = t2.related
WHERE t1.name LIKE %:str% OR t2.name LIKE %:str%;
Try to use
SELECT * FROM tags a WHERE name LIKE %:str% or exist (SELECT id FROM tags b WHERE b.name LIKE %:str% and b.id=a.related)
One option here is to self join the tags table and then group concatenate the related tags based on an input tag. The query below will output a CSV list of tags related to a given input. For example, if :str were 'MySQL' then the output would be 'MySQL,postgresql,SQL'. If you are using a language like PHP or Java it should be easy enough to explode that CSV list and get those suggestions into the drop down for autocompletion.
SELECT t1.name,
GROUP_CONCAT(t2.name)
FROM tags t1
INNER JOIN tags t2
ON t1.id = t2.related
WHERE t1.name LIKE '%:str%' -- e.g. MySQL
GROUP BY t1.name
You could also do a self join and return one record for each suggested tag:
SELECT t2.name
FROM tags t1
INNER JOIN tags t2
ON t1.id = t2.related
WHERE t1.name LIKE '%:str%' -- e.g. MySQL
Since autocomplete must be fast, you cannot afford to be looking in more than one place for the answer.
Build (and maintain) a special table for autocomplete; it will have one text (or varchar) column with all possible keywords for a given item, plus another column(s) with the info about what it refers to.

SQL select statement optimizing (id, parent_id, child_ids)

we have a very old custom db (oracle, mysql, derby) with the restrictions: no new table fileds, no views, no functions, no procedures.
My table MYTABLE:
| id | ... | parent_id |
------------------------
| 1 | ... | |
| 2 | ... | 1 |
| 3 | ... | 1 |
| 4 | ... | 2 |
| 5 | ... | 1 |
and I my first statement:
select * from MYTABLE where id in ('1','2','3','4','5');
give my 5 records.
Then I need the information about the first (no deeper) child ids.
My current solution:
for (record in records) {
// get child ids as comma separated string list
// e.g. "2,3,5" for id 1
String childIds = getChildIds(record.id);
}
with the second statement in getChildIds(record.Id):
select id from MYTABLE where parent_id='record.Id';
So I have 1 + 5 = 6 statements for the required information.
I'm looking for a solution to select the records from the following "imaginary" table with the "imaginary" field "child_ids":
| id | ... | parent_id | child_ids |
------------------------------------
| 1 | ... | | 2,3,5 |
| 2 | ... | 1 | 4 |
| 3 | ... | 1 | |
| 4 | ... | 2 | |
| 5 | ... | 1 | |
Does anyone have an idea how I can get this information with only one statement (or with 2 statements)?
Thanks for your help, Thomas
FOR MYSQL:
How about using the GROUP_CONCAT() function like the following:
SELECT id, parent_id,
GROUP_CONCAT(child_id ORDER BY child_id SEPARATOR ',') AS child_ids
FROM MYTABLE
WHERE id IN ('1','2','3','4','5')
FOR ORACLE:
If you have a later version of Oracle you could use the LISTAGG() function:
SELECT parent_id,
LISTAGG(child_id, ', ') WITHIN GROUP (ORDER BY child_id) "child_ids"
FROM MYTABLE
WHERE id IN ('1','2','3','4','5')
GROUP BY parent_id
FOR DERBY:
I don't know anything about derby, but doing a little research it uses IBM DB2 SQL syntax. So, maybe using a combination of XMLSERIALIZE(), XMLAGG(), and XMLTEXT() will work for you:
SELECT parent_id,
XMLSERIALIZE(XMLAGG(XMLTEXT(child_id) ORDER BY child_id) AS CLOB(30K))
FROM table GROUP BY parent_id

Optimize SQL-Query that is using REGEXP in a JOIN

I have the following situation:
Table Words:
| ID | WORD |
|----|--------|
| 1 | us |
| 2 | to |
| 3 | belong |
| 4 | are |
| 5 | base |
| 6 | your |
| 7 | all |
| 8 | is |
| 9 | yours |
Table Sentence:
| ID | SENTENCE |
|----|-------------------------------------------|
| 1 | <<7>> <<6>> <<5>> <<4>> <<3>> <<2>> <<1>> |
| 2 | <<7>> <<8>> <<9>> |
And i want to replace the <<(\d)>> with the equivalent word from the Word-Table.
So the result should be
| ID | SENTENCE |
|----|--------------------------------|
| 1 | all your base are belong to us |
| 2 | all is yours |
What i came up with is the following SQL-Code:
SELECT id, GROUP_CONCAT(word ORDER BY pos SEPARATOR ' ') AS sentence FROM (
SELECT sentence.id, words.word, LOCATE(words.id, sentence.sentence) AS pos
FROM sentence
LEFT JOIN words
ON (sentence.sentence REGEXP CONCAT('<<',words.id,'>>'))
) AS TEMP
GROUP BY id
I made a sqlfiddle for this:
http://sqlfiddle.com/#!2/634b8/4
The code basically is working, but i'd like to ask you pros if there is a way without a derived table or without filesort in the execution plan.
You should make a table with one entry per word, so your sentense (sic) can be made by joining on that table. It would look something like this
SentenceId, wordId, location
2, 7, 1
2, 8, 2
2, 9, 3
They way you have it set up, you are not taking advantage of your database, basically putting several points of data in 1 table-field.
The location field (it is tempting to call it "order", but as this is an SQL keyword, don't do it, you'll hate yourself) can be used to 'sort' the sentence.
(and you might want to rename sentense to sentence?)

MySQL: Sort by group and field

I have a table with the following (simplified) structure:
INT id,
INT type,
INT sort
What I need is a SELECT that sorts my data in a way, so that:
all rows of the same type are in sequency, sorted ascendingly by sort internally, and
all "blocks" of one type are sorted by their minimum sort.
Example:
If the table looks like this:
| id | type | sort |
| 1 | 1 | 3 |
| 2 | 3 | 5 |
| 3 | 3 | 1 |
| 4 | 2 | 4 |
| 5 | 1 | 2 |
| 6 | 2 | 6 |
The query should sort the result like this:
| id | type | sort |
| 3 | 3 | 1 |
| 2 | 3 | 5 |
| 5 | 1 | 2 |
| 1 | 1 | 3 |
| 4 | 2 | 4 |
| 6 | 2 | 6 |
I hope this makes it clear enough.
Looks to me, as this should be a very common requirement, but I didn't find any examples close enough to be able to transfer it to my use case on my own. I suppose I can't avoid at least one subquery, but I didn't figure it out on my own.
Any help is appreciated, thanks in advance.
By the way: I'm going to use this query with CakePHP 2.1, so if you know of a comfortable way to do it with Cake, please let me know.
This is simpler than it initially sounds. I believe the following should do the trick:
SELECT a.id, a.type, a.sort
FROM Some_Table as a
JOIN (SELECT type, MIN(sort) as min
FROM Some_Table
GROUP BY type) as b
ON b.type = a.type
ORDER BY b.min, a.type, a.sort
For best (fastest) results, you're probably going to want an index on (type, sort).
You want an additional sort by a.type (instead of (b.min, a.sort)), in case there are two groups with the same sort value (would result in mixed rows). If there are no duplicate values, you can remove it.
sort and type are reserved words on some databases and can cause you problems.
Have you tried?
ORDER BY TYPE DESC, SORT ASC