Is it possible to create this mysql Function? - mysql

I'm a newbie in mysql programming, and i would create a mysql function myFunction with a string parametre; this function query myTable and return a string from the query-result like this example :
myTable
---------------
|id | value |
---------------
| id1 |value1 |
| id2 |value2 |
| id3 |value3 |
| id4 |value4 |
---------------
Calling this function is like this
myFunction('value2#value1#value4')
and must return
'id2#id1#id4'
Thank you very much

This is a demo of how you can do it, of course you can put all together but I find it cleaner to split the functions.
Check the SQL Fiddle
My table
-- the table def
create table myTable (id char(3), value char(6));
insert into myTable values( 'id1', 'value1');
insert into myTable values( 'id2', 'value2');
insert into myTable values( 'id3', 'value3');
insert into myTable values( 'id4', 'value4');
Get a specific id
-- get Id by Value
CREATE function getIdByValue( theValue TEXT )
RETURNS TEXT READS SQL DATA
BEGIN
DECLARE theId TEXT;
DECLARE ok INT DEFAULT FALSE;
DECLARE crs CURSOR FOR
SELECT id FROM myTable where value = theValue;
DECLARE CONTINUE HANDLER FOR NOT FOUND SET ok = TRUE;
SET theId = '';
OPEN crs;
read_loop: LOOP
FETCH crs INTO theId;
IF ok THEN
LEAVE read_loop;
END IF;
END LOOP;
CLOSE crs;
RETURN theId;
END//
MyFunction as you describe
-- the myFunction, usage: myFunction('value2#value1#value4')
CREATE function myFunction( v TEXT )
RETURNS TEXT READS SQL DATA
BEGIN
DECLARE theId TEXT;
DECLARE theIds TEXT;
DECLARE theValue TEXT;
DECLARE vInstr INT;
SET theId = '';
SET theIds = '';
v_loop: LOOP
SET vInstr = INSTR(v,'#');
IF vInstr = 0 THEN
SET theValue = v;
SET theId = getIdByValue(theValue);
ELSE
SET theValue = SUBSTRING(v, 1, vInstr-1);
SET v = SUBSTRING(v, vInstr+1);
SET theId = concat( getIdByValue(theValue), '#');
END IF;
SET theIds = CONCAT(theIds, theId);
IF vInstr = 0 THEN
LEAVE v_loop;
END IF;
END LOOP;
RETURN theIds;
END//
The call
SELECT myFunction( 'value2' );
SELECT myFunction( 'value2#value4' );
SELECT myFunction( 'value2#value4#value1' );
SELECT myFunction( 'value2#value4#value1#value3' );
The results
id2
id2#id4
id2#id4#id1
id2#id4#id1#id3

you can use something like,
CREATE FUNCTION myFunction (param1) RETURNS datatype
[NOT] DETERMINISTIC
statements
check this link for more information
http://www.mysqltutorial.org/mysql-stored-function/

Any reason you couldn't just run this kind of query?
SELECT GROUP_CONCAT(id ORDER BY FIELD(`value`, 'value2', 'value1', 'value4'))
FROM myTable
WHERE `value` IN ('value2','value1','value4')
;
Edited: Added value as first argument to FIELD() function to make it return the ids in the proper order.

Related

What seems to be the error here in the below code?

Error Code: 2014 Commands out of sync; you can't run this command now
Platform- My SQL Workbench
DELIMITER//
DROP PROCEDURE IF EXISTS must_watch_movies;
CREATE PROCEDURE
must_watch_movies ()
BEGIN DECLARE mTitle
VARCHAR (45);
DECLARE mDistributor VARCHAR (45);
DECLARE mRelease datetime;
DECLARE result VARCHAR (1000);
DECLARE no_records INTEGER DEFAULT FALSE;
DECLARE cursor_movies CURSOR FOR
SELECT title, Distributor, year (release_date)
FROM movies WHERE gross > 200000000 ORDER BY title;
DECLARE CONTINUE HANDLER FOR NOT FOUND SET SET no_records = TRUE;
OPEN cursor_movies;
WHILE no_records = FALSE DO
FETCH cursor_movies INTO mTitle, mDistributor, mRelease;
SET result =
CONCAT ("'", mTitle, "','" mDistributor, "','" mRelease, "|");
END WHILE;
CLOSE cursor_movies;
SELECT result AS "Output";
END//
DELIMITER;
CALL must_watch_movies();
The declared values match the datatypes for actual column values in the table.
like mTitle and title are the same type
You have some errors in your code
A double SET when you decalre a handler
You have to concat result if you want all movies.
The out put must be some what more complicated, but i leave that to you
CREATE TABLE movies (title varchar(10),Distributor varchar(19), release_date date,gross BIGINT)
INSERT INTO movies VALUES('text1','text2', NOW(),200000001),('text3','text4', NOW(),200000001),('text5','text6', NOW(),200000001)
CREATE PROCEDURE
must_watch_movies ()
BEGIN DECLARE mTitle
VARCHAR (45);
DECLARE mDistributor VARCHAR (45);
DECLARE mRelease INT;
DECLARE result VARCHAR (1000) DEFAULT "";
DECLARE no_records INTEGER DEFAULT FALSE;
DECLARE cursor_movies CURSOR FOR
SELECT title, Distributor, year (release_date)
FROM movies WHERE gross > 200000000 ORDER BY title;
DECLARE CONTINUE HANDLER FOR NOT FOUND SET no_records = TRUE;
OPEN cursor_movies;
WHILE no_records = FALSE DO
FETCH cursor_movies INTO mTitle, mDistributor, mRelease;
SET result = CONCAT (result,"'", mTitle, "','", mDistributor, "','", mRelease, "|");
END WHILE;
CLOSE cursor_movies;
SELECT result AS "Output";
END
CALL must_watch_movies()
| Output |
| :--------------------------------------------------------------------------------------- |
| 'text1','text2','2021|'text3','text4','2021|'text5','text6','2021|'text5','text6','2021| |
✓
db<>fiddle here

return null value in the JSON_EXTRACT

MyJsonArray
[{"ID":"D29","PersonID":"23616639"},{"ID":"D30","PersonID":"22629626"}]
and I want from sql Function set this array in to my Table but return null value in the variable and not set record in My database
my function:
DELIMITER $$
CREATE DEFINER=`toshiari`#`localhost` FUNCTION `setTitleRecords`(`Title` VARCHAR(166) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci, `List` JSON) RETURNS int(4)
BEGIN
DECLARE Item INT;
DECLARE HolderLENGTH INT;
DECLARE ValidJson INT;
DECLARE ID VARCHAR(166);
DECLARE PersonID VARCHAR(166);
DECLARE S1 VARCHAR(166);
DECLARE S2 VARCHAR(166);
SET ValidJson = (SELECT JSON_VALID(List));
IF ValidJson = 1 THEN
SET HolderLENGTH = (SELECT JSON_LENGTH(List));
SET Item = 0;
WHILE Item < HolderLENGTH DO
SET S1 = CONCAT("'$[",Item, "].ID'");
SET S2 = CONCAT("'$[",Item, "].PersonID'");
SET ID = (SELECT JSON_EXTRACT(List,S1));
SET PersonID = (SELECT JSON_EXTRACT(List,S2));
INSERT INTO `Titles`(`ID`,`PersonID`,`Title`) VALUES (ID, PersonID, Title);
SET Item = Item + 1;
END WHILE;
RETURN 3;
ELSE
RETURN 2;
END IF;
END$$
DELIMITER ;
when I use this command in the Sql commands no problem and return true value
SELECT JSON_EXTRACT('[{"ID":"D29","PersonID":"23616639"},{"ID":"D30","PersonID":"22629626"}]','$[0].ID') return "D29"
return
"D29"
but in when run function from this code
return error and said:
SET #p0='DR'; SET #p1='[{\"ID\":\"D29\",\"PersonID\":\"23616639\"},{\"ID\":\"D30\",\"PersonID\":\"22629626\"}]'; SELECT `setTitleRecords`(#p0, #p1) AS `setTitleRecords`;
#4042 - Syntax error in JSON path in argument 2 to function 'json_extract' at position 1
I created a little test, in order to reproduce your issues. Basically you just need to redeclare S1 and S2, in the following way:
SET S1 = CONCAT('$[',Item,'].ID');
SET S2 = CONCAT('$[',Item,'].PersonID');
And that's it. You can check the test in the following url: https://www.db-fiddle.com/f/2TPgF868snjwcHN3uwoSEA/0

Mysql function that query a table and reconstruct a string from the query-result

I'm a newbie in mysql programming, and i would create a mysql function myFunction with a string parametre; that query a table and reconstruct a string from the query-result like this example :
myTable
---------------
|id | value |
---------------
| id1 |value1 |
| id2 |value2 |
| id3 |value3 |
| id4 |value4 |
---------------
Calling this function is like this
myFunction('value2#value1#value4')
and must return
'id2#id1#id4'
Check the SQL Fiddle
My table
-- the table def
create table myTable (id char(3), value char(6));
insert into myTable values( 'id1', 'value1');
insert into myTable values( 'id2', 'value2');
insert into myTable values( 'id3', 'value3');
insert into myTable values( 'id4', 'value4');
Get a specific id
-- get Id by Value
CREATE function getIdByValue( theValue TEXT )
RETURNS TEXT READS SQL DATA
BEGIN
DECLARE theId TEXT;
DECLARE ok INT DEFAULT FALSE;
DECLARE crs CURSOR FOR
SELECT id FROM myTable where value = theValue;
DECLARE CONTINUE HANDLER FOR NOT FOUND SET ok = TRUE;
SET theId = '';
OPEN crs;
read_loop: LOOP
FETCH crs INTO theId;
IF ok THEN
LEAVE read_loop;
END IF;
END LOOP;
CLOSE crs;
RETURN theId;
END//
MyFunction as you describe
-- the myFunction, usage: myFunction('value2#value1#value4')
CREATE function myFunction( v TEXT )
RETURNS TEXT READS SQL DATA
BEGIN
DECLARE theId TEXT;
DECLARE theIds TEXT;
DECLARE theValue TEXT;
DECLARE vInstr INT;
SET theId = '';
SET theIds = '';
v_loop: LOOP
SET vInstr = INSTR(v,'#');
IF vInstr = 0 THEN
SET theValue = v;
SET theId = getIdByValue(theValue);
ELSE
SET theValue = SUBSTRING(v, 1, vInstr-1);
SET v = SUBSTRING(v, vInstr+1);
SET theId = concat( getIdByValue(theValue), '#');
END IF;
SET theIds = CONCAT(theIds, theId);
IF vInstr = 0 THEN
LEAVE v_loop;
END IF;
END LOOP;
RETURN theIds;
END//
The call
SELECT myFunction( 'value2' );
SELECT myFunction( 'value2#value4' );
SELECT myFunction( 'value2#value4#value1' );
SELECT myFunction( 'value2#value4#value1#value3' );
The results
id2
id2#id4
id2#id4#id1
id2#id4#id1#id3

T-SQL: split and aggregate comma-separated values

I have the following table with each row having comma-separated values:
ID
-----------------------------------------------------------------------------
10031,10042
10064,10023,10060,10065,10003,10011,10009,10012,10027,10004,10037,10039
10009
20011,10027,10032,10063,10023,10033,20060,10012,10020,10031,10011,20036,10041
I need to get a count for each ID (a groupby).
I am just trying to avoid cursor implementation and stumped on how to do this without cursors.
Any Help would be appreciated !
You will want to use a split function:
create FUNCTION [dbo].[Split](#String varchar(MAX), #Delimiter char(1))
returns #temptable TABLE (items varchar(MAX))
as
begin
declare #idx int
declare #slice varchar(8000)
select #idx = 1
if len(#String)<1 or #String is null return
while #idx!= 0
begin
set #idx = charindex(#Delimiter,#String)
if #idx!=0
set #slice = left(#String,#idx - 1)
else
set #slice = #String
if(len(#slice)>0)
insert into #temptable(Items) values(#slice)
set #String = right(#String,len(#String) - #idx)
if len(#String) = 0 break
end
return
end;
And then you can query the data in the following manner:
select items, count(items)
from table1 t1
cross apply dbo.split(t1.id, ',')
group by items
See SQL Fiddle With Demo
Well, the solution i always use, and probably there might be a better way, is to use a function that will split everything. No use for cursors, just a while loop.
if OBJECT_ID('splitValueByDelimiter') is not null
begin
drop function splitValueByDelimiter
end
go
create function splitValueByDelimiter (
#inputValue varchar(max)
, #delimiter varchar(1)
)
returns #results table (value varchar(max))
as
begin
declare #delimeterIndex int
, #tempValue varchar(max)
set #delimeterIndex = 1
while #delimeterIndex > 0 and len(isnull(#inputValue, '')) > 0
begin
set #delimeterIndex = charindex(#delimiter, #inputValue)
if #delimeterIndex > 0
set #tempValue = left(#inputValue, #delimeterIndex - 1)
else
set #tempValue = #inputValue
if(len(#tempValue)>0)
begin
insert
into #results
select #tempValue
end
set #inputValue = right(#inputValue, len(#inputValue) - #delimeterIndex)
end
return
end
After that you can call the output like this :
if object_id('test') is not null
begin
drop table test
end
go
create table test (
Id varchar(max)
)
insert
into test
select '10031,10042'
union all select '10064,10023,10060,10065,10003,10011,10009,10012,10027,10004,10037,10039'
union all select '10009'
union all select '20011,10027,10032,10063,10023,10033,20060,10012,10020,10031,10011,20036,10041'
select value
from test
cross apply splitValueByDelimiter(Id, ',')
Hope it helps, although i am still looping through everything
After reiterating the comment above about NOT putting multiple values into a single column (Use a separate child table with one value per row!),
Nevertheless, one possible approach: use a UDF to convert delimited string to a table. Once all the values have been converted to tables, combine all the tables into one table and do a group By on that table.
Create Function dbo.ParseTextString (#S Text, #delim VarChar(5))
Returns #tOut Table
(ValNum Integer Identity Primary Key,
sVal VarChar(8000))
As
Begin
Declare #dlLen TinyInt -- Length of delimiter
Declare #wind VarChar(8000) -- Will Contain Window into text string
Declare #winLen Integer -- Length of Window
Declare #isLastWin TinyInt -- Boolean to indicate processing Last Window
Declare #wPos Integer -- Start Position of Window within Text String
Declare #roVal VarChar(8000)-- String Data to insert into output Table
Declare #BtchSiz Integer -- Maximum Size of Window
Set #BtchSiz = 7900 -- (Reset to smaller values to test routine)
Declare #dlPos Integer -- Position within Window of next Delimiter
Declare #Strt Integer -- Start Position of each data value within Window
-- -------------------------------------------------------------------------
-- ---------------------------
If #delim is Null Set #delim = '|'
If DataLength(#S) = 0 Or
Substring(#S, 1, #BtchSiz) = #delim Return
-- --------------------------------------------
Select #dlLen = DataLength(#delim),
#Strt = 1, #wPos = 1,
#wind = Substring(#S, 1, #BtchSiz)
Select #winLen = DataLength(#wind),
#isLastWin = Case When DataLength(#wind) = #BtchSiz
Then 0 Else 1 End,
#dlPos = CharIndex(#delim, #wind, #Strt)
-- --------------------------------------------
While #Strt <= #winLen
Begin
If #dlPos = 0 Begin -- No More delimiters in window
If #isLastWin = 1 Set #dlPos = #winLen + 1
Else Begin
Set #wPos = #wPos + #Strt - 1
Set #wind = Substring(#S, #wPos, #BtchSiz)
-- ----------------------------------------
Select #winLen = DataLength(#wind), #Strt = 1,
#isLastWin = Case When DataLength(#wind) = #BtchSiz
Then 0 Else 1 End,
#dlPos = CharIndex(#delim, #wind, 1)
If #dlPos = 0 Set #dlPos = #winLen + 1
End
End
-- -------------------------------
Insert #tOut (sVal)
Select LTrim(Substring(#wind,
#Strt, #dlPos - #Strt))
-- -------------------------------
-- Move #Strt to char after last delimiter
Set #Strt = #dlPos + #dlLen
Set #dlPos = CharIndex(#delim, #wind, #Strt)
End
Return
End
Then write, (using your table schema),
Declare #AllVals VarChar(8000)
Select #AllVals = Coalesce(#allVals + ',', '') + ID
From Table Where ID Is Not null
-- -----------------------------------------
Select sVal, Count(*)
From dbo.ParseTextString(#AllVals, ',')
Group By sval

MySQL limit by sum

I want to limit my SELECT results in mySQL by sum.
For Example, this is my table:
(id, val)
Data Entries:
(1,100),
(2,300),
(3,50),
(4,3000)
I want to select first k entries such that the sum of val in those entries is just enough to make it to M.
For example, I want to find entries such that M = 425.
The result should be (1,100),(2,300),(3,50).
How can I do that in a mysql select query?
Try this variant -
SET #sum = 0;
SELECT id, val FROM (
SELECT *, #sum:=#sum + val mysum FROM mytable2 ORDER BY id
) t
WHERE mysum <= 450;
+------+------+
| id | val |
+------+------+
| 1 | 100 |
| 2 | 300 |
| 3 | 50 |
+------+------+
This stored procedure might help:
DELIMITER ;;
CREATE PROCEDURE selectLimitBySum (IN m INT)
BEGIN
DECLARE mTmp INT DEFAULT 0;
DECLARE idTmp INT DEFAULT 0;
DECLARE valTmp INT DEFAULT 0;
DECLARE doneLoop SMALLINT DEFAULT 0;
DECLARE crsSelect CURSOR FOR SELECT id, val FROM test3;
DECLARE CONTINUE HANDLER FOR NOT FOUND SET doneLoop = 1;
OPEN crsSelect;
aloop: LOOP
SET idTmp = 0;
SET valTmp = 0;
FETCH crsSelect INTO idTmp, valTmp;
if doneLoop THEN
LEAVE aloop;
END IF;
SELECT idTmp, valTmp;
SET mTmp = mTmp + valTmp;
if mTmp > m THEN
LEAVE aloop;
END IF;
END LOOP;
CLOSE crsSelect;
END ;;
DELIMITER ;
Please feel free to change the table names or variable names as per your needs.
from mysql reference manual:
The LIMIT clause can be used to constrain the number of rows returned by the SELECT statement. LIMIT takes one or two numeric arguments, which must both be nonnegative integer constants (except when using prepared statements).
So you cannot use limit the way you proposed. To achieve what you want you need to use your application (java, c, php or whatever else), read the result set row by row, and stop when your condition is reached.
or you can use a prepared statement, but anyway you cant have conditional limit (it must be a constant value) and it is not exactly what you asked for.
create table #limit(
id int,
val int
)
declare #sum int, #id int, #val int, #m int;
set #sum=0;
set #m=250; --Value of an entry
declare limit_cursor cursor for
select id, val from your_table order by id
open limit_cursor
fetch next from limit_cursor into #id, #val
while(##fetch_status=0)
begin
if(#sum<#m)
begin
set #sum = #sum+#val;
INSERT INTO #limit values (#id, #val);
fetch next from limit_cursor into #id, #val
end
else
begin
goto case1;
end
end
case1:
close limit_cursor
deallocate limit_cursor
select * from #limit
truncate table #limit