Arithmetic operation in MySQL through Procedure - mysql

I have a table t1 which have a column Marks in this column values are 10, 20, 30, 40.
Now I want to use a procedure to get this result:
Marks Total_Marks
10 10
20 30
30 60
40 100
DELIMITER //
CREATE PROCEDURE Total_Marks ( In Num Int(4) )
Begin
Declare Mark Int(4);
Declare Add_M Int(4);
DECLARE NO_MORE_ROWS BOOLEAN;
DECLARE DataCursor CURSOR FOR SELECT Marks
FROM t1 where marks = Num;
DECLARE DataCursor1 CURSOR FOR SELECT Sum(Marks) FROM t1;
OPEN DataCursor;
FETCH DataCursor INTO Mark;
CLOSE DataCursor;
OPEN DataCursor1;
READ_LOOP1: LOOP
FETCH DataCursor1 INTO Add_M;
IF NO_MORE_ROWS THEN
LEAVE READ_LOOP1;
END IF;
BEGIN
SET Add_M = SUM(Mark);
END;
END LOOP READ_LOOP1;
CLOSE dataCursor1;
SET NO_MORE_ROWS = FALSE;
end //
DELIMITER ;

You don't need a procedure at all, let alone cursors. But what you need, is a column that defines the order of the rows as mentioned in the comments.
create table foo (id int auto_increment primary key, bar int);
insert into foo(bar) values (10), (20), (30), (40);
In this example I introduced the column id for that matter. Or you can of course just order by your marks or whatever suits your needs.
select
bar
, #total := #total + bar as my_total
from
foo
, (select #total := 0) var_init
order by id
see it working live in this sqlfiddle
As explanation, with this cross joined query
, (select #total := 0) var_init
we initialize our variable holding the running total #total. It's the same as writing
set #total = 0;
select
bar
, #total := #total + bar as my_total
from
foo
order by id;
The rest is self explaining I guess.
You can read more about these type of variables here.
UPDATE (for completeness):
Here are two other possibilities how to solve it without variables. Although I like variables usually better, cause in this one
select
t1.bar
, sum(t2.bar)
from
foo t1
inner join foo t2 on t1.id >= t2.id
group by t1.id;
you end up with a potentially huge temporary table, since you join every row to all previous rows and then calculate the sum.
And in this solution
select
bar
, (select sum(bar) from foo sf where sf.id <= foo.id) as my_total
from
foo;
you have a dependent subquery executed for each row. This is even worse than the previous solution.
I posted those just for completeness and if you really can't use variables (because of creating a view for example).

Related

SQL Cursor to determine median value

I am trying to write a Stored Procedure to retrieve the median salary from a table and am having trouble figuring out how to retrieve the data from the cursor.
Currently my code is:
DELIMITER //
CREATE PROCEDURE MedianSalary(OUT median INT)
BEGIN
DECLARE counter int(5) DEFAULT 0;
DECLARE set_size int(5) DEFAULT (SELECT count(*) from employee);
DECLARE median_index int(5) DEFAULT (SELECT floor(count/2));
DECLARE all_salaries CURSOR
FOR SELECT salary from employee,
OPEN all_salaries;
WHILE #counter != #median_index
BEGIN
SET #counter = #counter + 1,
FETCH NEXT from all_salaries,
END;
FETCH all_salaries INTO median;
CLOSE all_salaries;
END //
DELIMITER ;
I can't seem to find any documentation similar to what I am trying to achieve, any help would be greatly appreciated.
I don't have an answer to your stored procedure problem, but note that we can actually find the median from a table in MySQL fairly easily using session variables to simulate the row number:
SET #row_number = 0;
SET #row_count = (SELECT COUNT(*) FROM yourtable);
SELECT AVG(salary) AS median
FROM
(
SELECT (#row_number:=#row_number + 1) AS rn, salary
FROM yourTable
ORDER BY salary
) t
WHERE
(#row_count % 2 = 0 AND rn IN (#row_count / 2, (#row_count / 2) + 1) OR
#row_count % 2 <> 0 AND rn = #row_count / 2);
Demo
Note the ugliness in the WHERE clause has to do with the edge case of your table having an even number of records. In this case, there technically is not a single median record, so instead I report the mean of the two records which sit about the median on either side.

Nested while loop taking more time than nested cursor

I am inserting n number of rows after fetching data from two sql statement. I have used two ways so far first one is Cursor and other is While loop.
Nested Cursor:
begin
declare userId,taskId int default 0;
declare userCnt int default 0;
declare c1 cursor for select us_id from us_uxusermaster ;
declare continue handler for not found set userCnt=1;
open c1;
CheckId: loop
fetch c1 into userId;
if userCnt=1
then
leave CheckId;
end if;
Select pl.pl_minTarget into target from pl_planlist pl inner join ap_affiliateplan ap inner join us_uxusermaster us on Find_in_set(pl.pl_id,us.us_planListId) and ap.ap_id =us.us_taskPlanId where us_id=userId and pl.pl_serviceName=2;
Begin
DECLARE taskId int default 0;
Declare taskCnt int default 0;
Declare t1 cursor for select tk.tk_id from tk_taskmaster tk where tk.tk_activeTime=AddDate(Current_date(),1) and tk_actStatus=0 and tk_status=1 limit target;
Declare continue handler for not found set taskCnt=1;
open t1;
CheckTask: loop
fetch t1 into taskId;
if taskCnt=1
then
leave CheckTask;
end if;
insert into ut_userstask(ut_tk_id,ut_us_id,ut_edtm,ut_eby) values (taskId,userId,current_timestamp,'Via-Event');
end loop checkTask;
close t1;
End;
end loop CheckId;
close c1;
end;
While Loop:
begin
declare taskName,taskCode,description,url,userLevel,TaskStatus,TaskActStatus,Steps,taskId,userId varchar(50);
declare activationTime,deActivationTime datetime;
Declare flag,flag2,counts,counts2 int default 0;
Drop Temporary Table if exists temptrigg;
Set #rownumber=0;
Set #rownumber2=0;
create temporary table temptrigg as(select * from (select (#rownumber := #rownumber + 1) AS newrow, us_id from us_uxusermaster) AS xst);
select count(*) into counts from temptrigg;
while(flag<counts)
Do
Set flag=flag+1;
Select us_id into userId from temptrigg where newrow=flag;
Drop Temporary Table if exists temptrigg2;
Create temporary table temptrigg2 as(select * from(select (#rownumber2 := #rownumber2 + 1) as newrow2,tk.tk_id from tk_taskmaster tk where tk.tk_activeTime=Current_date() and tk_actStatus=0 and tk_status=1)as xst);
Select count(*) into Counts2 from temptrigg2;
While(flag2<Counts2)
Do
Set flag2=flag2+1;
Select tk_id into taskId from temptrigg2 where newrow2=flag2;
insert into ut_userstask(ut_tk_id,ut_us_id,ut_edtm,ut_eby) values (taskId,userId,current_timestamp,'Via-Event');
End While;
End While;
end
Here the problem is that the while loop is taking double time than the cursor. I am confused about its future results. will it be good to follow the cursor by replacing nested while loop.
While inserting 425 rows cursor taking 23.05 sec and while loop is taking 46 secs. both the timing are too much for me. Is there any other way to increase performance.
Will be glad to know if any.
I'm not sure if I caught every check you have in there (especially the limit), but it would save a lot if you could squeeze it into a single insert..select like this:
Insert into ut_userstask(ut_tk_id,ut_us_id,ut_edtm,ut_eby)
Select
tk.tk_id,
us.us_id,
current_timestamp,
'Via-Event'
from pl_planlist pl
inner join ap_affiliateplan ap
inner join us_uxusermaster us on ap.ap_id = us.us_taskPlanId
inner join tk_taskmaster tk on tk.tk_activeTime=AddDate(Current_date(),1) and tk_actStatus=0 and tk_status=1
where
pl.pl_serviceName=2
and Find_in_set(pl.pl_id,us.us_planListId)
Other stuff to keep in mind: Make sure you have proper indexes and try to avoid functions like FIND_IN_SET. It is generally a sign that your database is not normalized enough, and it's very slow to use, since it bypasses any indexes available on the column.
Even if you can't put everthing in one select, it's probably still faster to loop through a main cursor (for instance to get the users), and perform an insert..select for each of the rows of the cursor.

SQL Server T-SQL breaking a string into a temp table for a join

We have a SQL Server Scalar Function and part of the process is to take one of the input values and do the following
'inputvalue'
Create a table variable and populate with the following rows
inputvalue
inputvalu
inputval
inputva
inputv
input
inpu
inp
Then this table is joined to a query, ordered by len of the inputvalue desc and returns the top 1. The actual code is here
DECLARE #Result NVARCHAR(20);
DECLARE #tempDialCodes TABLE (tempDialCode NVARCHAR(20));
DECLARE #counter INT = LEN(#PhoneNumber);
WHILE #counter > 2
BEGIN
INSERT INTO #tempDialCodes(tempDialCode) VALUES(#PhoneNumber);
SET #PhoneNumber = SUBSTRING(#PhoneNumber, 1, #counter - 1);
SET #counter = #counter - 1;
END
SET #Result = (SELECT TOP 1 [DialCodeID]
FROM DialCodes dc JOIN #tempDialCodes s
ON dc.DialCode = s.tempDialCode
ORDER BY LEN(DialCode) DESC);
RETURN #Result
It works fine but I am asking if there is a way to replace the while loop and somehow joining to the inputvalue to get the same result. When I say it works fine, it's too dam slow but it does work.
I'm stumped on how to break up this string without using a loop and to a table variable but my warning light tells me this is not efficient for running against a table with a million rows.
Are you familiar with tally tables? The speed difference can be incredible. I try to replace every loop with a tally table if possible. The only time I haven't been able to so far is when calling a proc from within a cursor. If using this solution I would recommend a permanent dbo.Tally table with a sufficiently large size rather than recreating every time in the function. You will find other uses for it!
declare #PhoneNumber nvarchar(20) = 'inputvalue';
declare #tempDialCodes table (tempDialCode nvarchar(20));
--create and populate tally table if you don't already a permanent one
--arbitrary 1000 rows for demo...you should figure out if that is enough
--this a 1-based tally table - you will need to tweak if you make it 0-based
declare #Tally table (N int primary key);
insert #Tally
select top (1000) row_number() over (order by o1.object_id) from sys.columns o1, sys.columns o2 order by 1;
--select * from #Tally order by N;
insert #tempDialCodes
select substring(#PhoneNumber, 1, t.N)
from #Tally t
where t.N between 3 and len(#PhoneNumber)
order by t.N desc;
select *
from #tempDialCodes
order by len(tempDialCode) desc;

MySQL stored procedure pass select as parameter

could you please give me an advice how to CALL prcd with SELECT results? Or advice me pls better solution.. I am open minded to all working solution
I have a procedure to control inserting data ...
CREATE PROCEDURE control_insert (
)
And I need to pass data from SELECT results to procedure ...
SELECT t.c1, t.c2
FROM table t1
LEFT JOIN other_table t2
ON t1.id = t2.id
WHERE 1=1
The point is, I need to get some data via SELECT (around 6 tables joined to the base table) and I need to do control for each row before insert.. each row should meet some conditions .. if it doesn't meet them, it should just skip it and process next one ...
The procedure should look like:
CREATE PROCEDURE control_insert (
IN v_c1 INT,
IN v_c2 INT
)
BEGIN
IF v_c1 > 1 THEN
INSERT INTO controlled_table (id, type) VALUES (v_c1, v_c2);
ELSE
/* do nothing */
END IF;
END;
CALL control_insert ( SELECT .... );
Could you help me with that? Is there any possibility to do this via MySQL? I can write a PERL skript, but I want to avoid this type of solution ... I just one to do it only in MySQL way
Thank you
EDIT1: I need to check if ID of the SELECT result and LABEL is already in this table for specific date ... this code above is only an example to demonstrate the situation
SOLUTION
I've found the solution ... so for the other visitors:
calling procedure:
CALL controlInsert();
procedure body:
CREATE PROCEDURE controlInsert()
BEGIN
DECLARE done INT DEFAULT FALSE;
DECLARE v_id INT;
DECLARE v_id_dupl INT;
DECLARE v_label INT;
DECLARE v_date DATE;
DECLARE v_type VARCHAR(100);
DECLARE v_category VARCHAR(255);
DECLARE v_user VARCHAR(255);
DECLARE v_country VARCHAR(255);
DECLARE c1 CURSOR FOR SELECT id, label, date, type, category, user, country FROM t1 LEFT JOIN ... /* whole select with 6 joins ended by ; */
DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = TRUE;
## open cursor
OPEN c1;
## loop through the cursor
read_loop: LOOP
## fetch cursor into variables
FETCH c1 INTO v_id , v_label, v_date, v_type, v_category, v_user, v_country;
## check if there is any record
IF done THEN
LEAVE read_loop;
END IF;
## get count of existing records
SELECT count(*) INTO v_id_dupl
FROM
WHERE 1=1
AND id = v_id
AND label= v_label
AND date = v_date;
## if v_id_dupl = 0 => no rows found (ok to load)
IF (v_id_dupl = 0) THEN
INSERT INTO target_table (id, label, date, type, category, user, country)
VALUES (v_id , v_label, v_date, v_type, v_category, v_user, v_country);
END IF;
END LOOP;
CLOSE c1;
END
If that is all your stored procedure is doing, then you don't actually need it. You can do the whole thing in a single statement:
INSERT INTO controlled_table (id, type)
SELECT t.c1, t.c2
FROM table t1
LEFT JOIN other_table t2 ON t1.id = t2.id
WHERE something = somethingElse
AND t.c1 > 1
Essentially, I've just combined your original query with the INSERT statement in your procedure.
If your procedure is more complex and needs to do multiple operations on each row, then you should look into using a cursor.

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