Error converting to float -- How to screen out bad values - sql-server

I am working in SQL Server 2008. I have a table with all columns set up as varchar(255), which is necessary since I do data validation on this table. I have the following query on this table:
SELECT
col_1
FROM
table_A
WHERE
col_1 NOT LIKE '%[^.0123456789]%'
AND CAST(col_1 AS float) <= 2.5
I'm getting an error stating that it can't convert one of my table values to data type float. The offending value is '3269e+'. I don't understanding why this value causes an error. Wouldn't this value have been excluded by the first condition in the WHERE clause? If I'm doing something wrong, how should I re-write this query?

Instead of trying to parse out the string using a like statement, you can use ISNUMERIC. This does have some false positives, which people discuss on the comments of the MSDN page. In your example, it could be:
SELECT
col_1
FROM
table_A
WHERE
ISNUMERIC(col_1) = 1
AND CAST(col_1 AS float) <= 2.5

You can do this with a cte to first separate the valid rows, then apply your math.
with ValidRows as
(
SELECT
col_1
FROM table_A
WHERE col_1 NOT LIKE '%[^.0123456789]%'
)
select *
from ValidRows
WHERE CAST(col_1 AS float) <= 2.5

No it wont be taken out by the first condition. that order of execution is done by query optimizer.
Tf you want to make it work you could use a subquery like this:
SELECT *
FROM (
SELECT col_1
FROM table_A
WHERE col_1 NOT LIKE '%[^.0123456789]%' ) t
WHERE CAST(col_1 AS float) <= 2.5
Here is a working version with data similar to yours:
DECLARE #value AS VARCHAR(MAX) = '1'
DECLARE #value1 AS VARCHAR(MAX) = '2.1'
DECLARE #value2 AS VARCHAR(MAX) = '1.5'
DECLARE #value3 AS VARCHAR(MAX) = '32344'
DECLARE #value4 AS VARCHAR(MAX) = '23324e+'
DECLARE #value5 AS VARCHAR(MAX) = '23434334e+'
select * from(
select #value as v
UNION
select #value1
UNION
select #value2
UNION
select #value3
UNION
select #value4
UNION
select #value5
) t
where t.v NOT LIKE '%[^.0123456789]%'
AND CAST(t.v AS float) <= 2.5

Related

Accumulating (concatenate) values in variable does not work

I'm trying to accumulate values into a variable in SQL Server>=2012.
It works in case 1 below, but in case 2 I get the answer ",CD" instead of the expected ",EF,AB,CD" Why?
In MMS:
USE MyDB
GO
-- Create a simple table
CREATE TABLE Tbl1 (Code VARCHAR(2), So TINYINT NULL)
INSERT INTO Tbl1 VALUES('AB', 10)
INSERT INTO Tbl1 VALUES('CD', NULL)
INSERT INTO Tbl1 VALUES('EF', 5)
GO
-- Case 1
DECLARE #MyVar VARCHAR(255) = ''
SELECT #MyVar=#MyVar + ',' + Code FROM Tbl1 ORDER BY So
SELECT #MyVar
GO
-- Case 2
DECLARE #MyVar VARCHAR(255) = ''
SELECT #MyVar=#MyVar + ',' + Code FROM Tbl1 ORDER BY ISNULL(So, 255)
SELECT #MyVar
GO
The explanation is in the documentation:
Don't use a variable in a SELECT statement to concatenate values (that
is, to compute aggregate values). Unexpected query results may occur.
Because, all expressions in the SELECT list (including assignments)
aren't necessarily run exactly once for each output row.
There are opinions (but not in the official docs), stating that without an ORDER BY clause (and/or a DISTINCT clause) the aggregation works as you expect.
If you are using SQL Server 2017+, you may use STRING_AGG() to build the expected output:
DECLARE #MyVar VARCHAR(255) = ''
SELECT #MyVar = STRING_AGG(Code, ',') WITHIN GROUP (ORDER BY ISNULL(So, 255))
FROM Tbl1
SELECT #MyVar

Substring is slow with while loop in SQL Server

One of my table column stores ~650,000 characters (each value of the column contains entire table). I know its bad design however, Client will not be able to change it.
I am tasked to convert the column into multiple columns.
I chose to use dbo.DelimitedSplit8K function
Unfortunately, it can only handle 8k characters at max.
So I decided to split the column into 81 8k batches using while loop and store the same in a variable table (temp or normal table made no improvement)
DECLARE #tab1 table ( serialnumber int, etext nvarchar(1000))
declare #scriptquan int = (select MAX(len (errortext)/8000) from mytable)
DECLARE #Counter INT
DECLARE #A bigint = 1
DECLARE #B bigint = 8000
SET #Counter=1
WHILE ( #Counter <= #scriptquan + 1)
BEGIN
insert into #tab1 select ItemNumber, Item from dbo.mytable cross apply dbo.DelimitedSplit8K(substring(errortext, #A, #B), CHAR(13)+CHAR(10))
SET #A = #A + 8000
SET #B = #B + 8000
SET #Counter = #Counter + 1
END
This followed by using below code
declare #tab2 table (Item nvarchar(max),itemnumber int, Colseq varchar(10)) -- declare table variable
;with cte as (
select [etext] ,ItemNumber, Item from #tab1 -- insert table name
cross apply dbo.DelimitedSplit8K(etext,' ')) -- insert table columns name that contains text
insert into #tab2 Select Item,itemnumber, 'a'+ cast (ItemNumber as varchar) colseq
from cte -- insert values to table variable
;WITH Tbl(item, colseq) AS(
select item, colseq from #tab2
),
CteRn AS(
SELECT item, colseq,
Rn = ROW_NUMBER() OVER(PARTITION BY colseq ORDER BY colseq)
FROM Tbl
)
SELECT
a1 Time,a2 Number,a3 Type,a4 Remarks
FROM CteRn r
PIVOT(
MAX(item)
FOR colseq IN(a1,a2,a3,a4)
)p
where a3 = 'error'
gives the desired output. However, just the loop takes 15 minutes to complete and overall query completes by 27 minutes. Is there any way I can make it faster? Total row count in my table is 2. So I don't think Index can help.
Client uses Azure SQL Database so I can't choose PowerShell or Python to accomplish this either.
Please let me know if more information is needed. I tried my best to mention everything I could.

SQL Server Conversion failed varchar to int

I have a table (no.1) which has 10 columns. One of them clm01 is integer and not allowed with null values.
There is a second table (no.2) which has many columns. One of them is string type clm02. An example of this column data is 1,2,3.
I'd like to make a query like:
select *
from table1 t1, table2 t2
where t1.clm01 not in (t2.clm2)
For example in table1 I have 5 records with values in clm01 1,2,3,4,5 and in table2 I've got 1 record with value in clm02 = 1,2,3
So I would like with the query to return only the record with the value 4 and 5 in the clm01.
Instead I get:
Conversion failed when converting the varchar value '1,2,3' to data type int
Any ideas?
Use STRING_SPLIT() function to split the comma separated values, if you are using SQL Server 2016.
SELECT *
FROM table1 t1
WHERE t1.clm1 NOT IN (SELECT Value FROM table2 t2
CROSS APPLY STRING_SPLIT(t2.clm2,','))
If you are using any lower versions of SQL server write a UDF to split string and use the function in CROSS APPLY clause.
CREATE FUNCTION [dbo].[SplitString]
(
#string NVARCHAR(MAX),
#delimiter CHAR(1)
)
RETURNS #output TABLE(Value NVARCHAR(MAX)
)
BEGIN
DECLARE #start INT, #end INT
SELECT #start = 1, #end = CHARINDEX(#delimiter, #string)
WHILE #start < LEN(#string) + 1 BEGIN
IF #end = 0
SET #end = LEN(#string) + 1
INSERT INTO #output (Value)
VALUES(SUBSTRING(#string, #start, #end - #start))
SET #start = #end + 1
SET #end = CHARINDEX(#delimiter, #string, #start)
END
RETURN
END
I decided to give you a couple of options but this really is a duplicate question I see pretty often.
There are two main ways of going about the problem.
1) Use LIKE to and compare the strings but you actually have to build strings a little oddly to do it:
SELECT *
FROM
#Table1 t1
WHERE
NOT EXISTS (SELECT *
FROM #Table2 t2
WHERE ',' + t2.clm02 + ',' LIKE '%,' + CAST(t1.clm01 AS VARCHAR(15)) + ',%')
What you see is ,1,2,3, is like %,clm01value,% you must add the delimiter to the strings for this to work properly and you have to cast/convert clm01 to a char datatype. There are drawbacks to this solution but if your data sets are straight forward it could work for you.
2) Split the comma delimited string to rows and then use a left join, not exists, or not in. here is a method to convert your csv to xml and then split
;WITH cteClm02Split AS (
SELECT
clm02
FROM
(SELECT
CAST('<X>' + REPLACE(clm02,',','</X><X>') + '</X>' AS XML) as xclm02
FROM
#Table2) t
CROSS APPLY (SELECT t.n.value('.','INT') clm02
FROM
t.xclm02.nodes('X') as t(n)) ca
)
SELECT t1.*
FROM
#Table1 t1
LEFT JOIN cteClm02Split t2
ON t1.clm01 = t2.clm02
WHERE
t2.clm02 IS NULL
OR use NOT EXISTS with same cte
SELECT t1.*
FROM
#Table1 t1
WHERE
NOT EXISTS (SELECT * FROM cteClm02Split t2 WHERE t1.clm01 = t2.clm02)
There are dozens of other ways to split delimited strings and you can choose whatever way works for you.
Note: I am not showing IN/NOT IN as an answer because I don't recommend the use of it. If you do use it make sure that you are never comparing a NULL in the select etc. Here is another good post concerning performance etc. NOT IN vs NOT EXISTS
here are the table variables that were used:
DECLARE #Table1 AS TABLE (clm01 INT)
DECLARE #Table2 AS TABLE (clm02 VARCHAR(15))
INSERT INTO #Table1 VALUES (1),(2),(3),(4),(5)
INSERT INTO #Table2 VALUES ('1,2,3')

SQL Server Select only searched numbers

I have a column which contains category values like
11,2,3
114,3,2
etc.
Want to select top category from these unique numbers. I've tried to select 11th category with
select *
from product
where category like '%11%'
but this one select bot 11 and like 114. How can I only select the 11?
Declare #Search varchar(25) = '11'
Select * from product where category+',' like '%'+#Search+',%'
DECLARE #x VARCHAR(MAX) = '11,2,3 114,3,2'
SELECT 1
WHERE ',' + #x + ',' LIKE '%,11,%'
You can do it in a set based way and this is faster than like for bigger sets..Using one of the functions from here..
declare #x='11,2,3 114,3,2'
;With cte
as
(select * from dbo.substring_numbers(#x,',')
)
select * from cte where item=11

Can I use #table variable in SQL Server Report Builder?

Using SQL Server 2008 Reporting services:
I'm trying to write a report that displays some correlated data so I thought to use a #table variable like so
DECLARE #Results TABLE (Number int
,Name nvarchar(250)
,Total1 money
,Total2 money
)
insert into #Results(Number, Name, Total1)
select number, name, sum(total)
from table1
group by number, name
update #Results
set total2 = total
from
(select number, sum(total) from table2) s
where s.number = number
select from #results
However, Report Builder keeps asking to enter a value for the variable #Results. It this at all possible?
EDIT: As suggested by KM I've used a stored procedure to solve my immediate problem, but the original question still stands: can I use #table variables in Report Builder?
No.
ReportBuilder will
2nd guess you
treats #Results as a parameter
Put all of that in a stored procedure and have report builder call that procedure. If you have many rows to process you might be better off (performance wise) with a #temp table where you create a clustered primary key on Number (or would it be Number+Name, not sure of your example code).
EDIT
you could try to do everything in one SELECT and send that to report builder, this should be the fastest (no temp tables):
select
dt.number, dt.name, dt.total1, s.total2
from (select
number, name, sum(total) AS total1
from table1
group by number, name
) dt
LEFT OUTER JOIN (select
number, sum(total) AS total2
from table2
GROUP BY number --<<OP code didn't have this, but is it needed??
) s ON dt.number=s.number
I've seen this problem as well. It seems SQLRS is a bit case-sensitive. If you ensure that your table variable is declared and referenced everywhere with the same letter case, you will clear up the prompt for parameter.
You can use Table Variables in SSRS dataset query like in my code where I am adding needed "empty" records for keep group footer in fixed postion (sample use pubs database):
DECLARE #NumberOfLines INT
DECLARE #RowsToProcess INT
DECLARE #CurrentRow INT
DECLARE #CurRow INT
DECLARE #cntMax INT
DECLARE #NumberOfRecords INT
DECLARE #SelectedType char(12)
DECLARE #varTable TABLE (# int, type char(12), ord int)
DECLARE #table1 TABLE (type char(12), title varchar(80), ord int )
DECLARE #table2 TABLE (type char(12), title varchar(80), ord int )
INSERT INTO #varTable
SELECT count(type) as '#', type, count(type) FROM titles GROUP BY type ORDER BY type
SELECT #cntMax = max(#) from #varTable
INSERT into #table1 (type, title, ord) SELECT type, N'', 1 FROM titles
INSERT into #table2 (type, title, ord) SELECT type, title, 1 FROM titles
SET #CurrentRow = 0
SET #SelectedType = N''
SET #NumberOfLines = #RowsPerPage
SELECT #RowsToProcess = COUNT(*) from #varTable
WHILE #CurrentRow < #RowsToProcess
BEGIN
SET #CurrentRow = #CurrentRow + 1
SELECT TOP 1 #NumberOfRecords = ord, #SelectedType = type
FROM #varTable WHERE type > #SelectedType
SET #CurRow = 0
WHILE #CurRow < (#NumberOfLines - #NumberOfRecords % #NumberOfLines) % #NumberOfLines
BEGIN
SET #CurRow = #CurRow + 1
INSERT into #table2 (type, title, ord)
SELECT type, '' , 2
FROM #varTable WHERE type = #SelectedType
END
END
SELECT type, title FROM #table2 ORDER BY type ASC, ord ASC, title ASC
Why can't you just UNION the two resultsets?
How about using a table valued function rather than a stored proc?
It's possible, only declare your table with '##'. Example:
DECLARE ##results TABLE (Number int
,Name nvarchar(250)
,Total1 money
,Total2 money
)
insert into ##results (Number, Name, Total1)
select number, name, sum(total)
from table1
group by number, name
update ##results
set total2 = total
from
(select number, sum(total) from table2) s
where s.number = number
select * from ##results

Resources