I need to convert an adjacency list to nested set in MySql. I have found only one resource over the internet to convert an adjacency list into a nested set using mysql(http://data.bangtech.com/sql/nested_set_treeview.htm). The code is also on the same webpage.
CREATE TABLE test.Tree
(emp CHAR(10) NOT NULL,
boss CHAR(10));
CREATE TABLE test.Personnel(
emp CHAR(20) PRIMARY KEY,
boss CHAR(20) REFERENCES Personnel(emp),
salary DECIMAL(6,2) NOT NULL
);
INSERT INTO test.Personnel VALUES ('jerry', 'NULL',1000.00);
INSERT INTO test.Personnel VALUES ('Bert', 'jerry',900.00);
INSERT INTO test.Personnel VALUES ('chuck', 'jerry',900.00);
INSERT INTO test.Personnel VALUES ('donna', 'chuck',800.00);
INSERT INTO test.Personnel VALUES ('eddie', 'chuck',700.00);
INSERT INTO test.Personnel VALUES ('fred', 'chuck',600.00);
INSERT INTO test.Tree
SELECT emp, boss FROM test.Personnel;
I make the Tree table from Personnel table. Tree table has the boss-employee hierarchy. This is an adjacency list. To convert it to the nest set, I applied this code.
BEGIN ATOMIC
DECLARE counter integer;
DECLARE max_counter integer;
DECLARE current_top integer;
SET counter = 2;
SET max_counter = 2 * (SELECT COUNT(*) FROM test.Tree);
SET current_top = 1;
INSERT INTO test.Stack
SELECT 1, emp, 1, NULL
FROM test.Tree
WHERE boss IS NULL;
DELETE FROM test.Tree
WHERE boss IS NULL;
WHILE counter <=(max_counter - 2)
LOOP IF EXISTS (SELECT * FROM test.Stack AS S1, test.Tree AS T1
WHERE S1.emp = T1.boss AND S1.stack_top = current_top)
THEN
BEGIN -- push when top has subordinates, set lft value
INSERT INTO test.Stack
SELECT (current_top + 1), MIN(T1.emp), counter, NULL
FROM test.Stack AS S1, test.Tree AS T1
WHERE S1.emp = T1.boss
AND S1.stack_top = current_top;
DELETE FROM test.Tree
WHERE emp = (SELECT emp
FROM test.Stack
WHERE stack_top = current_top + 1);
SET counter = counter + 1;
SET current_top = current_top + 1;
END
ELSE
BEGIN -- pop the stack and set rgt value
UPDATE test.Stack
SET rgt = counter,
stack_top = -stack_top -- pops the stack
WHERE stack_top = current_top
SET counter = counter + 1;
SET current_top = current_top - 1;
END IF;
END LOOP;
END;
MySQL workbench shows several syntax errors which I could not remove.
I am familiar with only very basic operations of mysql so could not debug the code on my own. How to remove all these errors? Plz Help.
The second source I found to do the above operation is http://www.sqlservercentral.com/articles/Hierarchy/94040/ but the code is in T Sql and I don't have enough skills to translate it to MySQL.
You should put NULL and not 'NULL' in the line
INSERT INTO test.Personnel VALUES ('jerry', 'NULL',1000.00);
Correct version:
INSERT INTO test.Personnel VALUES ('jerry', NULL, 1000.00);
Use Bash:
Bash converting:
# SQL command to fetch necessary fields, output it to text archive "tree"
SELECT id, parent_id, name FROM projects;
# Make a list "id|parentid|name" and sort by name
cat tree |
cut -d "|" -f 2-4 |
sed 's/^ *//;s/ *| */|/g' |
sort -t "|" -k 3,3 > list
# Creates the parenthood chain on second field
while IFS="|" read i p o
do
l=$p
while [[ "$p" != "NULL" ]]
do
p=$(grep -w "^$p" list | cut -d "|" -f 2)
l="$l,$p"
done
echo "$i|$l|$o"
done < list > listpar
# Creates left and right on 4th and 5th fields for interaction 0
let left=0
while IFS="|" read i l o
do
let dif=$(grep "\b$i,NULL|" listpar | wc -l)*2+1
let right=++left+dif
echo "$i|$l|$o|$left|$right"
let left=right
done <<< "$(grep "|NULL|" listpar)" > i0
# The same for following interactions
n=0
while [ -s i$n ]
do
while IFS="|" read i l nil left nil
do
grep "|$i,$l|" listpar |
while IFS="|" read i l o
do
let dif=$(grep "\b$i,$l|" listpar | wc -l)*2+1
let right=++left+dif
echo "$i|$l|$o|$left|$right"
let left=right
done
done < i$n > i$((++n))
done
# Show concatenated
cat i*|sort -t"|" -k 4n
# SQL commands
while IFS="|" read id nil nil left right
do
echo "UPDATE projects SET lft=$left, rgt=$right WHERE id=$id;"
done <<< "$(cat i*)"
Related
I'm using a procedure to calculate the length of user 'hiatus' (aka contingencies) from the program in our system. It runs after a procedure that determines user status depending on whether they are completing their daily treatment and to what extent.
The purpose of this procedure is to log the length of a user's contingency, by adding a row to a table with the following schema:
id_contingency int(11) NOT NULL AUTO_INCREMENT,
id_user int(11) DEFAULT NULL,
date_start date DEFAULT NULL,
program_day int(11) DEFAULT NULL,
date_end date DEFAULT NULL,
total_days int(11) DEFAULT NULL,
latest_tf_id archer(255) DEFAULT NULL
I considered adding this as a trigger on the update of the user_status table, but I can't risk an error preventing that table from updating. So, this procedure first closes contingencies that were previously open, when the user first entered the hiatus, but has now resumed the program, and it later opens new contingencies for users who have now started a hiatus in their treatment for the first time. It then remains open until they resume the program, and calculates how long they were on hiatus for.
This was my original procedure, and it returned error 1109 (unknown table tbl_user_status) :
DELIMITER $$
CREATE DEFINER=CURRENT_USER PROCEDURE `proc_cont_calc`
NO SQL
BEGIN
#CLOSE OPEN CONTINGENCIES FIRST or d0 > d1
CASE
WHEN tbl_user_status.d4 = 1 AND tbl_user_status.d2 > 0 AND tbl_user_status.user_status = 'seguimiento' THEN
UPDATE tbl_user_contingency, tbl_user_status SET
tbl_user_contingency.date_end = CURRENT_DATE,
tbl_user_contingency.total_days = DATEDIFF(tbl_user_contingency.date_start, tbl_user_contingency.date_end),
tbl_user_contingency.updated_by = 'proc_cont.close'
WHERE tbl_user_contingency.date_end = '' AND tbl_user_contingency.id_smoker = tbl_user_status.id_smoker LIMIT 1;
#OPEN NEW CONTINGENCIES
WHEN tbl_user_status.d5 = 1 AND tbl_user_status.d4 = 0 AND tbl_user_status.user_status = 'contingencia' THEN
INSERT INTO tbl_user_contingency (id_smoker, roadmap_day, date_start, latest_tf_id, updated_by) SELECT
id_smoker, roadmap_day, CURRENT_DATE, latest_tf_id, 'proc_cont.open' FROM tbl_user_status;
END CASE;
END$$
DELIMITER;
So I tried this (amongst other things):
CASE
WHEN (SELECT d4 FROM tbl_user_status) = 1 AND (SELECT d2 FROM tbl_user_status) > 0 AND (SELECT user_status FROM tbl_user_status) = 'seguimiento' THEN
UPDATE tbl_user_contingency, tbl_user_status SET
tbl_user_contingency.date_end = CURRENT_DATE,
tbl_user_contingency.total_days = DATEDIFF(tbl_user_contingency.date_start, tbl_user_contingency.date_end),
tbl_user_contingency.updated_by = 'proc_cont.close'
WHERE tbl_user_contingency.id_smoker = tbl_user_status.id_smoker LIMIT 1;
#OPEN NEW CONTINGENCIES
WHEN (SELECT d5 FROM tbl_user_status) = 1 AND (SELECT d4 FROM tbl_user_status) = 0 AND (SELECT user_status FROM tbl_user_status) = 'contingencia' THEN
INSERT INTO tbl_user_contingency (id_smoker, roadmap_day, date_start, latest_tf_id, updated_by) SELECT
id_smoker, roadmap_day, CURRENT_DATE, latest_tf_id, 'proc_cont.open' FROM tbl_user_status;
END CASE;
And now I'm getting error 1242 returning multiple rows.
How can I get this procedure to run properly? Thanks!
UPDATE - I tried #P.Salmon's suggestion to simply update the rows, but not all the fields were filling out, or the update overruns previous contingencies.
Thanks!
The case statement seems unnecessary here just move the conditions to where clauses for example
UPDATE tbl_user_contingency join tbl_user_status on tbl_user_contingency.id_smoker = tbl_user_status.id_smoker
SET
tbl_user_contingency.date_end = CURRENT_DATE,
tbl_user_contingency.total_days = DATEDIFF(tbl_user_contingency.date_start, tbl_user_contingency.date_end),
tbl_user_contingency.updated_by = 'proc_cont.close'
WHERE tbl_user_contingency.date_end = '' AND
tbl_user_status.d4 = 1 AND tbl_user_status.d2 > 0 AND tbl_user_status.user_status = 'seguimiento'
;
INSERT INTO tbl_user_contingency (id_smoker, roadmap_day, date_start, latest_tf_id, updated_by)
SELECT
id_smoker, roadmap_day, CURRENT_DATE, latest_tf_id, 'proc_cont.open'
FROM tbl_user_status
where tbl_user_status.d5 = 1 AND tbl_user_status.d4 = 0 AND tbl_user_status.user_status = 'contingencia'
;
You could improve your question and get thereby a better response if you describe what it is you are trying to do instead of having us guess by reverse engineering two non working code segments, by adding your table definitions, sample data and expected output as text to your question. BTW I hope you have a mechanism that will stop this thing doing stuff more than once.
I would like to convert the result of MySQL stored procedure to JSON data and insert into another table.
When trying to the varchar datatype its working fine but I need JSON or array kind of data.
call="CALL status(input1,#data1,#data2,#data3.......)
select="select #data1, #data2, #data3.....;"
output=$(mysql --user=root --password=xxx db << eof
$call
$select
eof)
mysql --user=root --password=xxx db << eof
insert into sam values ('$output');
eof
This is the output result but I need this output like JSON. How to construct as JSON and pass the value to insert query in MySQL.
#data1 #data2 #data3 #data4 #data5 #data6 ..................
1213 1174 367 57 8 7398 39 .............
This is my stored procedure:
DELIMITER ;;
CREATE PROCEDURE status(
IN input1 INT,
IN input2 INT,
OUT data1 INT,
OUT data2 INT,
OUT data3 INT)
BEGIN
-- data1
select count(*) INTO data1 from account where time >=input1 and time >=input2;
-- data2
SELECT SUM(if(status>0,1,0)) INTO data2 from account where time >=input1 and time >=input2;
-- data3
SELECT SUM(if(status=0,1,0)) INTO data3 from account where time >=input1 and time >=input2;
END
;;
How can i construct this output like JSON?? I'm new to this topic!!
MySQL 5.7.8 or later versions support the JSON data type.
As of MySQL 5.7.8, MySQL supports a native JSON data type
If it is the case for you then you can rewrite your stored procedure to output directly the desired JSON data.
Example data
SET #data1 = 1213;
SET #data2 = 1174;
SET #data3 = 367;
SET #data4 = 57;
SET #data5 = 8;
SET #data6 = 7398;
Example SELECT
SELECT JSON_MERGE(
JSON_OBJECT('#data1', #data1),
JSON_OBJECT('#data2', #data2),
JSON_OBJECT('#data3', #data3),
JSON_OBJECT('#data4', #data4),
JSON_OBJECT('#data5', #data5),
JSON_OBJECT('#data6', #data6)
);
Output (pretty printed just for better readability)
{
"#data1": 1213,
"#data2": 1174,
"#data3": 367,
"#data4": 57,
"#data5": 8,
"#data6": 7398
}
Then to profit the MySQL JSON handling capabilities, the destination column should have the JSON data type. Like this you will be able to query your json in mysql.
Bash
In case you do not have the privilege to rewrite the stored procedure you can use the following dirty solution. Simply pipe (|) the output of mysql into awk (for example) where we apply the necessary formatting for the desired fields. A tool designed for this purpose would do much better, for example: jo or jq.
mysql --login-path=local -s -e 'SET #data1 = 1213; SET #data2 = 1174; SET #data3 = 367; SET #data4 = 57; SET #data5 = 8; SET #data6 = 7398; SELECT #data1, #data2, #data3, #data4, #data5, #data6' | awk '{ print "{\"#data1\":"$1",", "\"#data2\":"$2",", "\"#data3\":"$3",", "\"#data4\":"$4",", "\"#data5\":"$5",", "\"#data6\":"$6"}"}'
Note the --login-path=local that I am using for mysql authentication instead of -u ... -p... (Read more here).
UPDATE (Only works with MySQL 5.7.8 or newer versions)
I would rewrite your stored procedure something like this:
DROP PROCEDURE IF EXISTS status_json;
DELIMITER ##
CREATE PROCEDURE status_json (
IN input1 INT,
IN input2 INT,
OUT data JSON
)
proc: BEGIN
SELECT JSON_MERGE(
JSON_OBJECT('count', (SELECT COUNT(*) FROM account WHERE time >= input1 AND time >= input2)),
JSON_OBJECT('sum_gt_0', (SELECT SUM(IF(status > 0, 1, 0)) FROM account WHERE time >= input1 AND time >= input2)),
JSON_OBJECT('sum_eq_0', (SELECT SUM(IF(status = 0, 1, 0)) FROM account WHERE time >= input1 AND time >= input2))
) INTO data;
END proc ##
DELIMITER ;
Call it:
CALL status_json(123, 456, #json);
And use the ouptut:
SELECT #json;
Example for the rewritten stored procedure
Table:
CREATE TABLE account (
id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
time INT UNSIGNED NOT NULL,
status TINYINT UNSIGNED NOT NULL
);
Data:
INSERT INTO account (time, status) VALUES
(1513329548, 0),
(1513329528, 1),
(1513329508, 1),
(1513329648, 0),
(1513329148, 1),
(1513329540, 0),
(1513322548, 0),
(1513327548, 1);
CALL and SELECT:
CALL status_json(1513329508, 1513322548, #json);
SELECT #json;
Result:
{"count": 5, "sum_eq_0": 3, "sum_gt_0": 2}
I have a dictionary table (words) and another table with concatenated 2 words like "helpme", "helloword" "loveme"...
I want to transform this table to "help me", "hello word", "love me"
I run this sequence :
SELECT
table_concatened.twowords,
t1.word as 'word1',
t2.word as 'word2'
FROM
table_concatened
JOIN dictionary_table AS t1 ON SUBSTRING(table_concatened.twowords,1,len(t1.word)) = t1.word
JOIN dictionary_table AS t2 ON SUBSTRING(table_concatened.twowords,len(t1.word)+1,len(table_concatened.twowords)) = t2.word;
It is working, but is took a very long time with my table.
How can I optimise my sql sequence?
---- exemple of table ---
dictionary_table
|hello|
|word |
|love |
|me |
exemple of table_concatened :
|helloword|
|loveyou |
Edit:
1) The use case is for autocorrection. For example on skype, on iPhone, on chrome, when I type "helloword", I have auto correction to "hello word".
2) The database here is not very important. Our issue is about algo logic and performance optimisation.
If you don't mind going dynamic (and if SQL Server)
-- Generate Some Sample Data
Declare #Dictionary_Table table (word varchar(50));Insert Into #Dictionary_Table values ('hello'),('word'),('love'),('me')
Declare #table_concatened table (ID int,twowords varchar(50));Insert Into #table_concatened values (1,'helloword'),(2,'loveyou')
-- Generate SQL and Execute
Declare #SQL varchar(max)=''
Select #SQL = #SQL+concat(',(',ID,',''||',replace(twowords,'''',''''''),'||'')') From #table_concatened --Where ID=2
Select #SQL = Replace(#SQL,MapFrom,MapTo)
From (
Select MapFrom = word
,MapTo = '|'+ltrim(rtrim(word))+'|'
From #Dictionary_Table
Union All
Select '|',' ' -- Remove Any Remaining |
Union All
Select ' ',' ' -- Remove Any Remaining |
) A
Select #SQL = 'Select ID,Value=ltrim(rtrim(Value)) From ('+Stuff(#SQL,1,1,'values')+') N(ID,Value)'
Exec(#SQL)
Returns
ID Value
1 hello word
2 love you
I met a problem when calling a user-defined function in MySQL. The computation is very simple but can't grasp where it went wrong and why it went wrong. Here's the thing.
So I created this function:
DELIMITER //
CREATE FUNCTION fn_computeLoanAmortization (_empId INT, _typeId INT)
RETURNS DECIMAL(17, 2)
BEGIN
SET #loanDeduction = 0.00;
SELECT TotalAmount, PeriodicDeduction, TotalInstallments, DeductionFlag
INTO #totalAmount, #periodicDeduction, #totalInstallments, #deductionFlag
FROM loans_table
WHERE TypeId = _typeId AND EmpId = _empId;
IF (#deductionFlag = 1) THEN
SET #remaining = #totalAmount - #totalInstallments;
IF(#remaining < #periodicDeduction) THEN
SET #loanDeduction = #remaining;
ELSE
SET #loanDeduction = #periodicDeduction;
END IF;
END IF;
RETURN #loanDeduction;
END;//
DELIMITER ;
If I call it like this, it works fine:
SELECT fn_computeLoanAmortization(3, 4)
But if I call it inside a SELECT statement, the result becomes erroneous:
SELECT Id, fn_computeLoanAmortization(Id, 4) AS Amort FROM emp_table
There's only one entry in the loans_table and the above statement should only result with one row having value in the Amort column but there are lots of random rows with the same Amort value as the one with the matching entry, which should not be the case.
Have anyone met this kind of weird dilemma? Or I might have done something wrong from my end. Kindly enlighten me.
Thank you very much.
EDIT:
By erroneous, I meant it like this:
loans_table has one record
EmpId = 1
TypeId = 2
PeriodicDeduction = 100
TotalAmount = 1000
TotalInstallments = 200
DeductionFlag = 1
emp_table has several rows
EmpId = 1
Name = Paolo
EmpId = 2
Name = Nikko
...
EmpId = 5
Name = Ariel
when I query the following statements, I get the correct value:
SELECT fn_computeLoanAmortization(1, 2)
SELECT Id, fn_computeLoanAmortization(Id, 2) AS Amort FROM emp_table WHERE EmpId = 1
But when I query this statement, I get incorrect values:
SELECT Id, fn_computeLoanAmortization(Id, 2) AS Amort FROM emp_table
Resultset would be:
EmpId | Amort
--------------------
1 | 100
2 | 100 (this should be 0, but the query returns 100)
3 | 100 (same error here)
...
5 | 100 (same error here up to the last record)
Inside your function, the variables you use to retrieve the values from the loans_table table are not local variables local to the function but session variables. When the select inside the function does not find any row, those variables still have the same values as from the previous execution of the function.
Use real local variables instead. In order to do that, use the variables names without # as a prefix and declare the variables at the beginning of the function. See this answer for more details.
I suspect the problem is that the variables in the INTO are not re-set when there is no matching row.
Just set them before the INTO:
BEGIN
SET #loanDeduction = 0.00;
SET #totalAmount = 0;
SET #periodicDeduction = 0;
SET #totalInstallments = 0;
SET #deductionFlag = 0;
SELECT TotalAmount, PeriodicDeduction, TotalInstallments, DeductionFlag
. . .
You might just want to set them to NULL.
Or, switch your logic to use local variables:
SET v_loanDeduction = 0.00;
SET v_totalAmount = 0;
SET v_periodicDeduction = 0;
SET v_totalInstallments = 0;
SET v_deductionFlag = 0;
And so on.
How to Auto Increment ID Numbers with Letters and Numbers, example "KP-0001" it will increment to "KP-0002"
Thank you!
here is a useful article
auto increment with a string of numbers and letters
But basically I encourage you to create your own algorithm on this. You can add that algorithm in BEFORE INSERT trigger. Or you can do that on the front-end.
Example of pseudocode for the algorthm
get the lastID [KP-0001]
remove some characters and put it in a variable [KP-]
convert the remaining into number since it's a string [0001]
increment by 1 [1 + 1 = 2]
convert it back to string and pad zero on the right [0002]
concatenate the variable and the newly incremented number [KP-0002]
save it.
I tried to do that in many ways but was unable to reach the solution... I also used triggers but that too didn't help me...
But I found a quick solution for that...
For example you want your employee to have employee codes 'emp101', 'emp102',...etc.
that too with an auto increment...
First of all create a table with three fields the first field containing the letters you want to have at the beginning i.e."emp", the second field containing the auto increasing numbers i.e 101,102,..etc., the third field containing both i.e 'emp101', 'emp102',...etc.
CREATE TABLE employee
(
empstr varchar( 5 ) default 'emp',
empno int( 5 ) AUTO_INCREMENT PRIMARY KEY ,
empcode varchar( 10 )
);
now providing an auto_increment value to empno.
ALTER TABLE employee AUTO_INCREMENT=101;
now coming to the topic... each time you insert values you have to concatenate the first two fields to get the values for the third field
INSERT INTO employee( empcode )
VALUES ('xyz');
UPDATE employee SET empcode = concat( empstr, empno ) ;
You can't auto increment varchar data type. Other way of doing this is to bifurcate varchar column into two different columns one will have integer part and other will have alphabet like in your case KP- once you auto increment all integer rows just concatenate these two columns
CREATE TABLE Customer (
CUSId INT NOT NULL IDENTITY(1, 1) PRIMARY KEY
,CUSKey AS 'Cus' + RIGHT('000' + CONVERT(VARCHAR(5), CUSId), 6) PERSISTED
,CusName VARCHAR(50)
,mobileno INT
,Gender VARCHAR(10)
)
Auto-increment is an integer, so adding text will not be possible.
Check out this question for other references.
Make a procedure, in my case MySQL.
CREATE PROCEDURE MOSTRAR_CODIGO_CLASE_PRODUCTO()
BEGIN
declare max varchar(10);
declare num int;
declare CCodigo varchar(10);
set max = (select MAX(Codigo_CP) from CLASE_PRODUCTO);
set num = (SELECT LTRIM(RIGHT(max,4)));
if num>=1 and num <=8 then
set num = num + 1;
set CCodigo = (select concat('CP000' , CAST(num as CHAR)));
elseif num>=9 and num <=98 then
set num = num + 1;
set CCodigo = (select concat('CP00' , CAST(num as CHAR)));
elseif num>=99 and num <=998 then
set num = num + 1;
set CCodigo = (select concat('CP0' , CAST(num as CHAR)));
elseif num>=999 and num <=9998 then
set num = num + 1;
set CCodigo = (select concat('CP' , CAST(num as CHAR)));
else
set CCodigo=(select 'CP0001');
end if;
SELECT MAX(CCodigo) AS Codigo_CP FROM CLASE_PRODUCTO;
END $
Java Class
public static boolean insertarClaseProducto(ClaseP cp){
boolean resp = false;
Connection cn;
Connection con = new Connection();
cn = con.connect();
try{
CallableStatement cs = cn.prepareCall("CALL REGISTRAR_CLASE_PRODUCTO (?)");
cs.setString(1, cp.getNombreCP());
int i = cs.executeUpdate();
if(i==1)
resp = true;
else
resp = false;
}catch(Exception e){System.out.println(e);}
return resp;
}
returns:
Codigo_MP Nombre_MP Estado_MP
MP0001 LG HAB
MP0002 GENIUS HAB
MP0003 MICRONICS HAB
MP0004 SONY HAB
MP0005 PANASONIC HAB