FUNCTIONS
-- EXAMPLE OF FUNCTION THAT TAKES A DATE ENTERED BY A USER AND ADDS ONE YEAR TO THAT DATE
DELIMITER $
CREATE FUNCTION add_one_year(p_base_date DATE)
RETURNS DATE
READS SQL DATA
BEGIN
RETURN (SELECT DATE_ADD(p_base_date, INTERVAL 1 YEAR));
END $
DELIMITER ;
-- HOW TO INVOKE THIS FUNCTION
SELECT add_one_year('2017-07-30');
-- HOW TO DROP THE FUNCTION
DROP FUNCTION add_one_year;
-- HOW TO SHOW FUNCTIONS THAT EXIST
SHOW FUNCTION STATUS;
-- HOW TO SHOW CRITERIA THAT CREATED FUNCTION
SHOW CREATE FUNCTION add_one_year;
-- EXAMPLE OF HOW TO CREATE average_commission, WHICH HAS 2 ARGUMENTS p_no_sales AND p_no_salespersons, WHICH RETURNS A DECIMAL (19,2), WHICH SELECTS THE AVG() FROM THE PRICE COLUMN OF products1 AND MULTIPLIES IT BY THE NUMBER ENTERED IN p_no_sales, AND THEN DIVIDES THAT BY THE NUMBER ENTERED IN p_no_salespersons).
DELIMITER $
CREATE FUNCTION average_commission(p_no_sales INT, p_no_salespersons INT)
RETURNS DECIMAL (19,2)
READS SQL DATA
BEGIN
RETURN (SELECT ((SELECT AVG(Price) from products1) * p_no_sales) / p_no_salespersons);
END $
-- EXAMPLE OF HOW TO INVOKE THE FUNCTION average_commission PLUGGING IN A NUMBER 17 FOR p_no_sales AND THE NUMBER 4 for p_no_salespersons.
SELECT average_commission(17,4);
-- VERY SIMPLE FUNCTION EXAMPLE, WHICH ADDS 7 TO WHATEVER NUMBER IS INPUT FOR THE ARGUMENT
DELIMITER $
CREATE FUNCTION add_seven(p_enter_number_here INT)
RETURNS INT
READS SQL DATA
BEGIN
RETURN 7 + (SELECT p_enter_number_here);
END $
DELIMITER ;
/****************************************
FUNCTIONS
****************************************/
/*
A FUNCTION accepts parameters, performs an action, and then returns a value(s). There are built-in functions, and you have the ability to create your own functions that return a value or a table. A function can return a results set in a similiar fashion to a stored procedure, but a function can be referenced in a FROM clause, whereas a procedure cannot.
Types of built-in functions:
SCALAR - operates on a single row, returns a single value
AGGREGATE FUNCTIONS - takes one or more input values, then returns a single value summarizing those values
LOGICAL FUNCTIONS - scalar functions that compare multiple values to determine a single output
WINDOW FUNCTIONS - operates on a window (set) of rows
You can also create your own SCALAR and TABLE VALUED FUNCTIONS, which often are given a prefix of 'udf' for User Defined Function. See the TABLE VALUED FUNCTION example below for the syntax for creating a User Defined Function.
TABLE VALUED FUNCTIONS - a function that returns a virtual table. Can be thought of as a View (a saved query) that accepts parameters.
*/
/****************************************
EXAMPLES OF BUILT-IN FUNCTIONS
****************************************/
-- SCALAR FUNCTIONS - operates on a single row, returns a single value. Can be used like an expression in queries. If you apply a scalar function to a set of rows, it will act on each row individually, therefore it's still one in and one out. May be deterministic or non-deterministic (deterministic always returns the same value, but non-deterministic do not always return the same results every time they are called, even with the same set of input values. Example - if you compare a function with today's date against a due date, the same date going in won't always have the same value coming out, depending on what today's date is).
-- Scalar example #1, YEAR.
SELECT YEAR(SellStartDate)
FROM products1;
-- Scalar example #2, DATEDIFF, GETDATE, DATENAME.
SELECT DATEDIFF(yy,SellStartDate, GETDATE()) AS YearsSold, DATENAME(mm,SellStartDate), ProductID
FROM products1
ORDER BY ProductID;
-- AGGREGATE FUNCTIONS - takes one or more input values (set of data), then returns a single value summarizing those values. Aggregate functions ignore NULL values.
-- Aggregate example #1, COUNT. COUNT(*) by itself will return a count of all rows in a table.
SELECT COUNT(*) AS Products, COUNT(DISTINCT ProductCategoryID) AS Categories0
FROM products1
WHERE Products LIKE '%Bikes';
-- LOGICAL FUNCTIONS - scalar functions that compare multiple values to determine a single output.
-- Logical example #1, ISNUMERIC, used to determine if a string value is number. It returns a 1 if this is True (it is numeric) and 0 if False (it's not numeric).
SELECT Name, Size as NumericSize
FROM products1
WHERE ISNUMERIC(Size) = 1;
-- Logical example #2, IIF, which checks something, and then returns either one value or another based on the result.
SELECT productid, listprice, IIF(listprice > 50, 'high', 'low') AS PricePoint
FROM products1;
-- Logical example #3, CHOOSE, which turns a code into a string value. In example below, 1 will return as 'Bikes', 2 will return as 'Components', 3 will return as 'Clothing', etc. (the string values are derived from the ordinal position you place the values in).)
SELECT ProductName, Color, Size
CHOOSE(ProductCategoryID, 'Bikes', 'Components', 'Clothing') AS Category
FROM products1;
-- WINDOW FUNCTIONS - operates on a window (set) of rows. Includes ranking, offest, aggregate, and distribution functions. The OVER clause defines a window or user-specified set of rows within a query result set, which is used before the window function then computes a value for each row in the window.
-- Window function example #1, RANK, which looks at values and gives them a numeric ranking based on the value.
SELECT TOP 3 ProductID, Name, ListPrice
RANK() OVER(ORDER BY ListPrice DESC) AS RankByPrice
FROM products1
ORDER BY RankByPrice;
-- Window function example #2, ROW_NUMBER counts the rows, and RANK ranks along with a PARTION (the ranking restarts each time a new SalesPersonID comes up in the result set: TITION (allows you to reset the numbering each time a new partition comes up.
SELECT SalesPersonID, TotalDue,
ROW_NUMBER() OVER(ORDER BY SalesPersonID, TotalDue) AS 'Row Count',
RANK() OVER(PARTITION BY SalesPersonID ORDER BY SalesPersonID, TotalDue) AS 'Rank by SalesPerson'
FROM Sales.SalesOrderHeader
ORDER BY SalesPersonID;
/* Excerpt from results. Note how ROW_NUMBER is not partitioned, so it counts the rows throughout entire result set. RANK is partioned by SalesPersonID, so it restarts the ranking when a new SalesPersonID comes up:
SalesPersonID TotalDue Row Count Rank by SalesPerson
...
274 95426.0186 46 46
274 97248.3525 47 47
274 126852.1615 48 48
275 6.3484 49 1
275 12.0152 50 2
275 18.3792 51 3
278 22.3061 52 1
278 25.3868 53 2
278 27.4401 54 3
278 27.4401 54 4
...
*/
/****************************************
CREATING A USER DEFINED FUNCTION
****************************************/
/* SCALAR USER DEFINED FUNCTIONS - a function that returns a single value. Note that a scalar valued
function uses BEGIN and END, the RETURN keyword is needed (apparently a SELECT cannot be used to
return a value through a function, RETURN is needed), and that the SELECT syntax is simply SELECT
followed by the function name (SELECT udf_functionname(2)), rather than the syntax used when SELECTing
a table valued function, which is more like SELECTing from a table (SELECT * FROM udf_functionname(2)).
Also note that you are apparently required to include schema prefix (in this case 'dbo.') when calling
a scalar function.
*/
CREATE FUNCTION udf_prod_num_orders(@productID INT)
RETURNS INT
AS
BEGIN
RETURN
(
SELECT COUNT(ProductID)
FROM orders1
WHERE ProductID = @productID
)
END;
SELECT dbo.udf_prod_num_orders(1);
/*TABLE VALUED USER DEFINED FUNCTIONS - a function that returns a virtual table. Can be thought of as
a View (named, saved query) that accepts parameters. Note that an inline table valued function
(this kind) does not use BEGIN and END (though a multi-line table valued function apparently does),
the RETURN keyword is required to pass the value out of the function, and that the SELECT syntax is
not like that with calling a scalar function (SELECT udf_functionname(2)), but rather is like SELECTing
from a table (SELECT * FROM udf_functionname(2)).
*/
CREATE FUNCTION udf_prod_customer_date_table_valued(@productID INT)
RETURNS TABLE
AS
RETURN
(
SELECT CustomerID, OrderDate
FROM orders1
WHERE ProductID = @productID
);
SELECT * FROM udf_prod_customer_date_table_valued(1);
/****************************************
FUNCTION DETERMINISM
****************************************/
/*
Function determinism is a characteristic that indicates whether the function is guaranteed to return the
same result give the same set of input values (including an empty set) in different invocations. If the
function provides such a guarantee, it is said to be deterministic; otherwise, it is nondeterministic.
There are three main categories of function determinism:
1. Functions that are always deterministic
2. Functions that are deterministic when invoked in a certain way
3. Functions that are always nondeterministic
Always deterministic functions include all string functions, COALESCE, ISNULL, ABS, SQRT and others. They'll
always give the same result given the same inputs.
Other functions are deterministic depending on how they're used. CAST and CONVERT are not deterministic
when converting from a character string to a date or time type or the other way because the
interpretation of the value might be dependent on the login's language. RAND is also not always deterministic.
When it is used with a seed it is deterministic, always giving the same result. When not used with a
seed, it will give you a pseudo random value and is therefore nondeterministic.
Always nondeterministic functions include SYSDATETIME and NEWID.
*/
/****************************************
VIEWING A FUNCTION DEFINITION AFTER IT HAS BEEN CREATED
****************************************/
/* Viewing the definition can be done by finding the function in Object Explorer, right-clicking and selecting
Modify or Script Function As. It can also be done using SQL:
*/
-- Get the function name, definition, and relevant properties
SELECT sm.object_id,
OBJECT_NAME(sm.object_id) AS object_name,
o.type,
o.type_desc,
sm.definition,
sm.uses_ansi_nulls,
sm.uses_quoted_identifier,
sm.is_schema_bound,
sm.execute_as_principal_id
-- using the two system tables sys.sql_modules and sys.objects
FROM sys.sql_modules AS sm
JOIN sys.objects AS o ON sm.object_id = o.object_id
-- from the function 'dbo.ufnGetProductDealerPrice'
WHERE sm.object_id = OBJECT_ID('dbo.myfunctionnotsecret')
ORDER BY o.type;
GO
/****************************************
FUNCTION DEFINITION ENCRYPTION
****************************************/
/*
Using the WITH ENCRYPTION keyword when creating the function will hide the function definition from all future users
who try to view it. This can be useful if for example you are creating a simple text encryption function to jumble the
letters of a sensitive column. If an attacker were to access your database, she could find the encryption function
and use the definition to decrypt the data. However, if you hide the function definition from being viewed, then it
becomes more difficult to figure out what actions were taken via the encryption process, and therefore it becomes more
difficul to decrypt the sensitive data.
*/
CREATE FUNCTION myfunctionnotsecret(@enterNumber INT)
RETURNS INT
WITH ENCRYPTION -- will encrypt the definition from future view
AS
BEGIN
declare @finalNumber INT
SELECT @finalNumber = @enterNumber * 2
RETURN @finalNumber
END
GO
/* The function will still work, but if you use WITH ENCRYPTION when defining the function, if you right-click and Modify/Script
in Object Explorer, you will get an error saying the text is encrypted. If you use the SQL script method, it'll show up with a
NULL value in the definition column.
*/