How to reference an input in SQL functions? - mysql

I have a table like this:
+----+--------+
| Id | Salary |
+----+--------+
| 1 | 100 |
| 2 | 200 |
| 3 | 300 |
+----+--------+
I am writing a function with mySQL to get the n th largest value in Salary.
Here is the function:
CREATE FUNCTION getNthHighestSalary(N INT) RETURNS INT
BEGIN
RETURN (
# Write your MySQL query statement below.
select DISTINCT Salary
FROM Employee
ORDER BY Salary DESC
LIMIT 1 offset (N - 1)
#FETCH NEXT 1 ROWS ONLY
);
END
But I got the an error near (N-1).
if I change (N-1) to 1 :
CREATE FUNCTION getNthHighestSalary(N INT) RETURNS INT
BEGIN
RETURN (
# Write your MySQL query statement below.
select DISTINCT Salary
FROM Employee
ORDER BY Salary DESC
LIMIT 1 offset 1
#FETCH NEXT 1 ROWS ONLY
);
END
It runs correctly.
So the question is how to reference input in SQL function? It seems it can be directly called as an argument as we do in other languages.

LIMIT argument cannot be a variable. Use prepared statement - in it the LIMIT parameter may be taken from a variable. But dynamic SQL is not allowed in the function - use stored procedure:
CREATE PROCEDURE getNthHighestSalary(N INT)
BEGIN
SET #sql := 'SELECT DISTINCT Salary
INTO #output
FROM Employee
ORDER BY Salary DESC
LIMIT 1 OFFSET ?';
PREPARE stmt FROM #sql;
SET #output := N-1;
EXECUTE stmt USING #output;
DROP PREPARE stmt;
SELECT #output;
END
fiddle

It seems I need to declare and set a variable before reference it in SQL query:
CREATE FUNCTION getNthHighestSalary(N INT) RETURNS INT
BEGIN
DECLARE M INT;
SET M=N-1;
RETURN (
# Write your MySQL query statement below.
select DISTINCT Salary
FROM Employee
ORDER BY Salary DESC
LIMIT 1 OFFSET M
#FETCH NEXT 1 ROWS ONLY
);
END

Related

MySQL cursor avg() fetch null

I have a computed column in cursor select query.
drop procedure if exists update_avg;
delimiter $$
create procedure update_avg()
BEGIN
declare score decimal(9,4);
declare id varchar(5);
declare done bool default false;
declare c_update cursor for
select stu_id, avg(score) from chooses group by stu_id;
declare continue HANDLER for not found set done = true;
open c_update;
fetch c_update into id, score;
select id, score; -- test purpose
while(not done) do
update student set average_score = score where student_id = id;
fetch c_update into id, score;
end while;
close c_update;
END
delimiter ;
call update_avg();
When I execute this query it works fine:
select stu_id, avg(score) from chooses group by stu_id;
|stu_id|avg(score)|
|------|----------|
|1 | 73.5000|
|10 | 93.0000|
|11 | 53.0000|
...
And when I call update_avg(); output:
|id|score|
|--|-----|
|1 |NULL |
My question is why this cursor cannot fetch avg(score) from select query and how to fix this.
This operation does not require cursor and could be done using correlated subquery:
update student
set average_score = (SELECT AVG(score)
FROM chooses
WHERE chooses.stu_id = student.student_id)
-- WHERE EXISTS (SELECT 1 FROM chooses WHERE chooses.stu_id = student.student_id)
-- this condition is to avoid updating rows
-- that do not have corresponding rows in chooses table

MYSQL concat char to create table name with select

I tried to use such code
select
price
from
(select concat("_",`id`) -- to set table name
as
dta
from
druk
where
date >= '2021-02-01' and date < '2021-03-01') d
If I put * instead price I get for example "_5438" - table name. One or more. In this way I can't get price.
I tried to use variables from examples I found, but some of them mysql do'es not recognize. What should I do to make proper, dynamic table name with concat?
You can use dynamic sql for this.
First you get the table nane into #dta and after that constructs the actual query which you run as prepare statements
But this onlöy works, if your forwst select only goves back 1 eow as result, that is why it is LIMIT 1
SELECT concat("_",`id`)
INTO
#dta
FROM
druk
WHERE
date >= '2021-02-01' AND `date` < '2021-03-01' LIMIT 1;
SET #sql := CONCAT('SELECT price FROM ',#dta );
PREPARE stmt FROM #sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
Still this does look like a poor design. to have many tables with the same design
For multiple ids you need a stored procedure, it can look like this).
But i didn_'t test it
CREATE TABLE druk(`id` INT , `date` date)
INSERT INTO druk VALUES (1,now()),(2,now())
CREATE TABLE _1 (price DECIMAL(8,3))
INSERT INTO _1 VALUE (2.3),(4.6),(7.9)
CREATE TABLE _2 (price DECIMAL(8,3))
INSERT INTO _2 VALUE (0.3),(1.6),(3.9)
CREATE PROCEDURE createpricelist( IN _datefrom varchar(29),IN _dateto varchar(20)
)
BEGIN
DECLARE finished INTEGER DEFAULT 0;
DECLARE _id VARCHAR(29);
DEClARE curid
CURSOR FOR
SELECT concat("_",`id`) -- to set table name
FROM
druk
WHERE
`date` >= _datefrom AND `date` < _dateto;
-- declare NOT FOUND handler
DECLARE CONTINUE HANDLER
FOR NOT FOUND SET finished = 1;
SET #datefrom := _datefrom;
SET #dateto := _dateto;
DROP TEMPORARY TABLE IF EXISTS tableprice;
CREATE TEMPORARY TABLE tableprice (price DECIMAL(8,2));
OPEN curid;
getprice: LOOP
FETCH curid INTO _id;
IF finished = 1 THEN
LEAVE getprice;
END IF;
SET #od = _id;
SET #sql := CONCAT('INSERT INTO tableprice (price) select price from ',_id);
PREPARE stmt FROM #sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
END LOOP getprice;
CLOSE curid;
-- Diplay temporary table you can use it outside the procedure
SELECT * FROM tableprice;
END
✓
CALL createpricelist(NOW() -INTERVAL 1 DAY,NOW())
| price |
| ----: |
| 2.30 |
| 4.60 |
| 7.90 |
| 0.30 |
| 1.60 |
| 3.90 |
✓
db<>fiddle here

sql get Nth hightst salary from table

Write a SQL query to get the nth highest salary from the Employee table.
+----+--------+
| Id | Salary |
+----+--------+
| 1 | 100 |
| 2 | 200 |
| 3 | 300 |
+----+--------+
For example, given the above Employee table, the nth highest salary where n = 2 is 200. If there is no nth highest salary, then the query should return null.
my answer
CREATE FUNCTION getNthHighestSalary(N INT) RETURNS INT
BEGIN
RETURN (
# Write your MySQL query statement below.
SELECT Salary FROM Employee ORDER BY Salary DESC LIMIT N-1, 1
);
END
error:
Runtime Error Message: Line 6: SyntaxError: near '-1, 1 ); END'
Last executed input: {"headers": {"Employee": ["Id", "Salary"]}, "argument": 1, "rows": {"Employee": [[1, 100]]}}
DELIMITER $
CREATE FUNCTION salarynth(n1 INT)
RETURNS INT
DETERMINISTIC
BEGIN
DECLARE myvar INT;
SELECT
SUBSTRING_INDEX(SUBSTRING_INDEX(CONCAT(group_concat(salary order by id),','), ',', n1),',',-1) INTO myvar FROM t;
RETURN myvar;
END$
DELIMITER ;
SELECT `salarynth`('3');
300

MySql Recursive Query Alternative? [duplicate]

I have the following table:
id | parent_id | quantity
-------------------------
1 | null | 5
2 | null | 3
3 | 2 | 10
4 | 2 | 15
5 | 3 | 2
6 | 5 | 4
7 | 1 | 9
Now I need a stored procedure in mysql that calls itself recursively and returns the computed quantity.
For example the id 6 has 5 as a parent which as 3 as a parent which has 2 as a parent.
So I need to compute 4 * 2 * 10 * 3 ( = 240) as a result.
I am fairly new to stored procedures and I won't use them very often in the future because I prefer having my business logic in my program code rather then in the database. But in this case I can't avoid it.
Maybe a mysql guru (that's you) can hack together a working statement in a couple of seconds.
its work only in mysql version >= 5
the stored procedure declaration is this,
you can give it little improve , but this working :
DELIMITER $$
CREATE PROCEDURE calctotal(
IN number INT,
OUT total INT
)
BEGIN
DECLARE parent_ID INT DEFAULT NULL ;
DECLARE tmptotal INT DEFAULT 0;
DECLARE tmptotal2 INT DEFAULT 0;
SELECT parentid FROM test WHERE id = number INTO parent_ID;
SELECT quantity FROM test WHERE id = number INTO tmptotal;
IF parent_ID IS NULL
THEN
SET total = tmptotal;
ELSE
CALL calctotal(parent_ID, tmptotal2);
SET total = tmptotal2 * tmptotal;
END IF;
END$$
DELIMITER ;
the calling is like
(its important to set this variable) :
SET ##GLOBAL.max_sp_recursion_depth = 255;
SET ##session.max_sp_recursion_depth = 255;
CALL calctotal(6, #total);
SELECT #total;
Take a look at Managing Hierarchical Data in MySQL by Mike Hillyer.
It contains fully worked examples on dealing with hierarchical data.
How about avoiding procedures:
SELECT quantity from (
SELECT #rq:=parent_id as id, #val:=#val*quantity as quantity from (
select * from testTable order by -id limit 1000000 # 'limit' is required for MariaDB if we want to sort rows in subquery
) t # we have to inverse ids first in order to get this working...
join
( select #rq:= 6 /* example query */, #val:= 1 /* we are going to multiply values */) tmp
where id=#rq
) c where id is null;
Check out Fiddle!
Note! this will not work if row's parent_id>id.
Cheers!
DELIMITER $$
CREATE DEFINER=`arun`#`%` PROCEDURE `recursivesubtree`( in iroot int(100) , in ilevel int(110) , in locid int(101) )
BEGIN
DECLARE irows,ichildid,iparentid,ichildcount,done INT DEFAULT 0;
DECLARE cname VARCHAR(64);
SET irows = ( SELECT COUNT(*) FROM account WHERE parent_id=iroot and location_id=locid );
IF ilevel = 0 THEN
DROP TEMPORARY TABLE IF EXISTS _descendants;
CREATE TEMPORARY TABLE _descendants (
childID INT, parentID INT, name VARCHAR(64), childcount INT, level INT
);
END IF;
IF irows > 0 THEN
BEGIN
DECLARE cur CURSOR FOR
SELECT
f.account_id,f.parent_id,f.account_name,
(SELECT COUNT(*) FROM account WHERE parent_id=t.account_id and location_id=locid ) AS childcount
FROM account t JOIN account f ON t.account_id=f.account_id
WHERE t.parent_id=iroot and t.location_id=locid
ORDER BY childcount<>0,t.account_id;
DECLARE CONTINUE HANDLER FOR SQLSTATE '02000' SET done = 1;
OPEN cur;
WHILE NOT done DO
FETCH cur INTO ichildid,iparentid,cname,ichildcount;
IF NOT done THEN
INSERT INTO _descendants VALUES(ichildid,iparentid,cname,ichildcount,ilevel );
IF ichildcount > 0 THEN
CALL recursivesubtree( ichildid, ilevel + 1 );
END IF;
END IF;
END WHILE;
CLOSE cur;
END;
END IF;
IF ilevel = 0 THEN
-- Show result table headed by name that corresponds to iroot:
SET cname = (SELECT account_name FROM account WHERE account_id=iroot and location_id=locid );
SET #sql = CONCAT('SELECT CONCAT(REPEAT(CHAR(36),2*level),IF(childcount,UPPER(name),name))',
' AS ', CHAR(39),cname,CHAR(39),' FROM _descendants');
PREPARE stmt FROM #sql;
EXECUTE stmt;
DROP PREPARE stmt;
END IF;
END$$
DELIMITER ;

mysql stored procedure that calls itself recursively

I have the following table:
id | parent_id | quantity
-------------------------
1 | null | 5
2 | null | 3
3 | 2 | 10
4 | 2 | 15
5 | 3 | 2
6 | 5 | 4
7 | 1 | 9
Now I need a stored procedure in mysql that calls itself recursively and returns the computed quantity.
For example the id 6 has 5 as a parent which as 3 as a parent which has 2 as a parent.
So I need to compute 4 * 2 * 10 * 3 ( = 240) as a result.
I am fairly new to stored procedures and I won't use them very often in the future because I prefer having my business logic in my program code rather then in the database. But in this case I can't avoid it.
Maybe a mysql guru (that's you) can hack together a working statement in a couple of seconds.
its work only in mysql version >= 5
the stored procedure declaration is this,
you can give it little improve , but this working :
DELIMITER $$
CREATE PROCEDURE calctotal(
IN number INT,
OUT total INT
)
BEGIN
DECLARE parent_ID INT DEFAULT NULL ;
DECLARE tmptotal INT DEFAULT 0;
DECLARE tmptotal2 INT DEFAULT 0;
SELECT parentid FROM test WHERE id = number INTO parent_ID;
SELECT quantity FROM test WHERE id = number INTO tmptotal;
IF parent_ID IS NULL
THEN
SET total = tmptotal;
ELSE
CALL calctotal(parent_ID, tmptotal2);
SET total = tmptotal2 * tmptotal;
END IF;
END$$
DELIMITER ;
the calling is like
(its important to set this variable) :
SET ##GLOBAL.max_sp_recursion_depth = 255;
SET ##session.max_sp_recursion_depth = 255;
CALL calctotal(6, #total);
SELECT #total;
Take a look at Managing Hierarchical Data in MySQL by Mike Hillyer.
It contains fully worked examples on dealing with hierarchical data.
How about avoiding procedures:
SELECT quantity from (
SELECT #rq:=parent_id as id, #val:=#val*quantity as quantity from (
select * from testTable order by -id limit 1000000 # 'limit' is required for MariaDB if we want to sort rows in subquery
) t # we have to inverse ids first in order to get this working...
join
( select #rq:= 6 /* example query */, #val:= 1 /* we are going to multiply values */) tmp
where id=#rq
) c where id is null;
Check out Fiddle!
Note! this will not work if row's parent_id>id.
Cheers!
DELIMITER $$
CREATE DEFINER=`arun`#`%` PROCEDURE `recursivesubtree`( in iroot int(100) , in ilevel int(110) , in locid int(101) )
BEGIN
DECLARE irows,ichildid,iparentid,ichildcount,done INT DEFAULT 0;
DECLARE cname VARCHAR(64);
SET irows = ( SELECT COUNT(*) FROM account WHERE parent_id=iroot and location_id=locid );
IF ilevel = 0 THEN
DROP TEMPORARY TABLE IF EXISTS _descendants;
CREATE TEMPORARY TABLE _descendants (
childID INT, parentID INT, name VARCHAR(64), childcount INT, level INT
);
END IF;
IF irows > 0 THEN
BEGIN
DECLARE cur CURSOR FOR
SELECT
f.account_id,f.parent_id,f.account_name,
(SELECT COUNT(*) FROM account WHERE parent_id=t.account_id and location_id=locid ) AS childcount
FROM account t JOIN account f ON t.account_id=f.account_id
WHERE t.parent_id=iroot and t.location_id=locid
ORDER BY childcount<>0,t.account_id;
DECLARE CONTINUE HANDLER FOR SQLSTATE '02000' SET done = 1;
OPEN cur;
WHILE NOT done DO
FETCH cur INTO ichildid,iparentid,cname,ichildcount;
IF NOT done THEN
INSERT INTO _descendants VALUES(ichildid,iparentid,cname,ichildcount,ilevel );
IF ichildcount > 0 THEN
CALL recursivesubtree( ichildid, ilevel + 1 );
END IF;
END IF;
END WHILE;
CLOSE cur;
END;
END IF;
IF ilevel = 0 THEN
-- Show result table headed by name that corresponds to iroot:
SET cname = (SELECT account_name FROM account WHERE account_id=iroot and location_id=locid );
SET #sql = CONCAT('SELECT CONCAT(REPEAT(CHAR(36),2*level),IF(childcount,UPPER(name),name))',
' AS ', CHAR(39),cname,CHAR(39),' FROM _descendants');
PREPARE stmt FROM #sql;
EXECUTE stmt;
DROP PREPARE stmt;
END IF;
END$$
DELIMITER ;