From CTE to mySQL stored procedure code - mysql

This is a query using sql CTE's to create a transitive closure of a table containing relations
between users.(edges of a graph).
How can this be done in mySQL?
WITH RECURSIVE transitive_closure(a, b, distance, path_string) AS
( SELECT a, b, 1 AS distance,
a || '.' || b || '.' AS path_string
FROM edges
WHERE a = 1 -- source
UNION ALL
SELECT tc.a, e.b, tc.distance + 1,
tc.path_string || e.b || '.' AS path_string
FROM edges AS e
JOIN transitive_closure AS tc ON e.a = tc.b
WHERE tc.path_string NOT LIKE '%' || e.b || '.%'
)
SELECT * FROM transitive_closure
ORDER BY a, b, distance;
or at least find all simple paths between two nodes without creating a transitive closure for all nodes.
code taken from: http://techportal.inviqa.com/2009/09/07/graphs-in-the-database-sql-meets-social-networks/

Related

MySQL 8 update with Replace() or Regex

How can i update data using replace or regex-like method from
id | jdata
---------------
01 | {"name1":["number","2"]}
02 | {"val1":["number","12"],"val2":["number","22"]}
to
id | jdata
---------------
01 | {"name1":2 }
02 | {"val1": 12,"val2":22 }
I need to make a proper json entry for numbers and replace an array with a number from that array. Column "jdata" can have any number of similar attributes from the example. Something similar to this would do:
UPDATE table SET jdata = REPLACE(jdata, '["number","%d"]', %d);
Two ways:
The long, more clumsy way, using JSON_ARRAY:
UPDATE table1,
(
SELECT
id,
JSON_EXTRACT(jdata, "$.name1[0]") as A,
JSON_EXTRACT(jdata, "$.name1[1]") as B,
JSON_EXTRACT(jdata, "$.val1[0]") as C,
JSON_EXTRACT(jdata, "$.val1[1]") as D,
JSON_EXTRACT(jdata, "$.val2[0]") as E,
JSON_EXTRACT(jdata, "$.val2[1]") as F
FROM table1
) x
SET jdata = CASE WHEN table1.id=1 THEN JSON_ARRAY("name1",x.B)
ELSE JSON_ARRAY("val1",x.D,"val2",F) END
WHERE x.id=table1.id;
Or using JSON_REPLACE:
update table1
set jdata = JSON_REPLACE(jdata, "$.name1",JSON_EXTRACT(jdata,"$.name1[1]"))
where id=1;
update table1
set jdata = JSON_REPLACE(jdata, "$.val1",JSON_EXTRACT(jdata,"$.val1[1]"),
"$.val2",JSON_EXTRACT(jdata,"$.val2[1]"))
where id=2;
see: DBFIDDLE for both options
EDIT: To get more depth in the query, you can start with below, and create a new JSON message from this stuff without the number:
WITH RECURSIVE cte1 as (
select 0 as x
union all
select x+1 from cte1 where x<10
)
select
id,
x,
JSON_UNQUOTE(JSON_EXTRACT(JSON_KEYS(jdata),CONCAT("$[",x,"]"))) j,
JSON_EXTRACT(jdata,CONCAT("$.",JSON_UNQUOTE(JSON_EXTRACT(JSON_KEYS(jdata),CONCAT("$[",x,"]"))))) v,
JSON_UNQUOTE(JSON_EXTRACT(jdata,CONCAT("$.",JSON_UNQUOTE(JSON_EXTRACT(JSON_KEYS(jdata),CONCAT("$[",x,"]"))),"[0]"))) v1,
JSON_UNQUOTE(JSON_EXTRACT(jdata,CONCAT("$.",JSON_UNQUOTE(JSON_EXTRACT(JSON_KEYS(jdata),CONCAT("$[",x,"]"))),"[1]"))) v2
from table1
cross join cte1
where x<JSON_DEPTH(jdata)
and not JSON_EXTRACT(JSON_KEYS(jdata),CONCAT("$[",x,"]")) is null
order by id,x;
output:
id
x
j
v
v1
v2
1
0
name1
["number", "2"]
number
2
2
0
val1
["number", "12"]
number
12
2
1
val2
["number", "22"]
number
22
This should take care of JSON message which also contains values like val3, val4, etc, until a maximum depth which is now fixed to 10 in cte1.
EDIT2: When it is just needed to remove the "number" from the JSON message, you can also repeat this UPDATE until all "number" tags are removed (you can repeat this in a stored procedure, I am not going to write the stored procedure for you 😉)
update
table1,
( WITH RECURSIVE cte1 as (
select 0 as x
union all
select x+1 from cte1 where x<10
) select * from cte1 )x
set jdata = JSON_REMOVE(table1.jdata, CONCAT("$.",JSON_UNQUOTE(JSON_EXTRACT(JSON_KEYS(jdata),CONCAT("$[",x,"]"))),"[0]"))
where JSON_UNQUOTE(JSON_EXTRACT(jdata,CONCAT("$.",JSON_UNQUOTE(JSON_EXTRACT(JSON_KEYS(jdata),CONCAT("$[",x,"]"))),"[0]"))) = "number"
An example, where I do run the update 2 times, is in this DBFIDDLE

How to use NOT IN in binary tree problem using MySQL

I am trying to solve this problem,
You are given a table, BST, containing two columns: N and P, where N
represents the value of a node in Binary Tree, and P is the parent of
N.
Write a query to find the node type of Binary Tree ordered by the
value of the node. Output one of the following for each node:
Root: If node is root node.
Leaf: If node is leaf node.
Inner: If node is neither root nor leaf node.
Input:
Desired Output:
1 Leaf
2 Inner
3 Leaf
5 Root
6 Leaf
8 Inner
9 Leaf
This is my query, can anyone tell me why it's not working?
select case
when P is NULL then CONCAT_WS(" ", N, 'Root')
when N not in (SELECT DISTINCT P FROM BST) then CONCAT_WS(" ", N, 'Leaf')
else CONCAT_WS(" ", N, 'Inner')
end
from BST ORDER BY N ASC;
You have NULL inside the P column in which case an expression such as:
1 NOT IN (NULL, 2, 8, 5)
will return unknown instead of the "expected" result true (ref).
The solution is to make a subtle change like so:
N NOT IN (SELECT P FROM BST WHERE P IS NOT NULL)
Or use an EXISTS query:
NOT EXISTS (SELECT * FROM BST AS bst2 WHERE bst2.P = bst.N)
SELECT n,
ELT((1 + (2 * (t1.p IS NULL)) + (EXISTS (SELECT NULL FROM BST t2 WHERE t1.n=t2.p))), 'Leaf', 'Inner', 'Single', 'Root') type
FROM BST t1
ORDER BY n;
https://dbfiddle.uk/?rdbms=mysql_8.0&fiddle=287b1de2b2bb532d73619c19bcf8a86b
I add one more option 'Single' - the case when the node is root and leaf at the same time (node 4 in my fiddle).

How to display zero values in mysql query?

This code make an operation on a and b and check if result is equal to c.
If yes, just print this row. My question is how to change this code to display
rows when result (a / b) = 0.
SELECT id, a, b, operation, c
FROM expressions
where
case
when operation LIKE '+' AND (a + b = c) then c
when operation LIKE '-' AND (a - b = c) then c
when operation like '/' AND (a / b = c) then c
when operation like '*' AND (a * b = c) then c
ELSE FALSE END;
Output:
id a b operation c
1 2 3 + 5
4 4 7 * 28
11 0 1 / 0
14 239 0 * 0
15 18 18 - 0
1, 2 rows are ok and printed. 3, 4, 5 rows should be printed but they are not!
When a / b = 0 then second condition in sql query is false - row is not printed, e.g. 0 / 1 = 0. It is 0 and should be printed. In contrart to 1 / 0 which shouldn`t be printed.
My solution is to cast (a / b = c) to unsigned but it is not working?
You shouldn't mix types like boolean and int because implicit conversion occurs.
Use explicit value instead of TRUE/FALSE (0 is treated as FALSE).
SELECT id, a, b, operation, c
FROM expressions
where
case
when operation LIKE '+' AND (a + b = c) then 1
when operation LIKE '-' AND (a - b = c) then 1
when operation like '/' AND (a / b = c) then 1
when operation like '*' AND (a * b = c) then 1
ELSE 0 END = 1;
alternatively:
SELECT id, a, b, operation, c
FROM expressions
where
case
when operation LIKE '+' AND (a + b = c) then TRUE
when operation LIKE '-' AND (a - b = c) then TRUE
when operation like '/' AND (a / b = c) then TRUE
when operation like '*' AND (a * b = c) then TRUE
ELSE FALSE END;
As already pointed out, the issue is that 0 is treated as false.
I would simplify the logic to:
SELECT id, a, b, operation, c
FROM expressions
WHERE (operation LIKE '+' AND (a + b = c) ) OR
(operation LIKE '-' AND (a - b = c) ) OR
(operation LIKE '/' AND (a / b = c) ) OR
(operation LIKE '*' AND (a * b = c) );
I don't think the CASE makes the code more understandable.
If you are concerned about divide-by-zero, then use nullif():
(operation LIKE '/' AND (a / nullif(b, 0) = c) ) OR

I have 3 columns x,y and type. I'd like to find out if there's at least 3 connections of the same type on a grid using SQL

I have a game table like:
CREATE TABLE game_piece(
x Integer,
y Integer,
type Integer
);
Each (x,y) can only have 1 piece. Representing a grid (numbers being types):
1235
1134
9678
By connected I mean they have to be directly next to the origin in a vertical or horizontal fashion like:
C C=connected
COC O=origin
C
I'd like to check if there's 3 pieces connected anywhere on the grid without needing to get the whole grid of the database and doing it in python, if there's decent solution. Suggestions?
To clarify my comments on Xophmeister's answer, like this:
SELECT o.x, o.y
FROM game_piece o
JOIN game_piece p
ON p.type = o.type
AND (
(o.x = p.x AND p.y IN (o.y-1,o.y+1))
OR
(o.y = p.y AND p.x IN (o.x-1,o.x+1))
)
GROUP BY o.x, o.y
HAVING COUNT(*) > 1
And here it is working on your test data: http://sqlfiddle.com/#!3/0bd34/1
Edit: Since you only want to know if the condition exists, the best way to do it is to just shove LIMIT 1 on the end and see whether the query returns a result or not. For some reason sqlfiddle doesn't like me putting the LIMIT in there, but I tested it on my server and it works just fine.
By 'connected', I'm going to assume you mean adjacent: That is, (5,3,1234) and (4,3,1234) would be connected.
As such, what you can do is join the table to itself twice, where each join depends on the one that preceded, and the conditions include:
on nextPiece.type = lastPiece.type
and (nextPiece.x in (lastPiece.x - 1, lastPiece.x + 1)
or nextPiece.y in (lastPiece.x - 1, lastPiece.x + 1))
Note that this doesn't consider diagonals as being adjacent.
The problem with this technique is that it will return duplicates: If record A is connected to record B, then both A and B will show in the result set. As you're joining twice, you'll see three duplicates... You can do a select distinct if all you are interested in is whether you've found a match, but the query in general will not be particularly fast either way (depending on how big your grid is and how sparsely it is populated).
EDIT See Braiba's solution (and comments, below): I made a mistake :P
Depending on what do you mean by connected, you don't need to dump the whole db but only the 2 pieces on 4 directions.
select x, y,
from game_piece
where (
(x between origin_x - 2 AND origin_x + 2 AND y = origin_y)
OR (y between origin_y - 2 AND origin_y + 2 AND x = origin_x)
)
AND type = the_type;
origin_x, origin_y are the coordinates of the piece you want to check.
That will dump between 1 and 8 pieces you'll have to check.
If the game table is very large, you should add an index on the x and y column, otherwise that might not be useful.
Hope it helps.
M.
This will return the number of different connection types:
select count(distinct type) as connections
from game_piece
where ((y = $y and x between $x - 1 and $x + 1)
or (x = $x and y between $y - 1 and $y + 1))
and (x != $x or y != $y) -- exclude the origin itself
You can use this solution:
SELECT 1
FROM game_piece
WHERE
(x = $o_x AND y IN ($o_y + 1, $o_y - 1)) OR
(y = $o_y AND x IN ($o_x + 1, $o_x - 1))
GROUP BY type
HAVING COUNT(1) = 3
$o_x and $o_y being the originX and originY input parameters respectively.
If there are exactly 3 pieces of the same type connected to the origin (either vertically or horizontally), this will return 1, otherwise, it will return an empty result-set.
Edit:
What you can try in order to find out if there's any pieces on the grid having 2 or more of the same adjacent types:
SELECT COUNT(1) > 0 AS doesExist
FROM
(
SELECT 1
FROM game_piece p
INNER JOIN game_piece o ON
p.type = o.type AND (
(p.x = o.x AND p.y IN (o.y + 1, o.y - 1)) OR
(p.y = o.y AND p.x IN (o.x + 1, o.x - 1))
)
GROUP BY p.type, o.x, o.y
HAVING COUNT(1) > 1
) a
Which will return 1 if there are one or more pieces and 0 if not.

Concat different tables?

I need to concatenate from two different tables.
Compare s.panelid (result like "AA") to b.modulecodes and return number_of_strings. Then put s.panelid (result like "AA") and number_of_string together.
select concat(Mid(s.panelid, 5, 2), ' - ' , '??') as `Module Type-Strings`
from r2rtool.stringtopanel s, be.modulecodes b
where s.insertts > '2011-07-15' and s.insertts < '2011-07-26' and Mid(s.panelid, 5, 2) != 99
group by date(insertts), `Module Type-Strings`
order by `Module Type-Strings`;
Be (Table): modulecodes, number_of_strings
AA - 12
AB - 4
AD - 3
AE - 12
When I run the above query it returns things like: Module Type-Strings = 'AA-??' and "AB-??" of course.
I am looking for: Module Type-Strings = 'AA-12'
Just in case you haven't tried it already...
Have you tried this?
select concat(Mid(s.panelid, 5, 2), ' - ' , b.number_of_string) as `Module Type-Strings`
from r2rtool.stringtopanel s, be.modulecodes b
where s.insertts > '2011-07-15' and s.insertts < '2011-07-26' and Mid(s.panelid, 5, 2) != 99
group by date(insertts), `Module Type-Strings`
order by `Module Type-Strings`;
There I'm basically replacing the '??' with the column you are asking about, number_of_string in the be.modulecodes table (aliased as b in the from clause).