Cursors in MySQL Stored Procedures
https://www.sitepoint.com/cursors-mysql-stored-procedures/
After my previous article on Stored Procedures was published on SitePoint, I received quite a number of comments. One of them suggested further elaboration on CURSOR, an important feature in Stored Procedures.
As cursors are a part of a Stored Procedure, we will elaborate a bit more on SP in this article as well. In particular, we will see how to return a dataset from an SP.
What is a CURSOR?
A cursor can’t be used by itself in MySQL. It is an essential component in stored procedures. I would be inclined to treat a cursor as a “pointer” in C/C++, or an iterator in PHP’s foreach statement.
With cursors, we can traverse a dataset and manipulate each record to accomplish certain tasks. When such an operation on a record can also be done in the PHP layer, it saves data transfer amounts as we can just return the processed aggregation/statistical result back to the PHP layer (thus eliminating the select – foreach – manipulation process at the client side).
Since a cursor is implemented in a stored procedure, it has all the benefits (and limitations) of an SP (access control, pre-compiled, hard to debug, etc).
The official documentation on cursors is located here. It contains only four commands that are related to cursor declaration, opening, closing, and fetching. As mentioned above, we will also touch on some other stored procedure statements. Let’s get started.
A real world question
My personal website has a page showing the scores of my favorite NBA team: LA Lakers. The table structure behind it is straightforward:
Fig 1. The Lakers matches status table structure
I have been updating this table since 2008. Some of the latest records showing Lakers’ 2013-14 season are shown below:
Fig 2. The Lakers matches status table data (partial) for 2013-2014 season
(I am using MySQL Workbench as the GUI tool to manage my MySQL databases. You can use your favorite tool.)
Well, I have to admit Lakers are not playing very well these days. 6 consecutive losses up to Jan 15th. I get this “6 consecutive losses” by manually counting from the last played match all the way up (towards earlier games) and see how long an “L” (meaning a loss) in winlose column can appear. This is certainly doable but if the requirement becomes more complicated in a larger table, it takes more time and is more error prone.
Can we do this with a single SQL statement? I am not an SQL expert and I haven’t been able to figure out how to achieve the desired result (“6 consecutive losses”) from one SQL statement. The input of gurus will be highly appreciated – leave it in the comments below.
Can we do this in PHP? Yes, of course. We can retrieve the game data (particularly, the winlose column) for current season and do a traverse on the records to calculate the current longest win/lose streak. But to do that, we will have to grab all data for that year and most of the data will be wasted (as it is not likely for a team to have a win/lose streak for more than 20+ games in a 82-game regular season). However, we don’t know how many records should be retrieved into PHP to determine the streak, so this waste is a must. And finally, if the current win/lose streak is the only thing we want to know from that table, why pull all the raw data?
Can we do this via other means? Yes, it is possible. For example, we can create a redundant table specifically designed to store the current win/lose streak. Every insertion of the record will update that table too. But this is way too cumbersome and too error prone.
So, what is a better way to achieve this result?
Using Cursor in a Stored Procedure
As the name of this article suggests, we will see a better alternative (in my view) to solve this problem: using cursor in a Stored Procedure.
Let’s create the first SP in MySQL Workbench as follows:
DELIMITER $$
CREATE DEFINER=`root`@`localhost` PROCEDURE `streak`(in cur_year int, out longeststreak int, out status char(1))
BEGIN
declare current_win char(1);
declare current_streak int;
declare current_status char (1);
declare cur cursor for select winlose from lakers where year=cur_year and winlose<>'' order by id desc;
set current_streak=0;
open cur;
fetch cur into current_win;
set current_streak = current_streak +1;
start_loop: loop
fetch cur into current_status;
if current_status <> current_win then
leave start_loop;
else
set current_streak=current_streak+1;
end if;
end loop;
close cur;
select current_streak into longeststreak;
select current_win into `status`;
END
In this SP, we have one input parameter and two output parameters. This defines the signature of the SP.
In the SP body, we also declared a few local variables to hold the streak status (win or lose, current_win), current streak and current win/lose status for a particular match.
declare cur cursor for select winlose from lakers where year=cur_year and winlose<>'' order by id desc;
The above line is the cursor declaration. We declared a cursor named cur and the dataset bind to that cursor is the win/lose status for those matches played (thus its winlose column is either “W” or “L” instead of nothing) in a particular year ordered by id (the latest played games will have the highest ID) descending.
Though not displayed explicitly, we can imagine that this dataset will contain a series of “L”s and “W”s. Based on the data shown in Figure 2 above, it should be: “LLLLLLWLL…” (6 Ls, 1 Ws, etc).
To calculate the win/lose streak, we begin with the latest (and the first in the dataset) match data. When a cursor is opened, it always starts at the first record in the associated dataset.
After the first data is grabbed, the cursor will move to the next record. In this way, a cursor behaves very much like a queue, traversing the dataset in a FIFO (First In First Out) manner. This is exactly what we wanted.
After getting the current win/lose status and set the streak number, we continue to loop through (traverse) the remainder of the dataset. With each loop iteration, the cursor will “point” to the next record until we break the loop or all the records are consumed.
If the next win/lose status is the same as the current win/lose status, it means the streak goes on and we increase the streak number by 1 and continue the traversing; otherwise, it means the streak discontinues and we can leave the loop earlier.
Finally, we close the cursor and release the resources. Then we return the desired output.
Next, we can enhance the access control of the SP as described in my previous article.
To test the output of this SP, we will write a short PHP script:
<?php
$dbms = 'mysql';
$host = 'localhost';
$db = 'sitepoint';
$user = 'root';
$pass = 'your_pass_here';
$dsn = "$dbms:host=$host;dbname=$db";
$cn=new PDO($dsn, $user, $pass);
$cn->exec('call streak(2013, @longeststreak, @status)');
$res=$cn->query('select @longeststreak, @status')->fetchAll();
var_dump($res); //Dump the output here to get a raw view of the output
$win=$res[0]['@status']='L'?'Loss':'Win';
$streak=$res[0]['@longeststreak'];
echo "Lakers is now $streak consecutive $win.\n";
This will output something like the following figure:
(This output is based on Lakers’ match up to Jan 15th, 2014.)
Return a dataset from a Stored Procedure
A few discussions went along on how to return a dataset from an SP, which constructs the dataset out of the results from a few repeated calls to another SP.
A user may want to know more from our previously created SP that only returns a win/lose streak for one year; thus we can get a table showing the win/lose streaks for all the years in a form like:
| YEAR | Win/Lose | Streak |
|---|---|---|
| 2013 | L | 6 |
| 2012 | L | 4 |
| 2011 | L | 2 |
(Well, a more useful result can be to return the longest win streak and loss streak in a particular season. This requirement can be easily expanded from the previous SP so I will leave it to interested parties to implement. For the purpose of this article, we will stick to the current win/loss streak.)
MySQL SP can only return scalar results (an integer, a string, etc), unless the result is returned by a select ... from ... statement (and it becomes a dataset). The issue here is that the table-form data we want to see does not exist in our current db structure and is constructed from another SP.
To tackle this, we need the help of a temporary table, or if situation allows and requires, a redundant table. Let’s see how we can achieve our target via a temporary table.
First, we will create a second SP as shown below:
DELIMITER $$
CREATE DEFINER=`root`@`%` PROCEDURE `yearly_streak`()
begin
declare cur_year, max_year, min_year int;
select max(year), min(year) from lakers into max_year, min_year;
DROP TEMPORARY TABLE IF EXISTS yearly_streak;
CREATE TEMPORARY TABLE yearly_streak (season int, streak int, win char(1));
set cur_year=max_year;
year_loop: loop
if cur_year<min_year then
leave year_loop;
end if;
call streak(cur_year, @l, @s);
insert into yearly_streak values (cur_year, @l, @s);
set cur_year=cur_year-1;
end loop;
select * from yearly_streak;
DROP TEMPORARY TABLE IF EXISTS yearly_streak;
END
A few key things to notice here:
- We determine the max year and min year by selecting from the table
lakers; - We created a temp table to hold the output, with the structure requested by the output (
season,streak,win); - In the loop, we first execute our previously created SP, with necessary parameters (
call streak(cur_year, @l, @s);), then grab the data returned and insert into the temp table (insert into yearly_streak values (cur_year, @l, @s);). - Finally, we select from the temp table and return the dataset, then do some cleaning (
DROP TEMPORARY TABLE IF EXISTS yearly_streak;).
To get the results, we create another short PHP script as shown below:
<?php
... // Here goes the db connection parameters
$cn=new PDO($dsn, $user, $pass);
$res=$cn->query('call yearly_streak')->fetchAll();
foreach ($res as $r)
{
echo sprintf("In year %d, the longest W/L streaks is %d %s\n", $r['season'], $r['streak'], $r['win']);
}
And the display will be like:
Please note that the above is a bit different from calling our first SP.
The first SP does not return a dataset but only two parameters. In that case, we use PDO exec then query to fetch the output; while in the second SP, we returned a dataset from the SP, so we use PDO query directly to invoke the call to the SP.
Voila! We did it!
Conclusion
In this article, we dug further into MySQL stored procedures and took a look at the cursor functionality. We have demonstrated how to fetch scalar data by output parameters (defined as out var_name vartype in the SP declaration) and also fetch a calculated dataset via a temp table. During this process, a few statements otherwise used in stored procedures also surfaced.
The official documentation on the syntax of stored procedure and various statements can be found on the MySQL website. To create a stored procedure, please refer to these documents and to understand the statements, please see here.
Feel free to comment and let us know your thoughts!
Cursors in MySQL Stored Procedures的更多相关文章
- Home / Python MySQL Tutorial / Calling MySQL Stored Procedures in Python Calling MySQL Stored Procedures in Python
f you are not familiar with MySQL stored procedures or want to review it as a refresher, you can fol ...
- [MySQL] Stored Procedures 【转载】
Stored routines (procedures and functions) can be particularly useful in certain situations: When mu ...
- An Introduction to Stored Procedures in MySQL 5
https://code.tutsplus.com/articles/an-introduction-to-stored-procedures-in-mysql-5--net-17843 MySQL ...
- MySQL Error Handling in Stored Procedures 2
Summary: this tutorial shows you how to use MySQL handler to handle exceptions or errors encountered ...
- Why Stored Procedures?
http://www.w3resource.com/mysql/mysql-procedure.php Stored procedures are fast. MySQL server takes s ...
- java调用存储过程(stored procedures)的HelloWorld例子
1.java调用存储过程(stored procedures)的HelloWorld程序 有点数据 库基础的人都知道.存储过程(stored procedures)和java没什么关系.它是一段纯粹的 ...
- Drop all the tables, stored procedures, triggers, constraints and all the dependencies in one SQL statement
Is there any way in which I can clean a database in SQl Server 2005 by dropping all the tables and d ...
- Good Practices to Write Stored Procedures in SQL Server
Reference to: http://www.c-sharpcorner.com/UploadFile/skumaar_mca/good-practices-to-write-the-stored ...
- Part 10 Stored procedures in sql server
Stored procedures in sql server Stored procedures with output parameters Stored procedure output par ...
随机推荐
- ehcache2拾遗之cache持久化
问题描述 应用在使用过程中会需要重启等,但是如果ehcache随着应用一起重启,那么刚重启的时候就会出现大量的miss,需要一定的访问量来重建缓存,如果缓存能够持久化,重启之后可以复用将会有助于缓解重 ...
- 元素设置为display:none,其绑定的事件仍存在
元素设置为display:none,虽然该元素从页面中消失了,其绑定的事件仍存在. <body> <button class="button1">chang ...
- 360路由器刷openwrt、不死uboot、双系统 、wifi中继
该类教程网上有很多,但是很多不全,给小白用户造成了很多困扰.我也是按照网上的教程刷了半天,才熟悉了是怎么个回事.这里整理成教程. 注意: 请看教程步骤走,不要跳跃性刷机.不懂的术语.软件,若本文无介绍 ...
- iOS-性能优化4
UITableView性能优化技巧 Table view需要有很好的滚动性能,不然用户会在滚动过程中发现动画的瑕疵. 为了保证table view平滑滚动,确保你采取了以下的措施: 正确使用`reus ...
- web应用程序性能优化
web应用程序基本上都是在浏览器地址栏输入一段网站,然后进入,最后浏览器显示你想要的东西. 这就是用户所能体会到的东西.那作为程序员我们看到了什么呢? 一次HTTP 请求主要的流程是: 1.DNS服务 ...
- urldecode解码方法
输入为带有urldecode转码文本,输出正常文本. //20130625 by zhangyl private string ConvertToString(string input) { inpu ...
- 探讨Nodejs中的作用域问题。
在JS中有全局作用域和函数作用域,而在Nodejs中也自己的作用域,分为全局作用域(global)和模块作用域. js作用域: 以前学js的时候我们的全局对象是window,如: var a = 10 ...
- poj2513Colored Sticks(无向图的欧拉回路)
/* 题意:将两端涂有颜色的木棒连在一起,并且连接处的颜色相同! 思路:将每一个单词看成一个节点,建立节点之间的无向图!判断是否是欧拉回路或者是欧拉路 并查集判通 + 奇度节点个数等于2或者0 */ ...
- [转载]基于TFS实践敏捷-工作项跟踪
工作项跟踪(1) 可跟踪性是软件过程的重要能力,TFS主要是以工作项来实现过程的可跟踪性.曾有人问:"你们实际项目里的工作项是怎么样的?能不能让我们看看?"我也一直很好奇别的公司T ...
- vs切换当前编辑文件时自动定位目录树
在编辑区,切换当前编辑文件时(单击.cpp或.h文件选项卡),"解决方案资源管理器"目录树会自动定位当前编辑的文件,并以灰色标识,当一个解决方案中的工程数目数目很多,每个工程下面又 ...