Your approach does not take into account that month or day can be a single-digit value. If you had a row like this:
year month day
---- ----- ---
2014 7 18
your method would concatenate the parts as 2014718 and the subsequent attempt to convert that into a date would result in the error in question.
You probably meant to combine the parts like this: 20140718, which would convert to date without issues. To me, the easiest way to get to that format from numerical parts is to go like this:
calculate year * 10000 + month * 100 + day;
convert the previous result to char(8);
convert the string to date.
So, in Transact-SQL it would be like this:
CAST(CAST(year * 10000 + month * 100 + day AS char(8)) AS date)
On a different note, I cannot possibly know whether you really need to store your dates split like that, of course, but if you do insist on doing so, at least consider using integer type(s) for the parts. I realise that decimal(2,0) may serve as some kind of constraint for you that makes sure that you cannot have month or day values with more than 2 digits, but it still does not protect you from having invalid months or days. And another major point is decimal(2,0) requires more storage space than even int, let alone smallint or tinyint.
So, this would seem fine to me
year int,
month int,
day int
but if you are into saving the storage space as much as possible, you could also try this:
year smallint,
month tinyint,
day tinyint
Finally, to make sure you cannot have invalid values in those columns, you could add a check constraint like below:
ALTER TABLE tablename
ADD CONSTRAINT CK_tablename_yearmonthday CHECK
ISDATE(CAST(year * 10000 + month * 100 + day AS char(8))) = 1
;