简体   繁体   中英

SQL Server varchar conversion

Bumped into an issue.

SQL query

INSERT INTO LogItems (Date, Type, UserID, UserName, RefID, Param, OldVal, NewVal) 
VALUES (SYSDATETIMEOFFSET(), '17', '31', 'Nick', '1029', 'Discount', '0', '24.19')
, (SYSDATETIMEOFFSET(), '17', '31', 'Nick', '1029', 'CM applied', 0, 1)

getting message:

Msg 245, Level 16, State 1, Line 1
Conversion failed when converting the varchar value '24.19' to data type int.

The problem is that columns OldVal and NewVal store all kinds of data/numbers/text and is set to varchar(300).

Why is it trying to convert into 'int' if the filed is set as 'varchar'?

Thank you!

The problem is that in your VALUES table construct you have varying data in your rows. if we add some formatting, this becomes quickly apparent:

INSERT INTO LogItems (Date, Type, UserID, UserName, RefID, Param, OldVal, NewVal)
VALUES (SYSDATETIMEOFFSET(), '17', '31', 'Nick', '1029', 'Discount',  '0', '24.19'),
       (SYSDATETIMEOFFSET(), '17', '31', 'Nick', '1029', 'CM applied', 0 , 1);

Notice that the 2 last columns have different data types, the first has 2 varchar values (that are clearly numerical and thus should not be inside single quotes ( ' )), and the latter are both int values.

As you have different data types, and a column must be made up of the same data types through out, then values of a lower data type precedence are converted to the type with the highest. varchar has one of the lowest, and so the values '0' and '24.19' are implicitly converted to int s. For '0' that's fine, it's an int as well, however, the varchar representing the numerical value 24.19 cannot , hence the error.

If you don't wrap the values in single quotes, this will work, and the 1 will be implicitly converted to a decimal(4,2) .

Of course, the real problem is this:

The problem is that columns OldVal and NewVal store all kinds of data/numbers/text and is set to varchar(300).

That is a significant design flaw; fix that. A column can only be made up of one data type, and storing data that isn't meant to be in a varchar as a varchar will never work out well. varchar is NOT a one size fits all data type .

I would personally suggest storing the old row as a whole, not just the column that changed, along with the meta data you need. Then everything is store as it should be, and the "new" row is available in the original table. Especially as a solution like what you have will fall over the moment you update 2 or more columns at the same time.

This surprised me, and I've been doing SQL Server for years. Larnu has already answered very well but I feel like I can add something.

The surprise here is that although you are inserting a mixed bag of values into a varchar column, instead of implicitly converting them to varchar, SQL Server is deciding to implicitly convert them to int. This happens because there is an int in the list, and it has higher data type precedence, so SQL Server tries to cast everything to that type. If you were just inserting one row at a time, it would be fine. But because you are inserting two rows, SQL Server implictly invents a table structure for them, without checking what you actually want to do with them. From where I'm sitting, this does look kindof dumb on SQL Servers part. I'm sure it has its reasons.

I could say how you could design everything differently blah blah but sometimes people don't have that choice, so I'll try and solve your immediate problem.

You can force the cast to be made to varchar instead of int by wrapping all the values in an explicit CAST statement, like this:

INSERT INTO LogItems (Date, Type, UserID, UserName, RefID, Param, OldVal, NewVal) 
VALUES (SYSDATETIMEOFFSET(), '17', '31', 'Nick', '1029', 'Discount', Cast('0' as varchar(300)), Cast('24.19' as varchar(300)))
, (SYSDATETIMEOFFSET(), '17', '31', 'Nick', '1029', 'CM applied', Cast(0 as varchar(300)), Cast(1 as varchar(300)));

This may look crazy but if for example your INSERT statements were being generated in code, this is how to generate them and make the casting happen the way you want it to.

Here's a SQL Fiddle of the problem, which throws an error: http://sqlfiddle.com/#!18/dd7fe8/1

Here's a SQL Fiddle of my solution http://sqlfiddle.com/#!18/dd7fe8/4

Thank you all for your kind comments!

To answer some of the concerns, I did not do parameterized because all data is tightly (.) validated, I wish I could use multiple columns for each data type, or write old/new rows, the constrain is that this is log table that logs historical changes to all the fields on different pages. and users can change them multiple times. 0 and 1 I had as kinda switch (yes/no) flag when user completes certain action. Everything is logged.

And yes, when I put in quotes '0' and '1' and went through. Thank you Larnu and Codeulike for the answer and the feedback!

I felt it is weird that SQL is fine with 'xxxx' and 1 on separate queries but not fine when those values are passed in one query.

INSERT INTO LogItems (Date, Type, UserID, UserName, RefID, Param, OldVal, NewVal) VALUES (SYSDATETIMEOFFSET(), '17', '31', 'Nick', '1029', 'Discount', '0', '24.19'), (SYSDATETIMEOFFSET(), '17', '31', 'Nick', '1029', 'CM applied', '0', '1');

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM