Get the greatest value in a nvarchar column - sql

I'm developing a database with SQL Server 2012 SP2.
I have a table with a NVARCHAR(20) column, and it will have numbers: "000001", "000002", etc.
I need to get the greatest value in that column and convert it to int. How can I do it?
I have found that I can convert a nvarchar to int with this sql sentence:
SELECT CAST(YourVarcharCol AS INT) FROM Table
But I don't know how can I get the max value in that column because the numbers are nvarchar.
UPDATE:
By the way, this column is NVARCHAR because I need to store text on it. I'm testing my solution and I need to store ONLY numbers to test it.

If your numbers are padded like in the example given and all have the same width, you can just sort them alphanumerically and then cast the max-value to INT or BIGINT (depending on your numbers range).
If there are very many rows it was much faster, especially if there is an index on this column...
Something like
SELECT TOP 1 * FROM YourTable
ORDER BY NumberColumn DESC
or, if you need the max-value only:
SELECT MAX(NumberColumn) FROM YourTable
If you have to deal with negative numbers or differently padded numbers you have to cast them first
SELECT TOP 1 * FROM YourTable
ORDER BY CAST(NumberColumn AS INT) DESC
or
SELECT MAX(CAST(NumberColumn AS INT)) FROM YourTable
Please note:
If you've got very many rows, the second might get rather slow. Read about sargable
If your NumberColumn might include invalid values, you have to check, Read about ISNUMERIC().
The best solution - in any case - was to use an indexed numeric column to store these values

Try this one...
I think MAX is enough.
SELECT max(CAST(YourVarcharCol AS INT)) FROM Table

Try this
SELECT MAX(t.Y) from (SELECT CAST(YourVarcharCol AS INT) as Y FROM Table) t

You should try this on finding the highest value:
SELECT MAX(CAST(YourVarcharCol AS INT)) AS FROM Table

If all the data follow the same padding and formatting pattern, a simple max(col) would do.
However, if not, you have to cast the values to int first. Searching on a columns cast to some other datatype will not use an index, if there's any, but will scan the whole table instead. It may or may not be OK for you, depending on requirements and number of rows in the table. If performance is what you need, then create a calculated column as try_cast( col as int), and create an index on it.
Note that you should not use cast, but try_cast instead, to guard against values that can't be cast (if you use a datatype to store something which is essencially of another datatype, it always opens up a possibility for errors).
Of couse, if you can change the original column's type to int, it would be the best.

This will return max int value
SELECT MAX(IIF(ISNUMERIC(YourVarcharCol) = 1, YourVarcharCol, '0') * 1) FROM Table

You can use like this
select max(cast(ColumnName AS INT ))from TableName

Related

SQL Server subquery behaviour

I have a case where I want to check to see if an integer value is found in a column of a table that is varchar, but is a mix of values that can be integers some are just strings. My first thought was to use a subquery to just select the rows with numeric-esque values. The setup looks like:
CREATE TABLE #tmp (
EmployeeID varchar(50) NOT NULL
)
INSERT INTO #tmp VALUES ('aa1234')
INSERT INTO #tmp VALUES ('1234')
INSERT INTO #tmp VALUES ('5678')
DECLARE #eid int
SET #eid = 5678
SELECT *
FROM (
SELECT EmployeeID
FROM #tmp
WHERE IsNumeric(EmployeeID) = 1) AS UED
WHERE UED.EmployeeID = #eid
DROP TABLE #tmp
However, this fails, with: "Conversion failed when converting the varchar value 'aa1234' to data type int.".
I don't understand why it is still trying to compare #eid to 'aa1234' when I've selected only the rows '1234' and '5678' in the subquery.
(I realize I can just cast #eid to varchar but I'm curious about SQL Server's behaviour in this case)
You can't easily control the order things will happen when SQL Server looks at the query you wrote and then determines the optimal execution plan. It won't always produce a plan that follows the same logic you typed, in the same order.
In this case, in order to find the rows you're looking for, SQL Server has to perform two filters:
identify only the rows that match your variable
identify only the rows that are numeric
It can do this in either order, so this is also valid:
identify only the rows that are numeric
identify only the rows that match your variable
If you look at the properties of this execution plan, you see that the predicate for the match to your variable is listed first (which still doesn't guarantee order of operation), but in any case, due to data type precedence, it has to try to convert the column data to the type of the variable:
Subqueries, CTEs, or writing the query a different way - especially in simple cases like this - are unlikely to change the order SQL Server uses to perform those operations.
You can force evaluation order in most scenarios by using a CASE expression (you also don't need the subquery):
SELECT EmployeeID
FROM #tmp
WHERE EmployeeID = CASE IsNumeric(EmployeeID) WHEN 1 THEN #eid END;
In modern versions of SQL Server (you forgot to tell us which version you use), you can also use TRY_CONVERT() instead:
SELECT EmployeeID
FROM #tmp
WHERE TRY_CONVERT(int, EmployeeID) = #eid;
This is essentially shorthand for the CASE expression, but with the added bonus that it allows you to specify an explicit type, which is one of the downsides of ISNUMERIC(). All ISNUMERIC() tells you is if the value can be converted to any numeric type. The string '1e2' passes the ISNUMERIC() check, because it can be converted to float, but try converting that to an int...
For completeness, the best solution - if there is an index on EmployeeID - is to just use a variable that matches the column data type, as you suggested.
But even better would be to use a data type that prevents junk data like 'aa1234' from getting into the table in the first place.

SQL Order By while ignoring leading characters

Im looking for some help with regards to ordering by a numeric value but the numeric values have leading chars (-).
For example
Column Order #
---5
--8
-6
A simple ORDER BY Order# DESC gives me an unordered output. How do you ignore the leading chars when sorting data such as the Order # above?
Using the H2 database syntax and functions, and a table declared as:
create table foo(bar varchar(10));
with the rows:
'---2'
'--3'
'-1'
the following query:
select bar from foo
order by cast (replace(bar, '-') as int);
gives me the results:
BAR
-1
---2
--3
which seems to be what you're after.
Disclaimer: this is possibly not the best way to do this, since the computed value is not indexed. For just ordering a reasonably-sized result set it doesn't matter.
The solution I came up with is similar to the answer given,
I can just cast the column data to an int and sort it the way ie
ORDER BY CAST((ColumnName) AS INT) DESC;
this sucessfully ignores the leading chars and sorts it by the numbers

Reg: Auto incrementing a value in SQL Table

I got a value in a field called it (EmployeeDetailKey - varchar(10)) with sequential values such as
00001, 00002, 00003....
It is in a table Employeedetail. When ever a new Employee detail has to be inserted I have to get the max(EmployeeDetailKey) and increment by 1 and store it back. If I have 10 employeedetail records that need to be inserted then the same procedure has to follow.
If the max(EmployeeDetailKey) = 00003 then after inserting 10 records
it has to be 00013. Later on after inserting let us say 100 records it has to
be 00113.
How can I do it in the form of MS-SQL statement.
Please note the column cannot be identity type.
Just add an identity column to your table. I would suggest something like:
IntEmployeeDetailKey int not null identity(1, 1) primary key,
. . .
Then add a computed column:
EmployeeDetailKey as (right(('00000' + cast(IntEmployeeDetailKey as varchar(10)), 5)
Then SQL Server will do the incrementing automatically. And you can get the value out as a zero-padded string.
If you prefer a solution without changing the table structure, then:
Cast your zero-padded string value to int. This is easy in SQl server, as it will easily convert such strings to numbers:
SELECT CAST('00003' AS int)
This will return integer value of 3.
Find MAX()
Just perform MAX() on column you've just converted to string, like...
SELECT MAX(CAST(mycolumn AS int)) FROM mytable
Actually, you don't have to do a conversion, as SQL server will sort the values correctly in original string representation.
Increment
This is easy, since you now have the integer value, so...
SELECT MAX(CAST(mycolumn AS int)) + 1 FROM mytable
Convert it back to zero-padded string
SQL Server 2008 is a bit tricky to tame here, since left-padding is not his speciality. However, starting from in SQL Server 2012, there is a FORMAT function available, so, you can use...
SELECT FORMAT(MAX(CAST(mycolumn AS int)) + 1, '00000') FROM mytable
If you have only SQL Server 2005 or 2008 available, you can use REPLICATE() combined with LEN() to get what you need (disclaimer: UGLY CODE):
SELECT REPLICATE('0', 5 - LEN(MAX(CAST(mycolumn AS int)) + 1)) + CAST((MAX(CAST(mycolumn AS int)) + 1) AS nvarchar(5)) FROM mytable
EDIT As Luaan hinted, you can use another padding option (shorter and more readable code):
SELECT RIGHT('00000' + CAST(MAX(CAST(mycolumn AS int) + 1) as nvarchar(5)), 5) FROM mytable
I don't know if it is mandatory that EmployeeDetailKey must have this format.
I'll suggest you to substitute the datatype from varchar(10) to IDENTITY.
IDENTITY is a sort of special data type that gets automatically incremented by SQL Server each time you insert a row in the parent table. You can learn more here: https://msdn.microsoft.com/en-us/library/ms186775.aspx
If you need to present it with leading zeroes you can always select it like this (liberally adapted from here: Most efficient T-SQL way to pad a varchar on the left to a certain length?):
SELECT right('00000'+ rtrim(EmployeeDetailKey), 5) FROM YourTable

Determine if a Varchar field has a Numeric entry

I've got a field in a table that has a DataType of varchar(10). This field contains numeric values that are formatted as a varchar, for the sole purpose of being used to join two tables together. Some sample data would be:
AcctNum AcctNumChar
2223333 2223333
3324444 3324444
For some records, the table sometimes thinks this field (AcctNumChar) is numeric and the join doesn't work properly. I then have to use an Update statement to re-enter the value as a varchar.
Is there any way to determine whether or not the field has a varchar or numeric value in it, using a query? I'm trying to narrow down which records are faulty without having to wait for one of the users to tell me that their query isn't returning any hits.
You can use isnumeric() for a generic comparison, for instance:
select (case when isnumeric(acctnum) = 1 then cast(acctnum as decimal(10, 0))
end)
In your case, though, you only seem to want integers:
(case when acctnum not like '%[^0-9]%' then cast(acctnum as decimal(10, 0))
end)
However, I would strongly suggest that you update the table to change the data type to a number, which appears to be the correct type for the value. You can also add a computed column as:
alter table t add AcctNum_Number as
(case when acctnum not like '%[^0-9]%' then cast(acctnum as decimal(10, 0))
end)
Then you can use the computed column rather than the character column.
There are several ways to check if varchar column contains numeric value but I recommend to you to us TRY_CONVERT function.
It will give you NULL if the value cannot be converted to number. For example, to get all records that have numeric values, you can do this:
SELECT *
FROM [table]
WHERE TRY_CONVERT(INT, [value]) IS NOT NULL
You can use CAST and CONVERT (Transact-SQL) functions here to solve your purpose.
reference here - https://msdn.microsoft.com/en-IN/library/ms187928.aspx.
IsNumeric worked, TRY_CONVERT didn't (SQL wouldn't recognize it as a built-in function for some reason). Anyway, for the record I ran the following query and got all of my suspect records:
SELECT *
FROM ACCT_LIST
where IsNumeric([ACCT_NUM_CHAR]) = 0
Use PATINDEX function:
DECLARE #s VARCHAR(20) = '123123'
SELECT PATINDEX('%[^0-9]%', #s)
If #s variable will have something different from range 0-9 this function will return the index of first occurence of non digit symbol. If all symbols are digits it will return 0.

Error converting data type varchar

I currently have a table with a column as varchar. This column can hold numbers or text. During certain queries I treat it as a bigint column (I do a join between it and a column in another table that is bigint)
As long as there were only numbers in this field had no trouble but the minute even one row had text and not numbers in this field I got a "Error converting data type varchar to bigint." error even if in the WHERE part I made sure none of the text fields came up.
To solve this I created a view as follows:
SELECT TOP (100) PERCENT ID, CAST(MyCol AS bigint) AS MyCol
FROM MyTable
WHERE (isnumeric(MyCol) = 1)
But even though the view shows only the rows with numeric values and casts Mycol to bigint I still get a Error converting data type varchar to bigint when running the following query:
SELECT * FROM MyView where mycol=1
When doing queries against the view it shouldn't know what is going on behind it! it should simply see two bigint fields! (see attached image, even mssql management studio shows the view fields as being bigint)
OK. I finally created a view that works:
SELECT TOP (100) PERCENT id, CAST(CASE WHEN IsNumeric(MyCol) = 1 THEN MyCol ELSE NULL END AS bigint) AS MyCol
FROM dbo.MyTable
WHERE (MyCol NOT LIKE '%[^0-9]%')
Thanks to AdaTheDev and CodeByMoonlight. I used your two answers to get to this. (Thanks to the other repliers too of course)
Now when I do joins with other bigint cols or do something like 'SELECT * FROM MyView where mycol=1' it returns the correct result with no errors. My guess is that the CAST in the query itself causes the query optimizer to not look at the original table as Christian Hayter said may be going on with the other views
Ideally, you want to try to avoid storing the data in this form - would be worth splitting the BIGINT data out in to a separate column for both performance and ease of querying.
However, you can do a JOIN like this example. Note, I'm not using ISNUMERIC() to determine if it's a valid BIGINT because that would validate incorrect values which would cause a conversion error (e.g. decimal numbers).
DECLARE #MyTable TABLE (MyCol VARCHAR(20))
DECLARE #OtherTable TABLE (Id BIGINT)
INSERT #MyTable VALUES ('1')
INSERT #MyTable VALUES ('Text')
INSERT #MyTable VALUES ('1 and some text')
INSERT #MyTable VALUES ('1.34')
INSERT #MyTable VALUES ('2')
INSERT #OtherTable VALUES (1)
INSERT #OtherTable VALUES (2)
INSERT #OtherTable VALUES (3)
SELECT *
FROM #MyTable m
JOIN #OtherTable o ON CAST(m.MyCol AS BIGINT) = o.Id
WHERE m.MyCol NOT LIKE '%[^0-9]%'
Update:
The only way I can find to get it to work for having a WHERE clause for a specific integer value without doing another CAST() on the supposedly bigint column in the where clause too, is to use a user defined function:
CREATE FUNCTION [dbo].[fnBigIntRecordsOnly]()
RETURNS #Results TABLE (BigIntCol BIGINT)
AS
BEGIN
INSERT #Results
SELECT CAST(MyCol AS BIGINT)
FROM MyTable
WHERE MyCol NOT LIKE '%[^0-9]%'
RETURN
END
SELECT * FROM [dbo].[fnBigIntRecordsOnly]() WHERE BigIntCol = 1
I don't really think this is a great idea performance wise, but it's a solution
To answer your question about the error message: when you reference a view name in another query (assuming it's a traditional view not a materialised view), SQL Server effectively does a macro replacement of the view definition into the consuming query and then executes that.
The advantage of doing this is that the query optimiser can do a much better job if it sees the whole query, rather than optimising the view separately as a "black box".
A consequence is that if an error occurs, error descriptions may look confusing because the execution engine is accessing the underlying tables for the data, not the view.
I'm not sure how materialised views are treated, but I would imagine that they are treated like tables, since the view data is cached in the database.
Having said that, I agree with previous answers - you should re-think your table design and separate out the text and integer data values into separate columns.
Try changing your view to this :
SELECT TOP 100 PERCENT ID,
Cast(Case When IsNumeric(MyCol) = 1 Then MyCol Else null End AS bigint) AS MyCol
FROM MyTable
WHERE (IsNumeric(MyCol) = 1)
Have you tried to convert other table's bigint field into varchar? As for me it makes sense to perform more robust conversion... It shouldn't affect your performance too much if varchar field is indexed.
Consider creating a redundant bigint field to hold the integer value of af MyCol.
You may then index the new field to speed up the join.
Try using this:
SELECT
ID,
CAST(MyCol AS bigint) as MyCol
FROM
(
SELECT TOP (100) PERCENT
ID,
MyCol
FROM
MyTable
WHERE
(isnumeric(MyCol) = 1)
) as tmp
This should work since the inner select only return numeric values and the outer select can therefore convert all values from the first select into a numeric. It seems that in your own code SQL tries to cast before executing the isnumeric function (maybe it has something to do with optimizing).
Try doing the select in 2 stages.
first create a view that selects all columns where my col is nummeric.
Then do a select in that view where you cast the varchar field.
The other thing you could look at is your design of tables to remove the need for the cast.
EDIT
Are some of the numbers larger than bigint?
Are there any spaces, leading, trailing or in the number?
Are there any format characters? Decimal points?