The series so far:
- Getting started with MySQL
- Working with MySQL tables
- Working with MySQL views
- Working with MySQL stored procedures
- Working with MySQL stored functions
In the previous three articles in this series, I focused on creating basic database objects that you can use to get started with MySQL. You learned how to build an initial database and then add tables, views, and stored procedures. In this article, I cover one more important type of object, the stored function, a routine that is stored in a database and can be invoked on-demand, similar to a user-defined scalar function in SQL Server or other database systems.
Stored functions work much like MySQL built-in functions. You can call either type of function in an expression, such as those in a query’s SELECT
, WHERE
, or ORDER
BY
clause. For example, you might use the CAST
built-in function in a SELECT
clause to convert a column to a different data type, as in CAST(plane_id
AS
CHAR)
. The expression converts the plane_id
column (an integer) to a character data type. In the same way, you can use a stored function in your expression, applying your own logic to the plane_id
column or any other column.
Before I go any further with stored functions, it’s important to note that there are three different types of MySQL functions that you can add at the database or server level:
- Stored functions. Functions that you create as database objects by using the
CREATE
FUNCTION
statement. - Loadable functions. Functions that are compiled as library files and then loaded to the server dynamically by running a
CREATE
FUNCTION
statement. - Native functions. Functions that are added to the server by modifying the MySQL source code and compiling it into mysqld.
This article focuses on creating stored functions, which share many of the same characteristics as MySQL stored procedures. In fact, I had considered writing about both of them in the previous article, but I think there are enough differences between them to warrant a separate article. It also provides a more consistent way of introducing each of the primary MySQL object types, even if it does mean repeating some of the information. With this in mind, let’s get started with the stored function.
Preparing your MySQL environment
As with the previous few articles, the examples in this article are based on the travel
database. If you already have it installed, you can skip this section. If not, you can start by running the following SQL script to create the database and its tables:
DROP DATABASE IF EXISTS travel; CREATE DATABASE travel; USE travel; CREATE TABLE manufacturers ( manufacturer_id INT UNSIGNED NOT NULL AUTO_INCREMENT, manufacturer VARCHAR(50) NOT NULL, create_date TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, last_update TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (manufacturer_id) ) ENGINE=InnoDB AUTO_INCREMENT=1001; CREATE TABLE airplanes ( plane_id INT UNSIGNED NOT NULL AUTO_INCREMENT, plane VARCHAR(50) NOT NULL, manufacturer_id INT UNSIGNED NOT NULL, engine_type VARCHAR(50) NOT NULL, engine_count TINYINT NOT NULL, max_weight MEDIUMINT UNSIGNED NOT NULL, wingspan DECIMAL(5,2) NOT NULL, plane_length DECIMAL(5,2) NOT NULL, parking_area INT GENERATED ALWAYS AS ((wingspan * plane_length)) STORED, icao_code CHAR(4) NOT NULL, create_date TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, last_update TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (plane_id), CONSTRAINT fk_manufacturer_id FOREIGN KEY (manufacturer_id) REFERENCES manufacturers (manufacturer_id) ) ENGINE=InnoDB AUTO_INCREMENT=101;
The airplanes
table includes a foreign key that references the manufacturers
table, so be sure to create the tables in the order shown here. After you create the tables, you can add sample data to them so you’ll be able to test your function. To populate the tables, run the following INSERT
statements:
INSERT INTO manufacturers (manufacturer) VALUES ('Airbus'), ('Beechcraft'), ('Piper'); INSERT INTO airplanes (plane, manufacturer_id, engine_type, engine_count, max_weight, wingspan, plane_length, icao_code) VALUES ('A380-800', 1001, 'jet', 4, 1267658, 261.65, 238.62, 'A388'), ('A319neo Sharklet', 1001, 'jet', 2, 166449, 117.45, 111.02, 'A319'), ('ACJ320neo (Corporate Jet version)', 1001, 'jet', 2, 174165, 117.45, 123.27, 'A320'), ('A300-200 (A300-C4-200, F4-200)', 1001, 'jet', 2, 363760, 147.08, 175.50, 'A30B'), ('Beech 390 Premier I, IA, II (Raytheon Premier I)', 1002, 'jet', 2, 12500, 44.50, 46.00, 'PRM1'), ('Beechjet 400 (from/same as MU-300-10 Diamond II)', 1002, 'jet', 2, 15780, 43.50, 48.42, 'BE40'), ('1900D', 1002, 'Turboprop', 2,17120, 57.75, 57.67, 'B190'), ('PA-24-400 Comanche', 1003, 'piston', 1, 3600, 36.00, 24.79, 'PA24'), ('PA-46-600TP Malibu Meridian, M600', 1003, 'Turboprop', 1, 6000, 43.17, 29.60, 'P46T'), ('J-3 Cub', 1003, 'piston', 1, 1220, 38.00, 22.42, 'J3');
As with the CREATE
TABLE
statements, you should run the INSERT
statements in the order specified here so you don’t violate the foreign key defined on the airplanes
table. Once you have the tables set up and populated, you can start creating stored functions.
Creating a stored function in MySQL
To add a stored function to a MySQL database, you can use the CREATE
FUNCTION
statement. The statement is similar to a CREATE
PROCEDURE
statement in several respects. In both cases, you must provide a name for the object and you must define a routine. You also have the option to include a DEFINER
clause, one or more characteristics, and one or more parameters.
Despite these similarities, the CREATE
FUNCTION
statement differs in several important ways:
- A stored function can return only one value, unlike a stored procedure, which can return multiple values or an entire result set.
- A stored function supports input parameters only. A stored procedure supports
IN
,OUT
, andINOUT
parameters in any combination. - A stored function must include a
RETURNS
clause in its definition before the routine. The clause specifies the data type for the function’s returned value. Stored procedures do not support this clause. - A stored function’s routine must include a
RETURN
statement that specifies the function’s returned value. The routine does not have to include any other statements, only theRETURN
statement. If it does include other statements, only theRETURN
statement can return a value.
With these guidelines in mind, let’s look at a simple example of a CREATE
FUNCTION
statement, which defines a stored function that converts pounds to kilograms:
DELIMITER // CREATE FUNCTION lbs_to_kg(lbs MEDIUMINT UNSIGNED) RETURNS MEDIUMINT UNSIGNED DETERMINISTIC BEGIN RETURN (lbs * 0.45359237); END// DELIMITER ;
The function is named lbs_to_kg
and includes one input parameter named lbs
. You do not have to include a parameter when defining a function, but typically you’ll want at least one. If you add more than one, you need to separate them with commas.
The parameter definition is enclosed in parentheses and includes the parameter’s data type, MEDIUMINT
UNSIGNED
. I chose this data type because I ultimately want to use the function for the max_weight
column in the airplanes
table, which is also defined with that data type.
In addition, I used the MEDIUMINT
UNSIGNED
data type for the RETURNS
clause. The clause specifies that the function’s returned value should be an integer in the range permitted by this data type. I figured we were safe with this data type because one pound is equivalent to 0.45359237 kilograms, so the returned value would never exceed the maximum value in the max_weight
column.
If you want to support a greater range of values, you can instead use the INT
or BIGINT
data type for the lbs
parameter and the RETURNS
clause. This would provide you with more flexibility should you want to use the function to convert values that exceed those in the max_weight
column.
The RETURNS
clause is followed by the DETERMINISTIC
characteristic. A characteristic is one of several options that can be added to a function definition, each one affecting the function in a different way. For example, you can add a characteristic to indicate the routine language or to define the routine’s nature. These are the same characteristics available to stored procedures.
The DETERMINISTIC
characteristic indicates that the function will return the same results for the same input parameter each time the function runs. By default, a function is considered nondeterministic unless specified otherwise. Using the DETERMINISTIC
characteristic can help the optimizer make better execution plan choices. However, assigning the characteristic to a nondeterministic function could cause the optimizer to make incorrect choices.
The function’s routine comes after the listed characteristics. For this routine, I’ve used the BEGIN…END
syntax to set up a compound statement, even though there is only one RETURN
statement. Often your routine will include a compound statement—a block of one or more SQL statements—and I wanted to be sure you understood how to include them in your function definition. As with stored procedures, it’s not uncommon for developers to use a compound statement, even if it includes only a single SQL statement.
The RETURN
statement defines a simple mathematic expression that multiples the lbs
input parameter value by 0.45359237
to arrive at the number of kilograms for the specified weight. The result from this calculation is what is returned by the function when you run it.
The preceding example also includes two DELIMITER
statements that surround the function definition. The first DELIMITER
statement changes the delimiter to double forward slashes (//), and the second DELIMITER
statement changes the delimiter back to a semi-colon (the default). As you saw in the previous article, this provides a way to pass the entire function definition to the server as a single statement.
Verifying a newly created stored function
After you run the CREATE
FUNCTION
statement, you can verify that it’s been added to the travel
database by viewing it in Navigator, as shown in Figure 1. (You might need to refresh Navigator to see the new function.)
From Navigator, you can open the function definition in the Routine tab by clicking the wrench icon next to the function name. Figure 2 shows the function definition on the Routine tab. The CREATE
FUNCTION
statement is nearly identical to what you created, except that it now includes the DEFINER
clause after the CREATE
keyword.
As you saw with views and stored procedures, the DEFINER
clause specifies which account has been designated as the object creator. I ran the CREATE
FUNCTION
statement when I was signed in under the root account on my local MySQL instance, so that’s the username added to the definition. By default, MySQL uses the account of the user who runs the CREATE
PROCEDURE
statement, but you can specify a different account as long as it’s been granted adequate permissions.
You might have noticed that the function definition on the Routine tab does not include the DELIMITER
statements or custom delimiter. However, if you were to update the definition and click Apply, Workbench would add those elements for you. (It would also add a DROP
PROCEDURE
statement that needs to run before the CREATE
FUNCTION
statement.)
Another way you can verify that the function has been created is to query the routines
view in the INFORMATION_SCHEMA
database:
SELECT * FROM information_schema.routines WHERE routine_schema = 'travel';
The results should include the lbs_to_kg
function, along with any other stored functions or stored procedures that have been created in the database. If you’re using the setup from the last article, your database might include the get_plane_info
stored procedure.
In the preceding example, I included a WHERE
clause that limits the results to the travel
database. However, you can further limit the results by also specifying the function name in the WHERE
clause and by specifying which column or columns to return. For example, the following SELECT
statement limits the results to the routine_definition
column and to the lbs_to_kg
function in the travel
database:
SELECT routine_definition FROM information_schema.routines WHERE routine_schema = 'travel' AND routine_name = 'lbs_to_kg';
The statement should now return only a single value, although it might be difficult to read. As you saw with stored procedures, you can view a value in its entirety in a separate window. Right-click the value directly in the results and click Open Value in Viewer. MySQL launches a window that displays the value, as shown in Figure 3. (Select the Text tab if it’s not already selected.)
As you can see, the window displays only the function’s routine body, which in this case, is a compound statement that includes a RETURN
statement.
Using a stored function in a MySQL query
After you verify that your function has been created, you should check that it works as expected. One way to do this is to create a simple SELECT
statement that does nothing but call the lbs_to_kg
function. For example, the following SELECT
statement includes only a SELECT
clause, and that clause contains only one expression:
SELECT lbs_to_kg(132) AS max_kg;
The expression calls the lbs_to_kg
function, passing in 132
as the parameter value. The expression also provides a name for the output column (max_kg
). The statement should return a value of 60
.
In many cases, you’ll want to use your stored function for more than just running it in a simple SELECT
statement. For example, you can use a stored function to transform or augment a column’s values when retrieving data, which is what I’ve done in the following SELECT
statement:
SELECT a.plane, max_weight AS max_lbs, lbs_to_kg(max_weight) AS max_kg FROM airplanes a INNER JOIN manufacturers m ON a.manufacturer_id = m.manufacturer_id WHERE m.manufacturer = 'airbus' ORDER BY a.plane;
The statement joins the airplanes
and manufacturers
tables based on the manufacturer_id
column in each table. The statement’s SELECT
clause includes an expression that uses the lbs_to_kg
function to convert the max_weight
column to kilograms and return a column named max_kg
. The statement returns the results shown in Figure 4.
The results include the original weight (in pounds) in the max_lbs
column and the weight in kilograms in the max_kg
column after converting the max_weight
values. By including both weights, you can quickly compare them to get a general sense of whether the function seems to be returning the expected results.
Updating a stored function in MySQL
As pointed out earlier, MySQL stored functions are similar to stored procedures in several ways. For example, they both support characteristics, input parameters, and the DEFINER
clause. They’re also similar in another important way. You can alter only the characteristics. You cannot change the routine or any other statement elements. Instead, you must first drop the function and then re-create it, incorporating any new elements.
To drop a stored function, you can use the DROP
FUNCTION
statement, as shown in the following example:
DROP FUNCTION IF EXISTS lbs_to_kg;
The IF
EXISTS
clause is optional, but it’s a handy way to avoid generating errors when you try to drop a function. The clause can be particularly useful when you’re developing your database schema and you’re regularly updating the objects.
After you’ve dropped the function, you can modify the definition to meet your new requirements. For example, the following CREATE
FUNCTION
statement re-creates the lbs_to_kg
function but this time adds a DECLARE
statement and an IF
construction to the compound statement:
DELIMITER // CREATE FUNCTION lbs_to_kg(lbs MEDIUMINT UNSIGNED) RETURNS VARCHAR(50) DETERMINISTIC BEGIN DECLARE msg VARCHAR(50); IF lbs > 999999 THEN SET msg = CONCAT(ROUND((lbs * 0.45359237), 0), ' kg exceeds airport weight limits.'); ELSEIF lbs >= 100000 AND lbs <= 999999 THEN SET msg = CONCAT(ROUND((lbs * 0.45359237), 0), ' kg exceeds runway weight limits.'); ELSE SET msg = CONCAT(ROUND((lbs * 0.45359237), 0), ' kg within weight limits.'); END IF; RETURN msg; END// DELIMITER ;
The DECLARE
statement declares the msg
local variable and assigns it the VARCHAR
data type. Notice that the RETURNS
clause has also been updated to the VARCHAR
data type to match the msg
variable. The variable can then be used in the final RETURN
statement to provide the function’s output value.
The compound statement also includes an IF
statement. The statement starts with an initial conditional clause, which is followed by an ELSEIF
clause and then an ELSE
clause. Each clause implements the same logic based on the value of the lbs
input parameter. If the lbs
value falls within the specified range, the msg
variable is set to a predefined value based on that range. (We’ll be covering conditional statements in more detail later in the series.)
The msg
value is determined first by converting the lbs
value to kilograms and then concatenating the results with a string (the message body). For example, if the lbs
value is greater than 99999, the msg
variable is set to the number of kilograms plus the message ‘ kg exceeds the airport weight limits.’
To help carry out this logic, each conditional clause also includes two built-in functions: ROUND
and CONCAT
. The ROUND
function rounds the calculated kilograms to a whole number, and the CONCAT
function concatenates the rounded kilograms with the specified text. For example, if the weight in pounds is 120,000, the IF
statement will set the msg
variable to ‘54431 kg exceeds runway weight limits.’ You can see this for yourself by running the following SELECT
statement:
SELECT lbs_to_kg(120000) AS max_kg;
The statement should return the results shown in Figure 5.
You can also use the lbs_to_kg
function in a more elaborate SELECT
statement, just like you did earlier:
SELECT m.manufacturer, a.plane, max_weight AS max_lbs, lbs_to_kg(max_weight) AS max_kg FROM airplanes a INNER JOIN manufacturers m ON a.manufacturer_id = m.manufacturer_id ORDER BY m.manufacturer, a.plane;
Now each returned row includes one of the three messages in the max_kg
column. The message is based on the number of pounds in the max_weight
column, which is passed to the function through its parameter. Figure 6 shows the results returned by the SELECT
statement.
Your function can, of course, include a much more complex routine than what I’ve done here, but these examples should be enough to give you a sense of what you can do with stored functions and how valuable they can be when building your queries.
Altering a stored function in MySQL
As I mentioned earlier, the only elements of a stored function definition that you can alter are the characteristics. For this, you can use an ALTER
FUNCTION
statement. For example, the following statement adds a COMMENT
characteristic and SQL
SECURITY
characteristic:
ALTER FUNCTION lbs_to_kg COMMENT 'converts weight to kilograms and generates message' SQL SECURITY INVOKER;
The COMMENT
characteristic simply adds a comment that describes the function’s purpose. The SQL
SECURITY
characteristic instructs MySQL to run the routine under the security context of the user account that invokes the function rather than using the definer account (the default behavior).
After you run the ALTER
FUNCTION
statement, you can verify that the characteristics have been added by viewing the function definition on the Routine tab, as shown in Figure 7.
The CREATE
PROCEDURE
statement now includes three characteristics—the one you added originally and the two you added when you ran the ALTER
FUNCTION
statement.
Working with stored functions in MySQL
Although stored functions are similar to stored procedures, they serve a distinctly different purpose: to return a value that can be used by an expression during its evaluation. For this reason, stored functions can be extremely useful and are well worth adding to your arsenal of tools, especially since they’re so easy to create and execute. However, they can also impact performance if not carefully implemented. For example, if your query returns thousands of rows of data, an overly complex function could bring your system to its knees as MySQL tries to apply the logic to each row. When used properly, however, stored functions can be incredibly beneficial, especially as you become more adept at building SQL queries.
The post Working with MySQL stored functions appeared first on Simple Talk.
from Simple Talk https://ift.tt/VatPLin
via
No comments:
Post a Comment