MySQL Substring between two DIFFERENT strings where the second needle comes AFTER the first - mysql

I have to extract certain data from a MySQL column. The table looks like so:
+----+---------------------+------------------------+
| id | time | data |
+----+---------------------+------------------------+
| 1 | 2016-10-28 00:12:01 | a Q1!! AF3 !! ext!! z |
| 2 | 2016-10-28 02:19:02 | z !!3F2 !AF66-2!! !!a |
| 3 | 2016-10-28 11:35:03 | AF!a !!! pl6 f !!! dd |
+----+---------------------+------------------------+
I want to grab the string from column data between the characters AF and the NEXT occurrence of !! So ideally the query SELECTid,[something] AS x FROM tbl would result in:
+----+------+
| id | x |
+----+------+
| 1 | 3 |
| 2 | 66-2 |
| 3 | !a |
+----+------+
Thoughts on how to do this? All the other questions I see don't quite relate, as they don't deal with finding the first occurrence of the second needle (!!) AFTER the first needle (AF).

There may be faster ways to do this but this is a good start:
select substring_index(substring_index(data, 'AF', -1), '!!', 1)

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 merge two strings of comma-separated numbers in MySQL?

For example, there are three rooms.
1|gold_room|1,2,3
2|silver_room|1,2,3
3|brown_room|2,4,6
4|brown_room|3
5|gold_room|4,5,6
Then, I'd like to get
gold_room|1,2,3,4,5,6
brown_room|2,3,4,6
silver_room|1,2,3
How can I achieve this?
I've tried: select * from room group by name; And it only prints the first row. And I know CONCAT() can combine two string values.
Please use below query,
select col2, GROUP_CONCAT(col3) from data group by col2;
Below is the Test case,
https://dbfiddle.uk/?rdbms=mysql_8.0&fiddle=ab35e8d66ffe3ac6436c17faf97ee9af
I'm not making an assumption that the lists don't have elements in common on separate rows.
First create a table of integers.
mysql> create table n (n int primary key);
mysql> insert into n values (1),(2),(3),(4),(5),(6);
You can join this to your rooms table using the FIND_IN_SET() function. Note that this cannot be optimized. It will execute N full table scans. But it does create an interim set of rows.
mysql> select * from n inner join rooms on find_in_set(n.n, rooms.csv) order by rooms.room, n.n;
+---+----+-------------+-------+
| n | id | room | csv |
+---+----+-------------+-------+
| 2 | 3 | brown_room | 2,4,6 |
| 3 | 4 | brown_room | 3 |
| 4 | 3 | brown_room | 2,4,6 |
| 6 | 3 | brown_room | 2,4,6 |
| 1 | 1 | gold_room | 1,2,3 |
| 2 | 1 | gold_room | 1,2,3 |
| 3 | 1 | gold_room | 1,2,3 |
| 4 | 5 | gold_room | 4,5,6 |
| 5 | 5 | gold_room | 4,5,6 |
| 6 | 5 | gold_room | 4,5,6 |
| 1 | 2 | silver_room | 1,2,3 |
| 2 | 2 | silver_room | 1,2,3 |
| 3 | 2 | silver_room | 1,2,3 |
+---+----+-------------+-------+
Use GROUP BY to reduce these rows to one row per room. Use GROUP_CONCAT() to put the integers together into a comma-separated list.
mysql> select room, group_concat(distinct n.n order by n.n) as csv
from n inner join rooms on find_in_set(n.n, rooms.csv) group by rooms.room
+-------------+-------------+
| room | csv |
+-------------+-------------+
| brown_room | 2,3,4,6 |
| gold_room | 1,2,3,4,5,6 |
| silver_room | 1,2,3 |
+-------------+-------------+
I think this is a lot of work, and impossible to optimize. I don't recommend it.
The problem is that you are storing comma-separated lists of numbers, and then you want to query it as if the elements in the list are discrete values. This is a problem for SQL.
It would be much better if you did not store your numbers in a comma-separated list. Store multiple rows per room, with one number per row. You can run a wider variety of queries if you do this, and it will be more flexible.
For example, the query you asked about, to produce a result with numbers in a comma-separated list is more simple, and you don't need the extra n table:
select room, group_concat(n order by n) as csv from rooms group by room
See also my answer to Is storing a delimited list in a database column really that bad?

How can i select multiple rows having the sum of a column lower than a specific value?

I have a table called 'markers ' that have the structure like this:
+------+---------------+-----------+-------------+
| id | about | time | type |
|------|---------------|-----------|-------------|
| 1 | text | 2 | restaurant |
|------|---------------|-----------|-------------|
| 2 | text | 1 | restaurant |
|------|---------------|-----------|-------------|
| 3 | text | 2 | museum |
|------|---------------|-----------|-------------|
| 4 | text | 2 | park |
+------+---------------+-----------+-------------+
From this table i want to select a combination of rows so that the sum of time is lower or equal to a specific value. For example i want to select row number 1,3 and 4 because the total time is lower or equal with 6.
I think the query should sound like this: SELECT 'all rows' WHERE SUM(time) < 6, but it doesn't work that simple.
Another question, that maybe comes, is if i choose the sum to be equal with 3, in this case i have several combinations ( row 1 with row 2, row 2 with row 3..and so one ). What can i do to choose one value of each type ?
I hope i made myself understood. Thanks in advance !
SELECT id, time, type
FROM `markers`
WHERE type IN (
SELECT type FROM markers
WHERE (type = 'restaurant' OR type = 'museum' OR type = 'park')
GROUP BY type HAVING SUM(time_to_spend)< 20
)
For 3 i would expect at something like this:
+------+---------------+-----------+-------------+
| id | about | time | type |
|------|---------------|-----------|-------------|
| 2 | text | 1 | restaurant |
|------|---------------|-----------|-------------|
| 3 | text | 2 | museum |
|------|---------------|-----------|-------------|
or any other combination.

How do I select records in MySQL with multiple columns matching map of values?

I have the following 3-column table:
+----+---------+------------+
| ID | First | Last |
+----+---------+------------+
| 1 | Maurice | Richard |
| 2 | Yvan | Cournoyer |
| 3 | Carey | Price |
| 4 | Guy | Lafleur |
| 5 | Steve | Shutt |
+----+---------+------------+
If I want to look for everyone in (Maurice,Guy) I can do select * from table where first in (Maurice,Guy).
If I want to find just Maurice Richard, I can do select * from table where first = "Maurice" and last = "Richard".
How do I do a map, an array of multiples?
[
[Maurice, Richard]
[Guy,Lafleur]
[Yvan,Cournoyer]
]
If I have an arbitrary number of entries, I cannot construct a long complex where (first = "Maurice" and last = "Richard") or (first = "Guy" and last = "Lafleur") or .....
How do I do the moral equivalent of where (first, last) in ((Guy,Lafleur),(Maurice,Richard)) ?
You can do it just like you describe it:
SELECT *
FROM mytable
WHERE (first, last) IN (('Guy','Lafleur'),('Maurice','Richard'))
Demo here

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?)