I am trying to create T-SQL function from Northwind to return new table, that will containt ProductID, ProductName, UnitsInStock and new column indicating if there are more UnitsInStock than function parameter.
Example: Let's have table of 2 products. First has 10 units in stock, second has 5. So function with parameter 6 should return:
1, Product1, 10, YES
2, Product2, 5, NO
Here's my non working code sofar :(
CREATE FUNCTION dbo.ProductsReorder
(
#minValue int
)
RETURNS #tabvar TABLE (int _ProductID, nvarchar _ProductName, int _UnitsInStock, nvarchar _Reorder)
AS
BEGIN
INSERT INTO #tabvar
SELECT ProductID, ProductName, UnitsInStock, Reorder =
CASE
WHEN UnitsInStock > #minValue THEN "YES"
ELSE "NO"
END
FROM Products
RETURN
END
T-SQL gives me this not really helpful answer: "Column, parameter, or variable#1: Cannot find data type _ProductID". I googled but I found gazillion different issues for such a result.
I dunno if it's good to use CASE here, I have a little Oracle background and decode function was great for these issues.
it's an easy answer -- especially as you are from oracle
the table definition in your function is the wrong way round.
Replace:
#tabvar TABLE (int _ProductID, nvarchar _ProductName, int _UnitsInStock, nvarchar _Reorder)
with something like
#tabvar TABLE ([_ProductID] INT, [_ProductName] NVARCHAR(50), [_UnitsInStrock] INT, [_Reorder] NVARCHAR(50))
In sql server the types come after the column names
In your table definition, you should put the column name first, then the data type. For example
_UnitsInStock int, ...
also, the NVARCHAR data type needs a length value.
_ProductName nvarchar(20)
Related
I have a table ConsoleGames wherein all columns are of type varchar(50). When I try to create a new table console_games by amending existing datatypes by using the query:
CREATE TABLE console_games
(
game_rank integer,
game_name varchar(1200),
platform_name varchar(1200),
game_year integer,
genre varchar(200),
publisher varchar(1200),
na_sales float,
eu_sales float,
jp_sales float,
other_sales float
)
INSERT INTO console_games
SELECT *
FROM [dbo].[RAWConsoleGames]
I get the following error message:
Msg 245, Level 16, State 1, Line 17
Conversion failed when converting the varchar value 'SAT' to data type int.
When I look into the data in the table the value 'SAT' is in a column for which I am not changing the datatype. 'SAT' value exists in the Platform column which is of varchar type and I am not trying to change the type to int.
Any help will be appreciated.
Thanks
Clearly 'SAT' is not and will never convert to an INT.
Always best to specify the columns to insert ... things change
Now, if the source data is suspect, add a try_convert(). If the conversion fails, a null value will be returned
I don't know the column names of your source, so I substituted SomeColN
INSERT INTO console_games
SELECT try_convert(integer ,SomeCol1)
,try_convert(varchar(1200),SomeCol2)
,try_convert(varchar(1200),SomeCol3)
,try_convert(integer ,SomeCol4)
,try_convert(varchar(200) ,SomeCol5)
,try_convert(varchar(1200),SomeCol6)
,try_convert(float ,SomeCol7)
,try_convert(float ,SomeCol8)
,try_convert(float ,SomeCol9)
,try_convert(float ,SomeCol10)
FROM [dbo].[RAWConsoleGames]
Just for fun, try:
Select try_convert(int,'SAT')
Select try_convert(int,'25.25')
Select try_convert(int,'25')
You should always define the list of columns you're inserting into, and you should also always define the list of columns you're selecting from. Furthermore, I'd recommend to explicitly do any type conversions instead of leaving that up to SQL Server - if you do it yourself, you know when and what you're doing.
So I'd write that statement like this:
-- **DEFINE** the list of columns you're inserting into
INSERT INTO console_games (rank, name, Platform, year, genre, publisher,
sales, eu_sales, jp_sales, other_sales)
-- **DEFINE** the list of columns you're selecting, and any conversions
SELECT
game_rank, game_name, platform_name,
CAST(game_year AS VARCHAR(50)), genre,
publisher,
CAST(na_sales AS VARCHAR(50)),
CAST(eu_sales AS VARCHAR(50)),
CAST(jp_sales AS VARCHAR(50)),
CAST(other_sales AS VARCHAR(50))
FROM
[dbo].[RAWConsoleGames]
I am trying to create view by filtering some table, and include some converted to different type column into select list. View filter excludes from result set rows in which this column can not be converted to that type. Then I select rows from this view and filter rows using this converted column. And I always get error Conversion failed when converting the nvarchar value '2aaa' to data type int
SQL Fiddle
MS SQL Server 2008 Schema Setup:
create table _tmp_aaa (id int identity(1, 1), value nvarchar(max) not null)
go
insert _tmp_aaa (value) values ('1111'), ('11'), ('2aaa')
go
create view _tmp_v_aaa
as
select id, cast(value as int) as value from _tmp_aaa where value like '1%'
go
Query 1:
select * from _tmp_v_aaa where value = 11
Are there any workarounds?
Add to your view ISNUMERIC to check if string is numeric value:
CREATE VIEW _tmp_v_aaa
AS
SELECT
id,
[value] = CAST((CASE WHEN ISNUMERIC([value]) = 1 THEN [value] ELSE NULL END) AS INT)
FROM _tmp_aaa
WHERE [value] LIKE '1%'
AND ISNUMERIC([value]) = 1
I tried some tricks... Obviously the optimizer tries to hand down your where criterium where it is not yet tranformed. This is one problem to be solved with a. multi-statement function. Their biggest disadvantage is the advantage in this case: the optimizer will not look into it, but just take their result "as is":
create function fn_tmp_v_aaa()
returns #tbl table(id INT, value INT)
as
BEGIN
INSERT INTO #tbl
select id, cast(value as int) as value from _tmp_aaa where value like '1%'
RETURN;
END
select * from dbo.fn_tmp_v_aaa() where value=11;
If you look at the execution plan , predicates are passed down to the table something like....
And your query gets translated to something like .....
select id, cast(value as int) as value
from tmp_aaa
where CONVERT(INT, value,0) like '1%'
AND CONVERT(INT, value,0) = CONVERT(INT, 11,0)
Now if you run this query you will get the same error you get when you query against the view.
Conversion failed when converting the nvarchar value '2aaa' to data type int.
When the predicate CONVERT(INT, value,0) like '1%' is converted , you have INT on one side of the expressions and varchar on another, INT being the higher precedence, sql server tries to convert whole expression to INT and fails hence the error message.
I have a Table with a computed column that uses a scalar function
CREATE FUNCTION [dbo].[ConvertToMillimetres]
(
#fromUnitOfMeasure int,
#value decimal(18,4)
)
RETURNS decimal(18,2)
WITH SCHEMABINDING
AS
BEGIN
RETURN
CASE
WHEN #fromUnitOfMeasure=1 THEN #value
WHEN #fromUnitOfMeasure=2 THEN #value * 100
WHEN #fromUnitOfMeasure=3 THEN #value * 1000
WHEN #fromUnitOfMeasure=4 THEN #value * 25.4
ELSE #value
END
END
GO
The table has this column
[LengthInMm] AS (CONVERT([decimal](18,2),[dbo].[ConvertToMillimetres]([LengthUnitOfMeasure],[Length]))) PERSISTED,
Assuming that the [Length] on the Table is 62.01249 and [LengthUnitOfMeasure] is 4 the LengthInMm computed value comes with 1575.11 but when i run the function directly like
SELECT [dbo].[ConvertToMillimetres] (4, 62.01249) GO
It comes with 1575.12
[Length] column is (decimal(18,4, null))
Can anyone tell why this happens?
I'm not sure this really counts as an answer, but you've perhaps got a problem with a specific version of SQL?
I just had a go at replicating it (on a local SQL 2014 install) and got the following:
create table dbo.Widgets (
Name varchar(20),
Length decimal(18,4),
LengthInMm AS [dbo].[ConvertToMillimetres] (4, Length) PERSISTED
)
insert into dbo.Widgets (Name, Length) values ('Thingy', 62.01249)
select * from dbo.Widgets
Which gives the result:
Name Length LengthInMm
-------------------- --------------------------------------- ---------------------------------------
Thingy 62.0125 1575.12
(1 row(s) affected)
Note that your definition uses [LengthInMm] AS (CONVERT([decimal](18,2),[dbo].[ConvertToMillimetres]([LengthUnitOfMeasure],[Length]))) PERSISTED, but that doesn't seem to make a difference to the result.
I also tried on my PC (Microsoft SQL Server 2012 - 11.0.2100.60 (X64)). Works fine:
CREATE TABLE dbo.data(
LengthUnitOfMeasure INT,
[Length] decimal(18,4),
[LengthInMm] AS (CONVERT([decimal](18,2),[dbo].[ConvertToMillimetres]([LengthUnitOfMeasure],[Length]))) PERSISTED
)
INSERT INTO dbo.data (LengthUnitOfMeasure, [Length])
SELECT 4, 62.01249
SELECT *
FROM dbo.data
/*
RESULT
LengthUnitOfMeasure Length LengthInMm
4 62.0125 1575.12
*/
I think, I found the answer:
Lets see what you are saying:
There is a column with decimal(18,4) data type.
There is a calculated column which depend on this column.
The result differs when you select the calculated field and when you provide the same result manually. (Right?)
Sorry, but the input parameters are not the same:
The column in the table is decimal(18,4). The value you are provided manually is decimal(7,5) (62.01249)
Since the column in the table can not store any values with scale of 5, the provided values will not be equal. (Furthermore there is no record in the table with the value of 62.01249 in the Length column)
What is the output when you query the [Length] column from the table? Is it 62.0124? If yes, then this is the answer. The results can not be equal since the input values are not equal.
To be a bit more specific: 62.01249 will be casted (implicit cast) to 62.0125.
ROUND(25.4 * 62.0124, 2) = 1575.11
ROUND(25.4 * 62.0125, 2) = 1575.12
EDIT Everybody who tried to rebuild the schema made the same mistake (Including me). When we (blindly) inserted the values from the original question into our instances, we inserted the 62.01249 into the Length column -> the same implicit cast occured, so we have the value 62.0125 in our tables.
I am stuck on converting a varchar column UserID to INT. I know, please don't ask why this UserID column was not created as INT initially, long story.
So I tried this, but it doesn't work. and give me an error:
select CAST(userID AS int) from audit
Error:
Conversion failed when converting the varchar value
'1581............................................................................................................................' to data type int.
I did select len(userID) from audit and it returns 128 characters, which are not spaces.
I tried to detect ASCII characters for those trailing after the ID number and ASCII value = 0.
I have also tried LTRIM, RTRIM, and replace char(0) with '', but does not work.
The only way it works when I tell the fixed number of character like this below, but UserID is not always 4 characters.
select CAST(LEFT(userID, 4) AS int) from audit
You could try updating the table to get rid of these characters:
UPDATE dbo.[audit]
SET UserID = REPLACE(UserID, CHAR(0), '')
WHERE CHARINDEX(CHAR(0), UserID) > 0;
But then you'll also need to fix whatever is putting this bad data into the table in the first place. In the meantime perhaps try:
SELECT CONVERT(INT, REPLACE(UserID, CHAR(0), ''))
FROM dbo.[audit];
But that is not a long term solution. Fix the data (and the data type while you're at it). If you can't fix the data type immediately, then you can quickly find the culprit by adding a check constraint:
ALTER TABLE dbo.[audit]
ADD CONSTRAINT do_not_allow_stupid_data
CHECK (CHARINDEX(CHAR(0), UserID) = 0);
EDIT
Ok, so that is definitely a 4-digit integer followed by six instances of CHAR(0). And the workaround I posted definitely works for me:
DECLARE #foo TABLE(UserID VARCHAR(32));
INSERT #foo SELECT 0x31353831000000000000;
-- this succeeds:
SELECT CONVERT(INT, REPLACE(UserID, CHAR(0), '')) FROM #foo;
-- this fails:
SELECT CONVERT(INT, UserID) FROM #foo;
Please confirm that this code on its own (well, the first SELECT, anyway) works for you. If it does then the error you are getting is from a different non-numeric character in a different row (and if it doesn't then perhaps you have a build where a particular bug hasn't been fixed). To try and narrow it down you can take random values from the following query and then loop through the characters:
SELECT UserID, CONVERT(VARBINARY(32), UserID)
FROM dbo.[audit]
WHERE UserID LIKE '%[^0-9]%';
So take a random row, and then paste the output into a query like this:
DECLARE #x VARCHAR(32), #i INT;
SET #x = CONVERT(VARCHAR(32), 0x...); -- paste the value here
SET #i = 1;
WHILE #i <= LEN(#x)
BEGIN
PRINT RTRIM(#i) + ' = ' + RTRIM(ASCII(SUBSTRING(#x, #i, 1)))
SET #i = #i + 1;
END
This may take some trial and error before you encounter a row that fails for some other reason than CHAR(0) - since you can't really filter out the rows that contain CHAR(0) because they could contain CHAR(0) and CHAR(something else). For all we know you have values in the table like:
SELECT '15' + CHAR(9) + '23' + CHAR(0);
...which also can't be converted to an integer, whether you've replaced CHAR(0) or not.
I know you don't want to hear it, but I am really glad this is painful for people, because now they have more war stories to push back when people make very poor decisions about data types.
This question has got 91,000 views so perhaps many people are looking for a more generic solution to the issue in the title "error converting varchar to INT"
If you are on SQL Server 2012+ one way of handling this invalid data is to use TRY_CAST
SELECT TRY_CAST (userID AS INT)
FROM audit
On previous versions you could use
SELECT CASE
WHEN ISNUMERIC(RTRIM(userID) + '.0e0') = 1
AND LEN(userID) <= 11
THEN CAST(userID AS INT)
END
FROM audit
Both return NULL if the value cannot be cast.
In the specific case that you have in your question with known bad values I would use the following however.
CAST(REPLACE(userID COLLATE Latin1_General_Bin, CHAR(0),'') AS INT)
Trying to replace the null character is often problematic except if using a binary collation.
This is more for someone Searching for a result, than the original post-er. This worked for me...
declare #value varchar(max) = 'sad';
select sum(cast(iif(isnumeric(#value) = 1, #value, 0) as bigint));
returns 0
declare #value varchar(max) = '3';
select sum(cast(iif(isnumeric(#value) = 1, #value, 0) as bigint));
returns 3
I would try triming the number to see what you get:
select len(rtrim(ltrim(userid))) from audit
if that return the correct value then just do:
select convert(int, rtrim(ltrim(userid))) from audit
if that doesn't return the correct value then I would do a replace to remove the empty space:
select convert(int, replace(userid, char(0), '')) from audit
This is how I solved the problem in my case:
First of all I made sure the column I need to convert to integer doesn't contain any spaces:
update data set col1 = TRIM(col1)
I also checked whether the column only contains numeric digits.
You can check it by:
select * from data where col1 like '%[^0-9]%' order by col1
If any nonnumeric values are present, you can save them to another table and remove them from the table you are working on.
select * into nonnumeric_data from data where col1 like '%[^0-9]%'
delete from data where col1 like '%[^0-9]%'
Problems with my data were the cases above. So after fixing them, I created a bigint variable and set the values of the varchar column to the integer column I created.
alter table data add int_col1 bigint
update data set int_col1 = CAST(col1 AS VARCHAR)
This worked for me, hope you find it useful as well.
Here's my setup: I am passing two arrays as XML into a SQL stored procedure.
These are:
<PhoneID Value=128/>
<PhoneID Value=129/>
<PhoneID Value=130/>
and
<AddressID Value=268/>
<AddressID Value=157/>
<AddressID Value=395/>
The Address and Phone tables look like this (pseudo-code follows):
Phone:
BIGINT PhoneID
BIGINT PhoneNumber
SMALLINT AreaCode
INT Extension
Address:
BIGINT AddressID
NVARCHAR StreetAddress
NVARCHAR CountryName
NVARCHAR City
BIGINT Zip
My dilemma is this:
I need to walk through the passed in arrays in lock-step to return
ContactInfo:
BIGINT PhoneNumber
NVARCHAR StreetAddress
BIGINT ZipCode
i.e. I need to return one ContactInfo built from Phone where PhoneID = 128 and Address where AddressID = 268, and another where PhoneID = 129 and AddressID = 268, etc.
My big question is: How do I walk two xml arrays in lock-step in sql?
This is in SQL Server 2008 R2.
Thanks everyone :)
The only viable approach I see is trying to do this:
parse/shred your XML arrays into temporary tables for phones and addresses
those temporary tables have an ID INT IDENTITY defined
when inserting into a fresh temporary table with this INT IDENTITY, both sets of data will get consecutive numbering (1, 2, 3, 4......)
you can then join the two temporary tables on their ID and should get your "lock-step" behavior:
So in code, this would look something like (mind you: your XML is invalid, too - the attribute values need to be in double quotes for this to work!):
DECLARE #phones XML = '<PhoneID Value="128"/><PhoneID Value="129"/><PhoneID Value="130"/>'
DECLARE #Addresses XML = '<AddressID Value="268"/><AddressID Value="157"/><AddressID Value="395"/>'
DECLARE #phoneTable TABLE (ID INT IDENTITY PRIMARY KEY, PhoneID INT)
DECLARE #AddressTable TABLE (ID INT IDENTITY PRIMARY KEY, AddressID INT)
INSERT INTO #phoneTable(PhoneID)
SELECT
PHID.value('(#Value)', 'int')
FROM
#phones.nodes('/PhoneID') AS PH(PHID)
INSERT INTO #AddressTable(AddressID)
SELECT
ADRID.value('(#Value)', 'int')
FROM
#addresses.nodes('/AddressID') AS AD(ADRID)
SELECT p.*, a.*
FROM #phoneTable p
INNER JOIN #addresstable a ON p.ID = a.ID
Does that work for you?? From here, you could then insert the data into your actual working tables and do any additional lookups or processing.
Turns out it's possible to in T-SQL, like so:
CREATE PROCEDURE [dbo].[XmlParsingProcedure]
#XmlArgs XML
AS
SELECT * FROM SomeTable
WHERE (s.XmlArgs IN (select x.XmlArgs.value('#Value','smallint') FROM #XmlArgs.nodes('//XmlArgs') as x(XmlArgs)))
However, intrinsic knowledge of the XML is required.
The above implies the XML schema:
<XmlArgs Value="some value"/>
<XmlArgs Value="some other value"/>
<XmlArgs Value="yet another value"/>
where both, 'XmlArgs' and 'Value' are case sensitive.