1.what exactly does this code do?
2.what is number(2)?
cursor c1 is
select employee_id, department_id, commission_pct from hr.employees;
emprec c1%rowtype;
v_hike number(2);
begin
open c1;
loop
fetch c1 into emprec;
exit when c1%notfound;
if emprec.department_id = 40 then v_hike := 10;
elsif emprec.department_id = 70 then v_hike := 15;
elsif emprec.commission_pct > 0.30 then v_hike := 5;
else v_hike := 10;
end if;
update employees set salary = salary + salary * v_hike/100
where employee_id = emprec.employee_id;
end loop;
end;
It's oracle cursor definition.. What exactly is that piece of sql doing is so hard to know how hard you can read it.
In first row you can see select which works with many set of rows.. Then you have define local variable. v_hike. In loop it's doing some expresions like count v_hike and then it's set new salary to employer
number(2) is incorrect write, it should be: number(p,s) where p-s digits before the decimal and s digits after the decimal..
what exactly does this code do?
First of all, in Oracle PL/SQL, it is syntactically incorrect since you have missed the DECLARE keyword in your PL/SQL anonymous block. You will get a compilation error.
Let me explain step by step :
cursor c1 is
select employee_id, department_id, commission_pct from hr.employees;
emprec c1%rowtype;
It is an explicit cursor, it is a work area to store the processing information of the select query. And emprec is a collection type which will hold the resultset when you issue the FETCH statement.
v_hike number(2);
This is a variable declared of NUMBER data type. NUMBER(2) means it can hold a number value with PRECISION 2.
begin
open c1;
loop
fetch c1 into emprec;
exit when c1%notfound;
if emprec.department_id = 40 then v_hike := 10;
elsif emprec.department_id = 70 then v_hike := 15;
elsif emprec.commission_pct > 0.30 then v_hike := 5;
else v_hike := 10;
end if;
update employees set salary = salary + salary * v_hike/100
where employee_id = emprec.employee_id;
end loop;
end;
Next steps are,
a. Open the cursor
b. for each row in the cursor, loop through and prepare the
collection in emprec
c. When there are no records to fetch from cursor then exit
d. Assign a number value to the v_hike variable based on the IF-ELSE
construct.
e. execute the UPDATE statement.
f. Come out of loop.
g. Scope of block ends with END keyword.
Related
In the code below, I'm trying go through the results of endDateTable row by row, comparing the current row's endDate to the previous row's endDate. If there has been any change since the previous, we increment #revisionNum. However, upon populating the new table, all of the #revisionNum entries are 0. What am I doing wrong?
NOTE: I'm using prepared statements in this manner since doing a straightforward SELECT into a variable gives a syntax error due to the LIMIT clause not allowing a variable in our version of MySQL.
BEGIN
DECLARE _currentEndDate DATETIME DEFAULT now();
DECLARE _priorEndDate DATETIME DEFAULT now();
SET #ResultsCount = (SELECT COUNT(*) FROM mainTable);
SET #j = 0;
WHILE #j < #ResultsCount DO
SET #revisionNum = 0;
/*CURRENT END DATE*/
SET #appResultQueryCurrent = CONCAT('
SELECT
end_date
INTO _currentEndDate
FROM endDateTable
LIMIT ', #j, ', 1'
);
PREPARE currentQueryStmt FROM #appResultQueryCurrent;
EXECUTE currentQueryStmt;
/*PREVIOUS END DATE*/
SET #appResultQueryPrior = CONCAT('
SELECT
end_date
INTO _priorAppEndDate
FROM endDateTable
LIMIT ', IF(#j = 0, 0, #j - 1), ', 1'
);
PREPARE priorQueryStmt FROM #appResultQueryPrior;
EXECUTE priorQueryStmt;
SET #revisionNum = IF(
#j = 0 OR (_currentEndDate = _priorEndDate),
#revisionNum,
IF(
_currentEndDate != _priorEndDate,
#revisionNum + 1,
#revisionNum
)
);
INSERT INTO finalTable (RevisionNum)
SELECT
#revisionNum AS RevisionNum
FROM endDateTable;
SET #j = #j +1;
END WHILE;
END $$
You don't need a loop, you can use INSERT INTO ... SELECT ..., incrementing the variable in the select query.
You also need an ORDER BY criteria to specify how to order the rows when comparing one row to the previous row.
INSERT INTO finalTable (RevisionNum, otherColumn)
SELECT revision, otherColumn
FROM (
SELECT IF(end_date = #prev_end_date, #revision, #revision := #revision + 1) AS revision,
#prev_end_date := end_date,
otherColumn
FROM endDateTable
CROSS JOIN (SELECT #prev_end_date := NULL, #revision := -1) AS vars
ORDER BY id) AS x
DEMO
The offset value in the LIMIT clause is tenuous without an ORDER BY.
Without an ORDER BY clause, MySQL is free to return results in any sequence.
There is no guarantee that LIMIT 41,1 will return the row before LIMIT 42,1, or that it won't return the exact same row as LIMIT 13,1 did.
(A table in a relational database represents an unordered set of tuples, there is no guaranteed "order" or rows in a table.)
But just adding ORDER BY to the queries isn't enough to fix the Rube-Goldberg-esque rigmarole.
In the code shown, it looks like each time through the loop, we're inserting a copy of endDateTable into finalTable. If that's 1,000 rows in endDateTable, we're going to get 1,000,000 rows (1,000 x 1,000) inserted into finalTable. Not at all clear why we need so many copies.
Given the code shown, it's not clear what the objective is. Looks like we are conditionally incrementing revisionNum, the end result of which is the highest revision num. Just guessing here.
If there is some kind of requirement to do this in a LOOP construct, within a procedure, I'd think we'd do a cursor loop. And we can use procedure variables vs user-defined variables.
Something along these lines:
BEGIN
DECLARE ld_current_end_date DATETIME;
DECLARE ld_prior_end_date DATETIME;
DECLARE li_done INT;
DECLARE li_revision_num INT;
DECLARE lcsr_end_date CURSOR FOR SELECT t.end_date FROM `endDateTable` t ORDER BY NULL;
DECLARE CONTINUE HANDLER FOR NOT FOUND SET li_done = TRUE;
SET li_done = FALSE;
SET li_revision_num = 0;
OPEN lcsr_end_date;
FETCH lcsr_end_date INTO ld_current_end_date;
SET ld_prior_end_date = ld_current_end_date;
WHILE NOT li_done DO
SET li_revision_num = li_revision_num + IF( ld_current_end_date <=> ld_prior_end_date ,0,1);
SET ld_prior_end_date := ld_current_end_date;
FETCH lcsr_end_date INTO ld_current_end_date;
END WHILE;
CLOSE lcsr_end_date;
INSERT INTO `finalTable` (revisionnum) VALUES (li_revision_num);
END $$
Note the "order by" clause on the SELECT, its not clear what the rows should be ordered on, so we're using a literal as a placeholder.
As the end result, we insert a single row into finalTable.
Again, it's not clear what the code in the question is supposed to achieve, but doing a cursor loop across ordered rows would be much more efficient than a bazillion dynamic SQL executions fetching individual rows.
I am getting an error in the following code below. The error is #1329 - No data - zero rows fetched, selected, or processed. What exactly does this mean and what is it that i am doing incorrectly? Thanks
create or replace procedure grade()
begin
declare no,s1,s2,s3,cnt,i,per int(20);
declare c1 cursor for select roll,sub1,sub2,sub3 from student;
set i=0;
select count(*) into cnt from student;
open c1;
while i<=cnt do
fetch c1 into no,s1,s2,s3;
set per=s1+s2+s3/3;
if per>=90 then
update student set grade='A+' where roll=no;
elseif (per<90 and per>=80)then
update student set grade='A' where roll=no;
elseif (per<80 and per>=70)then
update student set grade='B+' where roll=no;
elseif (per<70 and per>=60)then
update student set grade='B' where roll=no;
elseif (per<60 and per>=50)then
update student set grade='C+' where roll=no;
elseif (per<50 and per>=40)then
update student set grade='C' where roll=no;
else
update student set grade='FAIL' where roll=no;
end if;
set i=i+1;
end while;
close c1;
end$$
This logic:
set i = 0;
select count(*) into cnt from student;
while i <= cnt do
is going through the loop 1 extra time. It is on the last trip through the loop that you are getting the error.
If cnt is 3, for instance, then the loops are:
0
1
2
3
You either want:
set i = 1
or alternatively
while i < cnt
In SQL, I would use the first method, because things usually start counting at 1 rather than 0 in SQL -- but they are equivalent.
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.
can anybody help me with my sorting function - seriously I don't know how can I make it work as supposed to. :( Database is in MariaDB in Xampp. I use phpMyAdmin to execute the query.
DELIMITER $$
DROP FUNCTION IF EXISTS convRomanNumeral$$
CREATE FUNCTION convRomanNumeral (numeral CHAR(4))
RETURNS INT
BEGIN
DECLARE intnum INT;
CASE numeral
WHEN "I" THEN intnum = 1;
WHEN "II" THEN intnum = 2;
END CASE;
RETURN intnum;
END;
$$
SET #iteration = -1;
UPDATE `st0gk_docman_documents`
SET created_on = DATE('2016-06-14') + INTERVAL(#iteration := #iteration + 1) SECOND
WHERE `docman_category_id` = 141 ORDER BY convRomanNumeral(SUBSTRING(SUBSTRING_INDEX(title,'/',1),' ',-2) ASC, SUBSTRING_INDEX(title,'/',-2)+0 ASC;
So what I want to achieve is to sort documents by title. Example titles are:
Document Nr I/36/2006
Document Nr II/36/2006
Document Nr I/32/2006
Document Nr II/19/2006
After sorting them by first Roman number and then by second Arabic number I want to update the date. Code below for updating by only second Arabic number works properly:
SET #iteration = -1;
UPDATE `st0gk_docman_documents`
SET created_on = DATE('2016-06-14') + INTERVAL(#iteration := #iteration + 1) SECOND
WHERE `docman_category_id` = 141 ORDER BY SUBSTRING_INDEX(title,'/',-2)+0 ASC;
I would like to use CASE to return proper variable for Roman values. I know it's not perfect but I can't even make the CASE and FUNCTION work. What I am doing wrong? All suggestions are welcome.
The best way to do this is to add another column that has a sortable equivalent of that string. And use non-SQL code to do the parsing and building of that column before inserting into the table.
First mistake that I was making it was trying to execute the whole query at once... After taking the first lodge out of the way the debugging seemed way simpler. :D
So I created my case function to convert Roman numerals:
DELIMITER $$
DROP FUNCTION IF EXISTS convRomanNumeralSubFunction$$
CREATE FUNCTION convRomanNumeralSubFunction (numeral CHAR(1))
RETURNS INT
BEGIN
DECLARE intnum INT;
CASE numeral
WHEN "I" THEN SELECT 1 INTO intnum;
WHEN "X" THEN SELECT 10 INTO intnum;
WHEN "C" THEN SELECT 100 INTO intnum;
WHEN "M" THEN SELECT 1000 INTO intnum;
WHEN "V" THEN SELECT 5 INTO intnum;
WHEN "L" THEN SELECT 50 INTO intnum;
WHEN "D" THEN SELECT 500 INTO intnum;
END CASE;
RETURN intnum;
END;
$$
After that I declared the second function needed for conversion. I don't know if You can declare function inside function... and I didn't want to waste more time on this. For sure You can declare Function inside Procedure. Anyhow. WARNING: This function is not proof of BAD numerals like IIX. Numerals like that or will be badly counted. Also AXI will not count.
DELIMITER $$
DROP FUNCTION IF EXISTS convRomanNumeral$$
CREATE FUNCTION convRomanNumeral (numeral CHAR(10))
RETURNS INT
BEGIN
DECLARE currentintnum, previntnum, intnum, counter, numerallength INT;
SET numerallength = LENGTH(numeral);
SET counter = numerallength;
SET intnum = 0;
SET previntnum = 0;
WHILE counter > 0 DO
SET currentintnum = CAST(convRomanNumeralSubFunction(SUBSTRING(numeral,counter, 1)) as integer);
IF currentintnum < previntnum THEN
SET intnum = intnum - currentintnum;
ELSE
SET intnum = intnum + currentintnum;
END IF;
SET previntnum = currentintnum;
SET counter = counter - 1;
END WHILE;
RETURN intnum;
END;
$$
So that's it. Now You can convert all kind of Roman numerals and sort them up.
Use this to test the conversion:
SELECT convRomanNumeral("XIX");
This is example sorting code that I in the end used:
SET #iteration = -1;
UPDATE `st0gk_docman_documents`
SET created_on = DATE('2016-06-07') + INTERVAL(#iteration := #iteration + 1) SECOND
WHERE `docman_category_id` = 67 ORDER BY convRomanNumeralBreak(SUBSTRING_INDEX(SUBSTRING_INDEX(title,'/',1),' ',-1)) ASC, SUBSTRING_INDEX(title,'/',-2)+0 ASC;
Also one more thing - if You'll try to excecute this on mySQL then You have to fix this line:
SET currentintnum = CAST(convRomanNumeralSubFunction(SUBSTRING(numeral,counter, 1)) as integer);
into this:
SET currentintnum = CAST(convRomanNumeralSubFunction(SUBSTRING(numeral,counter, 1)) as SIGNED);
This code could be improved but as the #Rick James stated this should be done differently - not in as db update but in different table structure and sorting mechanism.
I've got a Stored Procedure that checks rows from one table to insert its details into another. I'm using a cursor but I have a big problem: the cursor loops 2 times over the same row. So I get 2 repeated inserts .
Here is the sp code:
IF (SELECT 1 FROM NOVEDADES WHERE LEGAJO_ID = pLEGAJO_ID AND FECHA >= pFECHA AND CONCEPTO_ID != 11 AND CONCEPTO_ID != 13 AND CONCEPTO_ID != 12 LIMIT 1) = 1
THEN
BEGIN
DECLARE vCONCEPTO_ID INT;
DECLARE vMONTO DECIMAL(12,2);
DECLARE vID INT;
DECLARE vDONE INT DEFAULT 0;
DECLARE CURSOR_NOVEDADES CURSOR FOR
SELECT ID
FROM NOVEDADES
WHERE LEGAJO_ID = pLEGAJO_ID
AND FECHA >= pFECHA
AND CONCEPTO_ID != 11
AND CONCEPTO_ID != 13
AND CONCEPTO_ID != 12;
DECLARE CONTINUE HANDLER FOR NOT FOUND SET vDONE=1;
OPEN CURSOR_NOVEDADES;
SET vDONE = 0;
REPEAT
FETCH CURSOR_NOVEDADES INTO vID;
SELECT CONCEPTO_ID, MONTO INTO vCONCEPTO_ID, vMONTO
FROM NOVEDADES WHERE ID = vID;
INSERT INTO LIQUIDACIONES_DETALLE (LIQUIDACION_ID, CONCEPTO_ID, MONTO)
VALUES(pLIQUIDACION_ID, vCONCEPTO_ID, vMONTO);
UNTIL vDONE END REPEAT;
CLOSE CURSOR_NOVEDADES;
END;
END IF;
variables beggining with "p" are IN parameters, with "v" are common variables.
I must say that the query of the cursor returns only 1 value.
I've tried with LOOP also, but same result.
I've tried "debugging" the procedure inserting some SELECTS and I see the repeated.
Thanks a lot.
On the last iteration through the loop, the fetch is failing. When it does so, you are re-inserting the previous values. Here is one way to fix this:
REPEAT
FETCH CURSOR_NOVEDADES INTO vID;
if ! vdone then
SELECT CONCEPTO_ID, MONTO INTO vCONCEPTO_ID, vMONTO
FROM NOVEDADES WHERE ID = vID;
INSERT INTO LIQUIDACIONES_DETALLE (LIQUIDACION_ID, CONCEPTO_ID, MONTO)
VALUES(pLIQUIDACION_ID, vCONCEPTO_ID, vMONTO);
end
UNTIL vDONE END REPEAT;