How to use NOT IN in binary tree problem using MySQL - 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).

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 truncate double precision value in PostgreSQL by keeping exactly first two decimals?

I'm trying to truncate double precision value when I'm build json using json_build_object() function in PostgreSQL 11.8 but with no luck. To be more precise I'm trying to truncate 19.9899999999999984 number to ONLY two decimals but making sure it DOES NOT round it to 20.00 (which is what it does), but to keep it at 19.98.
BTW, what I've tried so far was to use:
1) TRUNC(found_book.price::numeric, 2) and I get value 20.00
2) ROUND(found_book.price::numeric, 2) and I get value 19.99 -> so far this is closesest value but not what I need
3) ROUND(found_book.price::double precision, 2) and I get
[42883] ERROR: function round(double precision, integer) does not exist
Also here is whole code I'm using:
create or replace function public.get_book_by_book_id8(b_id bigint) returns json as
$BODY$
declare
found_book book;
book_authors json;
book_categories json;
book_price double precision;
begin
-- Load book data:
select * into found_book
from book b2
where b2.book_id = b_id;
-- Get assigned authors
select case when count(x) = 0 then '[]' else json_agg(x) end into book_authors
from (select aut.*
from book b
inner join author_book as ab on b.book_id = ab.book_id
inner join author as aut on ab.author_id = aut.author_id
where b.book_id = b_id) x;
-- Get assigned categories
select case when count(y) = 0 then '[]' else json_agg(y) end into book_categories
from (select cat.*
from book b
inner join category_book as cb on b.book_id = cb.book_id
inner join category as cat on cb.category_id = cat.category_id
where b.book_id = b_id) y;
book_price = trunc(found_book.price, 2);
-- Build the JSON response:
return (select json_build_object(
'book_id', found_book.book_id,
'title', found_book.title,
'price', book_price,
'amount', found_book.amount,
'is_deleted', found_book.is_deleted,
'authors', book_authors,
'categories', book_categories
));
end
$BODY$
language 'plpgsql';
select get_book_by_book_id8(186);
How do I achieve to keep EXACTLY ONLY two FIRST decimal digits 19.98 (any suggestion/help is greatly appreciated)?
P.S. PostgreSQL version is 11.8
In PostgreSQL 11.8 or 12.3 I cannot reproduce:
# select trunc('19.9899999999999984'::numeric, 2);
trunc
-------
19.98
(1 row)
# select trunc(19.9899999999999984::numeric, 2);
trunc
-------
19.98
(1 row)
# select trunc(19.9899999999999984, 2);
trunc
-------
19.98
(1 row)
Actually I can reproduce with the right type and a special setting:
# set extra_float_digits=0;
SET
# select trunc(19.9899999999999984::double precision::text::numeric, 2);
trunc
-------
19.99
(1 row)
And a possible solution:
# show extra_float_digits;
extra_float_digits
--------------------
3
(1 row)
select trunc(19.9899999999999984::double precision::text::numeric, 2);
trunc
-------
19.98
(1 row)
But note that:
Note: The extra_float_digits setting controls the number of extra
significant digits included when a floating point value is converted
to text for output. With the default value of 0, the output is the
same on every platform supported by PostgreSQL. Increasing it will
produce output that more accurately represents the stored value, but
may be unportable.
As #pifor suggested I've managed to get it done by directly passing trunc(found_book.price::double precision::text::numeric, 2) as value in json_build_object like this:
json_build_object(
'book_id', found_book.book_id,
'title', found_book.title,
'price', trunc(found_book.price::double precision::text::numeric, 2),
'amount', found_book.amount,
'is_deleted', found_book.is_deleted,
'authors', book_authors,
'categories', book_categories
)
Using book_price = trunc(found_book.price::double precision::text::numeric, 2); and passing it as value for 'price' key didn't work.
Thank you for your help. :)

How to get number of steps to get a sub of tree

How can I do to get number of steps to get a sub of tree .
for example I have a table like this:
id title parent_id
1 A 0
2 B 0
3 C 1
4 F 3
5 O 3
6 D 2
7 J 6
8 T 2
9 P 8
A // 1 step
C //2 step
F //3 step
O //3 step
B //1 step
D //2 step
J //3 step
T //2 step
P //3 step
for example if I give a number like 1 (id = 1 ), it should return 1 and id=6 it should return 2 as step.
my DBMS is MySQL.
It can be a recursive stored procedure, if your tree is not very deep. Something like this (with addition of proper condition handlers), or anything of the same kind, it can be written in various ways:
DELIMITER $
CREATE PROCEDURE depth(IN n INT, OUT depth INT)
BEGIN
DECLARE parent INT DEFAULT 0;
SET max_sp_recursion_depth=255;
SELECT parent_id INTO parent FROM t WHERE id=n;
IF parent = 0 THEN SET depth = 1;
ELSE CALL depth(parent,depth); SET depth = depth + 1;
END IF;
END $
DELIMITER ;
CALL(6,#depth);
SELECT #depth;
Also, MariaDB 10.2 supports recursive CTE. It's an early beta now, so it's not good for production, but if you're only evaluating your options, you can try it out. This should work:
WITH RECURSIVE tree(id,parent_id,depth) AS
(
SELECT id, parent_id, 1 from t WHERE parent_id=0
UNION ALL
SELECT t.id, t.parent_id, depth+1 FROM t JOIN tree ON tree.id = t.parent_id
) SELECT * FROM tree WHERE id = 6;

SQL last in Recursion(variable)

My recursion give me complete structure
For example:
(This example is only for KOMPARTNR=49807, but I have more KOMPARTNR where are more than 500 results.)
How real items structure looks:
49807(level 1) -> 50208(level 2) -> 520008(level 3)
49807(level 1) -> 52344(level 2)
49807(level 1) -> 20308(level 2) -> 51005(level 3)
Output looks:
KOMPARTNR ARTIKEL_NR level
49807 50208 1
49807 52344 1
49807 20308 1
50208 520008 2
520008 530000 3
52344 54500 2
20308 51005 2
51005 53250 3
I want the last records from the Structure. From the example above, only 530000, 54500 and 53250 will stand out. But it must be variable. Another KOMPARTNR have another result, that's why I wrote too "How real structure looks".
What I only want from this example:
KOMPARTNR ARTIKEL_NR level
520008 530000 3
52344 54500 2
51005 53250 3
Here is my recursion:
WITH n(KOMPARTNR, ARTIKEL_NR, level) AS
(SELECT SMSTLPOS.KOMPARTNR, SMSTLPOS.ARTNR, 1 AS level
FROM SMSTLPOS
WHERE
SMSTLPOS.KOMPARTNR='49807'
UNION ALL
SELECT SMSTLPOS1.KOMPARTNR, SMSTLPOS1.ARTNR, n.level+1
FROM SMSTLPOS as SMSTLPOS1, n
WHERE n.ARTIKEL_NR = SMSTLPOS1.KOMPARTNR
)
SELECT * FROM n
How can I get last records from the Structure?
You are almost there ...
WITH n(KOMPARTNR, ARTIKEL_NR, level) AS
(SELECT SMSTLPOS.KOMPARTNR, SMSTLPOS.ARTNR, 1 AS level
FROM SMSTLPOS
WHERE
SMSTLPOS.KOMPARTNR='49807'
UNION ALL
SELECT SMSTLPOS1.KOMPARTNR, SMSTLPOS1.ARTNR, n.level+1
FROM SMSTLPOS as SMSTLPOS1, n
WHERE n.ARTIKEL_NR = SMSTLPOS1.KOMPARTNR
)
SELECT * FROM n WHERE ARTIKEL_NR NOT IN (SELECT TOP (
(SELECT COUNT(*) FROM n) - 2
) ARTIKEL_NR
FROM n
)
And even simpler
WITH n(KOMPARTNR, ARTIKEL_NR, level) AS
(SELECT SMSTLPOS.KOMPARTNR, SMSTLPOS.ARTNR, 1 AS level
FROM SMSTLPOS
WHERE
SMSTLPOS.KOMPARTNR='49807'
UNION ALL
SELECT SMSTLPOS1.KOMPARTNR, SMSTLPOS1.ARTNR, n.level+1
FROM SMSTLPOS as SMSTLPOS1, n
WHERE n.ARTIKEL_NR = SMSTLPOS1.KOMPARTNR
)
SELECT * FROM n EXCEPT SELECT TOP (( SELECT COUNT(ARTIKEL_NR) FROM n) -2) * FROM n

From CTE to mySQL stored procedure code

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/