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
Related
My data set
Tabel Name Users
unique_id uid
123487.1 1000
123488.1
123489.1
123490.1
As shown above this is my existing data and i want to add uid, so my data should be displayed as shown below.
unique_id uid
123487.1 1000
123488.1 1001
123489.1 1002
123490.1 1003
You don't need cursors for this. Just do an update:
select #u := max(user_id)
from users;
update users
set user_id = (#u := #u + 1)
where user_id is null
order by unique_id;
Providing that uid value is the only a single value in your data set, you can use that simple query:
select unique_id, first_value(uid) over(order by unique_id) + row_number() over(order by unique_id) - 1 fv from users;
https://dbfiddle.uk/?rdbms=mysql_8.0&fiddle=d8102c3ef394d304eefa9d42b5a479ba
Best regards.
You can create a procedure like this:
CREATE PROCEDURE uid_update()
BEGIN
DECLARE Done_c INT;
DECLARE v_min_id INT;
declare number_plus int;
declare v_cur int;
DECLARE curs CURSOR FOR
select ROW_NUMBER() OVER (order by unique_id) rn
from testTable
where uid is null;
DECLARE CONTINUE HANDLER FOR NOT FOUND SET Done_c = 1;
SELECT max(uid) INTO number_plus FROM testTable;
OPEN curs;
SET Done_c = 0;
REPEAT
FETCH curs INTO v_cur;
select min(unique_id) into v_min_id
from testTable
where uid is null;
update testTable
set uid = number_plus + v_cur
where uid is null
and unique_id = v_min_id ;
commit;
UNTIL Done_c END REPEAT;
CLOSE curs;
END
And then call that procedure like this:
call uid_update;
The values will then be updated as you asked for.
Here is the DEMO.
I have records example:
Orden| date_record
-----|-------------------
2334 | 2017-05-17 05:00:30
2334 | 2017-05-17 05:00:50
2334 | 2017-05-17 05:10:30
3421 | 2017-05-17 07:09:40
I need to delete records that have duplicate ids only where the difference in date_record is less than 30 seconds.
Thanks
I create a table for your data Table1 and include a litle more data.
I create a temporal table newTable
include a row_number
include a field to mark for deletion
Create a function to debug you dont need it
Create a function process_time to check for deletion
Create a cursor to loop for the table and mark each row when second diff
is < 30
If a row is already marked <> 0 doesn't count for the next row calculations
The final step is delete from original table using the newTable marked rows
SQL DEMO
CREATE PROCEDURE process_time()
BEGIN
DECLARE int_id INTEGER DEFAULT 0;
DECLARE int_prev_id INTEGER DEFAULT 0;
DECLARE dtt_date datetime;
DECLARE v_finished INTEGER DEFAULT 0;
DECLARE newTable_cursor CURSOR FOR
SELECT `ID`, `date_record`
FROM newTable
ORDER BY `ID`, `date_record`;
-- declare NOT FOUND handler
DECLARE CONTINUE HANDLER
FOR NOT FOUND SET v_finished = 1;
OPEN newTable_cursor;
get_dates: LOOP
FETCH newTable_cursor INTO int_id, dtt_date;
IF v_finished = 1 THEN
LEAVE get_dates;
END IF;
IF int_prev_id = int_id THEN
SELECT #dd := TIMESTAMPDIFF(SECOND, MAX(`date_record`), dtt_date) as d -- MAX(`date_record`)
FROM newTable
WHERE `ID` = int_id
AND `date_record` < dtt_date
AND delete_mark = 0;
ELSE
SET int_prev_id := int_id;
SET #dd := null;
END IF;
IF #dd < 30 THEN
UPDATE newTable
SET `delete_mark` = #dd
WHERE `ID` = int_id
and `date_record` = dtt_date;
END IF;
END LOOP get_dates;
CLOSE newTable_cursor;
END;
OUTPUT
Below given is my procedure takes too much time to execute.
BEGIN
DECLARE rank1 BIGINT DEFAULT 0;
DECLARE id1 BIGINT;
DECLARE rankskip BIGINT DEFAULT 0;
DECLARE mark DECIMAL(10,2) DEFAULT 0;
DECLARE oldmark DECIMAL(10,2) DEFAULT -100000;
DECLARE done int DEFAULT 0;
DECLARE cursor_i CURSOR FOR
SELECT
(rightmarks - negativemarks) as mark, id
FROM
testresult
WHERE
testid = testid1
ORDER BY
(rightmarks - negativemarks) DESC;
DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = 1;
OPEN cursor_i;
read_loop: LOOP
FETCH cursor_i INTO mark, id1;
IF done = 1 THEN
LEAVE read_loop;
END IF;
IF oldmark = mark THEN
BEGIN
IF IsRankSkip = 1 THEN
BEGIN
SET rankskip = rankskip + 1;
END;
END IF;
END;
ELSE
BEGIN
SET rank1 = rank1 + rankskip + 1;
SET rankskip = 0;
END;
END IF;
SET oldmark = mark;
UPDATE testresult SET rank = rank1 WHERE id=id1;
END LOOP;
CLOSE cursor_i;
END
This loop iterate minimum 2000 times.
Here IsRankSkip and testid1 is an argument passed to the procedure.
This procedure takes 65.343152046204 time to execute. If anybody guide me how can I reduce executing time?
Thank you in advance.
You can do this with a single update statement, making use of variables that change during the execution of it:
UPDATE testresult a
JOIN ( SELECT id,
#row := #row + 1 row_number,
#rank := if(mark = #lastmark, #rank, #row) as rank,
#dense_rank := #dense_rank + if(mark = #lastmark, 0, 1) as dense_rank,
#lastmark := mark as mark
FROM ( SELECT rightmarks - negativemarks as mark,
id
FROM testresult
WHERE testid = testid1
ORDER BY 1 DESC
) data,
(SELECT #row := 0, #dense_rank := 0) r
) b
ON a.id = b.id
SET a.rank = if(IsRankSkip, b.rank, b.dense_rank);
The query with alias b calculates the rank and adds it as a column in the result set. In fact, it adds three kinds of numbers:
#row: the sequential row number without special treatment of equal values
#rank: the same as row number of the value differs from the previous value, otherwise it is the same as in the previous row
#dense_rank: this increments when the value differs from the previous value, otherwise it is the same as in the previous row
You can choose which one to update your rank column with. In the above SQL I have used the two procedure variables IsRankSkip and testid1.
Remark
If you are always calling your procedure for all testid values, then the above can be further improved, so all these updates are done with only one update statement.
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 ;
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 ;