My function is has two variables ('date', number of months to subtract from 'date').
here is my function:
create function a_testbed.PrevMonth (
in_date date,
in_mn_count int)
RETURNS int
BEGIN
/* local variable declaration */
declare v_date int(255);
declare v_date_format varchar(10);
declare v_date_sub varchar(10);
set v_date_format := DATE_FORMAT(v_date, '%Y-%m');
set v_date_sub := DATE_FORMAT(curdate(), '%Y-%m');
if (in_date is null) then
set v_date := v_date_sub;
elseif (in_mn_count is null or in_mn_count < 0) then
set in_mn_count := 0;
else
set v_date := v_date_sub;
end if;
RETURN v_date_format;
END;
#
Keep getting the error:
ERROR 1265 (01000) Data Truncated for column 'v_date' at row 1
any thoughts?
thanks!
There are lots of things wrong with your function:
You do set v_date_format := DATE_FORMAT(v_date, '%Y-%m');, but this is before you assignea value to v_date.
Later you assign set v_date := v_date_sub;, but you never use v_date after that, so what is the point?
You assign set in_mn_count := 0;, but never use this variable, either.
Finally you do RETURN v_date_format;. v_date_format is a VARCHAR, but the function is declared to return INT. And the contents of this string is in the form YYYY-MM, so it doesn't look like an integer.
I'm not sure if any of these are causing the Data truncated error, but that hardly seems to be important when the function is so broken.
You assign varchar to int set v_date := v_date_sub;
Change this:
declare v_date varchar(10);
I don't believe "INT(255)" is a valid datatype.
I know the 255 is just a display width attribute. The INT datatype defines the range of values, the display width doesn't impact the range of values. But I've just never seen the length modifier on an INT larger than 11. (10 for an unsigned int) (?)
As a test, you could try specifying INT(11), or just INT.
More problematic, perhaps, is that this variable with datatype INT is being passed as the first argument to DATE_FORMAT function. But the first argument of DATE_FORMAT needs to be a DATE, DATETIME or TIMESTAMP (iirc). There may be some implicit conversion from INT to DATE, but I don't think there is.
I don't understand why you need a function to subtract a number of months from a DATE. MySQL has builtin in functions, and simple expressions that do that.
For example:
dateexpr + INTERVAL -6 MONTH
returns a DATE (or DATETIME) value with 6 months subtracted from dateexpr.
If you need to "round down" to the first of the month, or convert to a string with just the 'yyyy-mm', you can do a DATE_FORMAT(dateexpr,'%Y-%m-01') or a SUBSTRING(dateexpr,1,7), respectively.
review
CREATE FUNCTION a_testbed.PrevMonth (
in_date date,
in_mn_count int)
That looks okay, two input parameters, a DATE and an INT. (Function parameters are always IN parameters.)
RETURNS int
A bit odd that a function named PrevMonth takes a DATE argument, but returns an INT, rather than a DATE, but this isn't an error.
BEGIN
/* local variable declaration */
DECLARE v_date INT(255);
The display width of 255 is bizzarre. Never seen that on an INT before.
DECLARE v_date_format VARCHAR(10);
DECLARE v_date_sub VARCHAR(10);
Two strings, looks fine.
SET v_date_format := DATE_FORMAT(v_date, '%Y-%m');
^^^^^^
But v_date is an INT, not a DATE or DATETIME. And it's not initialized, so it's NULL. If this doesn't throw an exception, then we'd expect v_date_format to be NULL. (I suspect you wanted to specify in_date here in place of v_date.)
SET v_date_sub := DATE_FORMAT(curdate(), '%Y-%m');
This should get us 7 characters, e.g. '2013-07'. So no problem there.
IF (in_date IS NULL) THEN
SET v_date := v_date_sub;
Here's a usage of the in_date parameter. Check if it's null. If it is, assign the same 7 characters that we assigned to v_date_sub. Got it.
ELSEIF (in_mn_count IS NULL OR in_mn_count < 0) THEN
SET in_mn_count := 0;
Is it valid to assign a value to an IN parameter? (That may be valid, but it is odd. Normally we don't assign values to parameters unless they are OUT, or INOUT paremeters. We normally assign values to procedure variables (and sometimes MySQL user variables.)
Why is this dependent on whether in_date is null or not (the preceding IF). Seems like we would want to check this parameter in either case.
ELSE
SET v_date := v_date_sub;
Isn't this the same action specified under the first IF? It seems like the logic here reads "if in_date is not null and ifnull(in_mn_count,-1)<0" we do one thing, else we do somthing else. We're either trying to assign a value to an IN parameter, or we're assigning current YYYY-MM to v_date.
END IF;
So far, the only use of the in_date parameter has been to check if it is NULL. And the only usage of the in_mn_count parameter has been to try to assign a zero to it, if it's null or less than zero.
RETURN v_date_format;
The only time we assigned anything to v_date_format was at the beginning of the function, when we assigned the return from a DATE_FORMAT function, passing in a NULL value (as an INT rather than a DATE.) So what was all the other code supposed to be doing?
END
Yes, please, end. There is so much wrong in this function, it makes my head hurt. So, please, yes, END. Just make it stop.
Related
DELIMITER //
CREATE PROC InserimentoValori()
BEGIN
DECLARE #caratteri varchar(30);
set #caratteri = 'abcdefghijklmnopqrstuvwxyz',
DECLARE x INT DEFAULT 1;
WHILE x <=100 DO
INSERT INTO Persona(nome,cognome,eta) VALUES((SELECT #caratteri = substring(#caratteri +1),(SELECT #caratteri = sebstring(#caratteri +1),(SELECT floor(rand() * 99) AS randNum));
SET x = x+1;
END WHILE
END //
DELIMITER ;
I want to create a stored procedure that insert random values into the table.
Thanks
There are a couple of errors.
We don't "declare" user defined variables in MySQL. Just SET them.
If you want to DECLARE a variable within a procedure, that needs to be a procedure variable.
A user defined variable has a name that starts with the # character. A procedure variable cannot start with a # character.
So, a line like this is an error:
DECLARE #foo ...
If you want to use a user defined variable, remove that line. If you want to use a procedure variable, remove the # from the beginning of the variable name (and make that same change everywhere you want to reference the procedure variable foo.)
And SEBSTRING is not the name of a MySQL provided function.
Also, a boolean expression in a SELECT list of a query will return 0, 1 or NULL.
For example:
SELECT #caratteri = substring(#caratteri +1)
That expression is comparing the value on the left side of the = with the value on the right, and is going to return 1 if they are equal, or 0 if the aren't, or NULL if either of the values is NULL.
To perform an assignment to a user defined variable in a SELECT statement, use the Pascal-style := operator.
(If you meant to do an assignment, the design makes it look like you are gogin to lop off the first character each time through the loop; that's eventually going to be an empty string, if we loop enough times. You may want to think about leaving the string static. Consider incrementing integer values, and use those as arguments in SUBSTRING function. And you can use the MOD operator to get the integer value to "wrap".)
I have one DATE input parameter for a procedure ex: IN p_date DATE.I want to validate this input DATE parameter format inside a procedure which should be in YYYY-MM-DD format. If the input parameter is having characters or date format is wrong it should through an exception using SIGNAL.
Please find the below code what i written
CREATE PROCEDURE `validation_check`(IN pdate_time DATE)
BEGIN
DECLARE InputValidation CONDITION FOR SQLSTATE '45000';
DECLARE dateValidation CONDITION FOR SQLSTATE '45000';
/* Doing NULL validation */
IF pdate_time IS NULL THEN
SIGNAL InputValidation
SET MESSAGE_TEXT='pdate_time should not be empty.';
END IF;
/* Doing Date format validation
IF STR_TO_DATE(pdate_time,'%Y-%m-%d') != pdate_time THEN
SIGNAL dateValidation
SET MESSAGE_TEXT='Input Date format should be in YYYY-MM-DD.';
END IF;
*/
/* Doing Date format validation */
IF pdate_time NOT REGEXP '/^[0-9]{4}-(0[1-9]|1[0-2])-(0[1-9]|[1-2][0-9]|3[0-1])$/' THEN
SIGNAL dateValidation
SET MESSAGE_TEXT='Input Date format should be in YYYY-MM-DD.';
END IF;
SELECT pdate_time;
END
Thanks,
Sagar
TL;DR
Regular expression will fail in this case, because they may only check date format, but not if it is a valid date (as, for example, '2014-02-30' has correct format but invalid data for date)
Use string functions
Concept
The solution is - yes, use string functions. However, regular expressions will also be useful - to check if format was good, you still need to check date itself. Since validation of date is a single separated issue - you should create a function for it. That function will accept string and return result as boolean value - so either date is ok or not. This will be re-usable and, therefore, more flexible.
Code
Here we go with the function:
CREATE FUNCTION VALIDATE_DATE(d VARCHAR(255))
RETURNS INT
BEGIN
DECLARE date_year VARCHAR(255) DEFAULT '';
DECLARE date_month VARCHAR(255) DEFAULT '';
DECLARE date_day VARCHAR(255) DEFAULT '';
DECLARE ym_delim INT DEFAULT 0;
DECLARE md_delim INT DEFAULT 0;
-- First, if it's just not xxx-yyy-zzz format:
SET ym_delim = LOCATE('-', d);
SET md_delim = LOCATE('-', d, ym_delim+1);
IF !ym_delim || !md_delim THEN
RETURN FALSE;
END IF;
-- Second, if resulted members are not YYYY, MM or DD:
SET date_year = SUBSTR(d, 1, ym_delim-1);
SET date_month = SUBSTR(d, ym_delim+1, md_delim-ym_delim-1);
SET date_day = SUBSTR(d, md_delim+1);
IF date_year NOT REGEXP '^[0-9]{4}$'
|| date_month NOT REGEXP '^[0-9]{2}$'
|| date_day NOT REGEXP '^[0-9]{2}$' THEN
RETURN FALSE;
END IF;
-- Finally, check if date itself is ok, like 2014-02-30 isn't ok:
IF DATE(CONCAT(date_year, '-', date_month, '-', date_day)) IS NULL THEN
RETURN FALSE;
END IF;
RETURN TRUE;
END//
DELIMITER ;
As you can see, we have three cases, when date validating fails:
First, if there are no proper delimiters (which are -). Then year, month and day just can't be found
Second, if extracted year, month and day part are just bad (for instance, date was foo-bar-baz). That's why we can't use date functions to extract those parts and so we have to use string functions.
Third - finally, if our date parts seems to be good, there still may be false result because of invalid combination (2014-13-01 has wrong month, for example).
Seems to be a solution
There is, however, STR_TO_DATE() function which may look like solution. Unfortunately, it will pass date parts which are not in corresponding format (such as 2014-1-1) - thus, it can't be used for direct format validation. That is why I used separate stored function instead.
What will be passed
All YYYY-MM-DD dates, which are correct in terms of MySQL, will be passed. That is, early dates, such as '0001-01-01' are correct :
mysql> SELECT VALIDATE_DATE('0001-01-01');
+-----------------------------+
| VALIDATE_DATE('0001-01-01') |
+-----------------------------+
| 1 |
+-----------------------------+
1 row in set (0.00 sec)
And, in fact, they should be correct, because they are valid for MySQL. However, such things as 001-01-01 won't be passed even despite fact, that such strings are correct for MySQL dates too:
mysql> SELECT VALIDATE_DATE('001-01-01'), DATE('001-01-01');
+----------------------------+-------------------+
| VALIDATE_DATE('001-01-01') | DATE('001-01-01') |
+----------------------------+-------------------+
| 0 | 0001-01-01 |
+----------------------------+-------------------+
1 row in set (0.00 sec)
And that is derived from your format expectations - you should filter all the things, which do not have YYYY-MM-DD format exactly, thus, you'll have such results.
I'm working with a large set of legacy data (converted from a flat-file db), where a field is formatted as the last 2 digits of the year the record was entered, followed by a 4 digit increment...
e.g., the third record created in 1998 would be "980003", and the eleventh record created in 2004 would be "040011".
i can not change these values - they exist through their company, are registered with the state, clients, etc. I know it'd be great to separate out the year and the rest of it into separate columns, but that's not possible. i can't even really do it "internally" since each row has about 300 fields that are all sortable, and they're very used to working with this field as a record identifier.
so i'm trying to implement a MySQL UDF (for the first time) to sort. The query executes successfully, and it allows me to "select whatever from table order by custom_sort(whatever)", but the order is not what i'd expect.
Here's what I'm using:
DELIMITER //
CREATE FUNCTION custom_sort(id VARCHAR(8))
RETURNS INT
READS SQL DATA
DETERMINISTIC
BEGIN
DECLARE year VARCHAR(2);
DECLARE balance VARCHAR(6);
DECLARE stringValue VARCHAR(8);
SET year = SUBSTRING(0, 2, id);
SET balance = SUBSTRING(2, 6, id);
IF(year <= 96) THEN
SET stringValue = CONCAT('20', year, balance);
ELSE
SET stringValue = CONCAT('19', year, balance);
END IF;
RETURN CAST(stringValue as UNSIGNED);
END//
The records only go back to 96 (thus the arbitrary "if first 2 characters are less than 96, prepend '20' otherwise prepend '19'). I'm not thrilled with this bit, but don't believe that's where the core problem is.
To throw another wrench in the works, it turns out that 1996 and 1997 are both 5 digits, following the same pattern described above but instead of a 4 digit increment, it's a 3 digit increment. Again, I suspect this will be a problem, but is not the core problem.
An example of the returns I'm getting with this custom_sort:
001471
051047
080628
040285
110877
020867
090744
001537
051111
080692
040349
110941
020931
090808
001603
051175
I really have no idea what I'm doing here and have never used MySQL for a UDF like this - any help would be appreciated.
TYIA
/EDIT typo
/EDIT 2 concat needed "year" value added - still getting same results
You have some problems with your substrings, and the cast to int at the end makes it sort values with more digits at the end, not by year. This should work better;
DELIMITER //
CREATE FUNCTION custom_sort(id VARCHAR(8))
RETURNS VARCHAR(10)
READS SQL DATA
DETERMINISTIC
BEGIN
DECLARE year VARCHAR(2);
DECLARE balance VARCHAR(6);
DECLARE stringValue VARCHAR(10);
SET year = SUBSTRING(id, 1, 2);
SET balance = SUBSTRING(id, 3, 6);
IF(year <= 96) THEN
SET stringValue = CONCAT('20', year, balance);
ELSE
SET stringValue = CONCAT('19', year, balance);
END IF;
RETURN stringValue;
END//
DELIMITER ;
This can be simplified a bit to;
DELIMITER //
CREATE FUNCTION custom_sort(id VARCHAR(8))
RETURNS varchar(10)
DETERMINISTIC
BEGIN
IF(SUBSTRING(id, 1, 2) <= '96') THEN
RETURN CONCAT('20', id);
ELSE
RETURN CONCAT('19', id);
END IF;
END//
DELIMITER ;
Duration = isnull(FunctionA(DateA,DateB),'')
The Function above calculates number of days and if day is null it displays
the value 0 instead of blank value
How can I change the above code to so that it shows blank and not 0 for value null?
If your function returns an integer the result from isnull will also be an integer. In the case the return value is null you will have an implicit conversion to integer for '' and that will be 0.
Try this:
declare #xx int
select isnull(#xx,'')
Result:
-----------
0
You can have the space if you first cast the return value from your function to varchar.
declare #xx int
select isnull(cast(#xx as varchar(10)),'')
Result:
----------
.
If your function returns 0 instead of null you can use nullif to get a null value before you cast to varchar.
declare #xx int = 0
select isnull(cast(nullif(#xx, 0) as varchar(10)),'')
Summary:
You need this:
Duration = isnull(cast(FunctionA(DateA,DateB) as varchar(10)),'')
or this
Duration = isnull(cast(nullif(FunctionA(DateA,DateB), 0) as varchar(10)),'')
If Duration is datatype int then you can't change that to an empty string (blank). You'll either have to change that to a string datatype (varchar for instance) or be okay with 0. int can either be NULL (if it is allowed) or a valid integer value. A blank string is not a valid integer value.
I use case statements and casting to do this.
Example:
case when columnX <> 0 then cast(columnX as nvarchar) else '' end
Basically, you're changing your numeric to show either as a character or a blank. You will have to do all your math before you change to nvarchar though, because outside of this, it becomes a string. It would be helpful if BLANK was a command and worked with numeric values though.
Hope this helps someone.
Is FunctionA returning 0 instead of null? The code you've written may be ok, but if FunctionA never returns null then...
You could declare Duration as a sql_variant datatype and allow implicit conversion to occur
so something like this should work
declare #DURATION sql_variant
select COALESCE(#DURATION, '')
set #DURATION=1
select COALESCE(#DURATION, '')
I'm writing some stored procedures in SQL Server 2008. Is the concept of optional input parameters possible here?
I suppose I could always pass in NULL for parameters I don't want to use, check the value in the stored procedure, and then take things from there, but I was interested if the concept is available here.
You can declare it like this:
CREATE PROCEDURE MyProcName
#Parameter1 INT = 1,
#Parameter2 VARCHAR (100) = 'StringValue',
#Parameter3 VARCHAR (100) = NULL
AS
/* Check for the NULL / default value (indicating nothing was passed) */
if (#Parameter3 IS NULL)
BEGIN
/* Whatever code you desire for a missing parameter */
INSERT INTO ........
END
/* And use it in the query as so */
SELECT *
FROM Table
WHERE Column = #Parameter
Yes, it is. Declare the parameter as so:
#Sort varchar(50) = NULL
Now you don't even have to pass the parameter in. It will default to NULL (or whatever you choose to default to).
In SQL Server 2014 and above at least, you can set a default, and it will take that and not error when you do not pass that parameter.
Partial example: the third parameter is added as optional. Execution (exec) of the actual procedure with only the first two parameters worked fine.
exec getlist 47,1,0
create procedure getlist
#convId int,
#SortOrder int,
#contestantsOnly bit = 0
as
The default mentioned in previous answers only works for simple cases. In more complicated cases, I use an IF clause near the beginning of the stored procedure to provide a value, if the parameter is NULL or empty and calculations are required.
I often use optional parameters in the WHERE clause, and discovered that SQL does not short circuit logic, so use a CASE statement to make sure not to try to evaluate NULL or empty dates or unique identifiers, like so:
CREATE Procedure ActivityReport
(
#FromDate varchar(50) = NULL,
#ToDate varchar(50) = NULL
)
AS
SET ARITHABORT ON
IF #ToDate IS NULL OR #ToDate = '' BEGIN
SET #ToDate = CONVERT(varchar, GETDATE(), 101)
END
SELECT ActivityDate, Details
FROM Activity
WHERE
1 = CASE
WHEN #FromDate IS NULL THEN 1
WHEN #FromDate = '' THEN 1
WHEN ActivityDate >= #FromDate AND ActivityDate < DATEADD(DD,1,#ToDate) THEN 1
ELSE 0
END