SQL Server: Computed Column Result Used In Another Computed Column - sql-server

I know its not possible to have a computed column take into consideration its calculations another computed column. I found out the hard ware with the following error:
"is not allowed to be used in another computed-column definition."
So i have the following data columns, which arent neccessarily important, but just so you understand what I am doing (any other columns referenced are standard non computed columns):
HardwareAssetDepreciableValue AS CONVERT(DECIMAL(7,2),HardwareAssetPurchaseValue -
HardwareAssetSalvageValue)
HardwareAssetLifeSpan AS CONVERT(DECIMAL(6,2),DATEDIFF(day,HardwareAssetDateInstalled,
HardwareAssetEndOfLifeDate)) / 365
They are all calculated and work as expected, however what I am having issues with is a rather complicated set of calculations at best, but wondering if anyone can suggest or help with alternatives to resolving the issue of multiple computed columns.
My query is:
HardwareAssetAccumulatedDepreciationValue AS CASE WHEN HardwareAssetDepreciationMethodID
= '1' THEN CONVERT(DECIMAL(7,2),((HardwareAssetDepreciableValue / HardwareAssetLifeSpan)
/ 365)) WHEN HardwareAssetDepreciationMethodID = '2' THEN CONVERT(DECIMAL(7,2),
HardwareAssetAccumulatedDepreciationValue + ((1.5 *(1/HardwareAssetLifeSpan))*
HardwareAssetBookValue)/365) ELSE CONVERT(DECIMAL(7,2),
HardwareAssetAccumulatedDepreciationValue + ((2 *(1/HardwareAssetLifeSpan))
*HardwareAssetBookValue)/365) END
Any help or advice is appreciated!

This error is not allowed to be used in another computed-column definition. occurs when you have used computed column to calculate another column value...
it seems you have below computed column
HardwareAssetDepreciableValue
And then you ,use the same to calculate below computed column
THEN CONVERT(DECIMAL(7,2),((HardwareAssetDepreciableValue / HardwareAssetLifeSpan)
/ 365))
you should not do like that..instead ,you should use base calculation
but wondering if anyone can suggest or help with alternatives to resolving the issue of multiple computed columns.
at present,there are no better alternatives ,one solution i could think of is a view to query the base table and use already existing computed values

A computed column is good for a "one off" IMHO.
For something a tad bit complex, I like to create a scalar user defined function, and wrap the "mini logic" in it.
Here is a simple Northwind example. It doesn't make alot of pratical sense, but demonstrates.
Now, I don't like the below udfExampleUdfTwoDoubleUdfOne idea. But it could work for you.
Its an idea for the arsenal of tools. Which is because you asked for alternate ideas.
Use Northwind
GO
create function dbo.udfExampleUdfOne(#OrderID as int, #ProductID int, #UnitPrice money )
returns int
as
begin
declare #returnValue int = 0
select #returnValue = ISNULL(#OrderID,0) + ISNULL(#ProductID,0) + ISNULL(#UnitPrice,0)
return #returnValue;
end;
GO
create function dbo.udfExampleUdfTwoDoubleUdfOne(#udfOneResult int)
returns int
as
begin
declare #returnValue int = 0
select #returnValue = ISNULL(#udfOneResult,0) * 2
return #returnValue;
end;
GO
SELECT TOP 1000 [OrderID]
,[ProductID]
,[UnitPrice]
,[Quantity]
,[Discount]
, MyValueOne = dbo.udfExampleUdfOne(OrderID , ProductID , UnitPrice)
, MyValueTwoWhichIsActuallyDoubleValueOne = dbo.udfExampleUdfTwoDoubleUdfOne(dbo.udfExampleUdfOne(OrderID , ProductID , UnitPrice))
FROM [Northwind].[dbo].[Order Details]
APPEND
So trying to mimic your example
create function dbo.udfComputeHardwareAssetDepreciableValue(#HardwareAssetPurchaseValue int, #HardwareAssetSalvageValue int )
returns int
as
begin
declare #returnValue int = 0
select #returnValue = ISNULL(#HardwareAssetPurchaseValue,0) - ISNULL(#HardwareAssetSalvageValue,0)
return #returnValue;
end;
create function dbo.udfComputeHardwareAssetLifeSpan(#HardwareAssetDateInstalled int, #HardwareAssetEndOfLifeDate int )
returns int
as
begin
declare #returnValue int = 0
select #returnValue = CONVERT(DECIMAL(6,2),DATEDIFF(day,#HardwareAssetDateInstalled,
#HardwareAssetEndOfLifeDate)) / 365
return #returnValue;
end;
Then write a third UDF that encapsulates your HardwareAssetAccumulatedDepreciationValue IF/THEN/CASE logic.
You ~could~ pass in the computed columns into the new udfComputeHardwareAssetAccumulatedDepreciationValue function as well.
Even if you are new , you should spend a little time struggling with this concept as its a tool in the best to avoid RBAR/CURSORS.
You can get the Northwind db from here:
https://technet.microsoft.com/en-us/library/ms143221(v=sql.105).aspx
Yes, its very old, but simple demos can be created from it.

you can make both of the columns user defined Function , i was looking for it but finally got , the function can access all the columns computed ND NON Computed...
thank you

Related

Dynamic SQL operator IN with multiple parameters [duplicate]

How do I parameterize a query containing an IN clause with a variable number of arguments, like this one?
SELECT * FROM Tags
WHERE Name IN ('ruby','rails','scruffy','rubyonrails')
ORDER BY Count DESC
In this query, the number of arguments could be anywhere from 1 to 5.
I would prefer not to use a dedicated stored procedure for this (or XML), but if there is some elegant way specific to SQL Server 2008, I am open to that.
You can parameterize each value, so something like:
string[] tags = new string[] { "ruby", "rails", "scruffy", "rubyonrails" };
string cmdText = "SELECT * FROM Tags WHERE Name IN ({0})";
string[] paramNames = tags.Select(
(s, i) => "#tag" + i.ToString()
).ToArray();
string inClause = string.Join(", ", paramNames);
using (SqlCommand cmd = new SqlCommand(string.Format(cmdText, inClause))) {
for(int i = 0; i < paramNames.Length; i++) {
cmd.Parameters.AddWithValue(paramNames[i], tags[i]);
}
}
Which will give you:
cmd.CommandText = "SELECT * FROM Tags WHERE Name IN (#tag0, #tag1, #tag2, #tag3)"
cmd.Parameters["#tag0"] = "ruby"
cmd.Parameters["#tag1"] = "rails"
cmd.Parameters["#tag2"] = "scruffy"
cmd.Parameters["#tag3"] = "rubyonrails"
No, this is not open to SQL injection. The only injected text into CommandText is not based on user input. It's solely based on the hardcoded "#tag" prefix, and the index of an array. The index will always be an integer, is not user generated, and is safe.
The user inputted values are still stuffed into parameters, so there is no vulnerability there.
Edit:
Injection concerns aside, take care to note that constructing the command text to accomodate a variable number of parameters (as above) impede's SQL server's ability to take advantage of cached queries. The net result is that you almost certainly lose the value of using parameters in the first place (as opposed to merely inserting the predicate strings into the SQL itself).
Not that cached query plans aren't valuable, but IMO this query isn't nearly complicated enough to see much benefit from it. While the compilation costs may approach (or even exceed) the execution costs, you're still talking milliseconds.
If you have enough RAM, I'd expect SQL Server would probably cache a plan for the common counts of parameters as well. I suppose you could always add five parameters, and let the unspecified tags be NULL - the query plan should be the same, but it seems pretty ugly to me and I'm not sure that it'd worth the micro-optimization (although, on Stack Overflow - it may very well be worth it).
Also, SQL Server 7 and later will auto-parameterize queries, so using parameters isn't really necessary from a performance standpoint - it is, however, critical from a security standpoint - especially with user inputted data like this.
Here's a quick-and-dirty technique I have used:
SELECT * FROM Tags
WHERE '|ruby|rails|scruffy|rubyonrails|'
LIKE '%|' + Name + '|%'
So here's the C# code:
string[] tags = new string[] { "ruby", "rails", "scruffy", "rubyonrails" };
const string cmdText = "select * from tags where '|' + #tags + '|' like '%|' + Name + '|%'";
using (SqlCommand cmd = new SqlCommand(cmdText)) {
cmd.Parameters.AddWithValue("#tags", string.Join("|", tags);
}
Two caveats:
The performance is terrible. LIKE "%...%" queries are not indexed.
Make sure you don't have any |, blank, or null tags or this won't work
There are other ways to accomplish this that some people may consider cleaner, so please keep reading.
For SQL Server 2008, you can use a table valued parameter. It's a bit of work, but it is arguably cleaner than my other method.
First, you have to create a type
CREATE TYPE dbo.TagNamesTableType AS TABLE ( Name nvarchar(50) )
Then, your ADO.NET code looks like this:
string[] tags = new string[] { "ruby", "rails", "scruffy", "rubyonrails" };
cmd.CommandText = "SELECT Tags.* FROM Tags JOIN #tagNames as P ON Tags.Name = P.Name";
// value must be IEnumerable<SqlDataRecord>
cmd.Parameters.AddWithValue("#tagNames", tags.AsSqlDataRecord("Name")).SqlDbType = SqlDbType.Structured;
cmd.Parameters["#tagNames"].TypeName = "dbo.TagNamesTableType";
// Extension method for converting IEnumerable<string> to IEnumerable<SqlDataRecord>
public static IEnumerable<SqlDataRecord> AsSqlDataRecord(this IEnumerable<string> values, string columnName) {
if (values == null || !values.Any()) return null; // Annoying, but SqlClient wants null instead of 0 rows
var firstRecord = values.First();
var metadata= new SqlMetaData(columnName, SqlDbType.NVarChar, 50); //50 as per SQL Type
return values.Select(v =>
{
var r = new SqlDataRecord(metadata);
r.SetValues(v);
return r;
});
}
Update
As Per #Doug
Please try to avoid var metadata = SqlMetaData.InferFromValue(firstRecord, columnName);
It's set first value length, so if first value is 3 characters then its set max length 3 and other records will truncated if more then 3 characters.
So, please try to use: var metadata= new SqlMetaData(columnName, SqlDbType.NVarChar, maxLen);
Note: -1 for max length.
The original question was "How do I parameterize a query ..."
This is not an answer to that original question. There are some very good demonstrations of how to do that, in other answers.
See the first answer from Mark Brackett (the first answer starting "You can parameterize each value") and Mark Brackett's second answer for the preferred answer that I (and 231 others) upvoted. The approach given in his answer allows 1) for effective use of bind variables, and 2) for predicates that are sargable.
Selected answer
I am addressing here the approach given in Joel Spolsky's answer, the answer "selected" as the right answer.
Joel Spolsky's approach is clever. And it works reasonably, it's going to exhibit predictable behavior and predictable performance, given "normal" values, and with the normative edge cases, such as NULL and the empty string. And it may be sufficient for a particular application.
But in terms generalizing this approach, let's also consider the more obscure corner cases, like when the Name column contains a wildcard character (as recognized by the LIKE predicate.) The wildcard character I see most commonly used is % (a percent sign.). So let's deal with that here now, and later go on to other cases.
Some problems with % character
Consider a Name value of 'pe%ter'. (For the examples here, I use a literal string value in place of the column name.) A row with a Name value of `'pe%ter' would be returned by a query of the form:
select ...
where '|peanut|butter|' like '%|' + 'pe%ter' + '|%'
But that same row will not be returned if the order of the search terms is reversed:
select ...
where '|butter|peanut|' like '%|' + 'pe%ter' + '|%'
The behavior we observe is kind of odd. Changing the order of the search terms in the list changes the result set.
It almost goes without saying that we might not want pe%ter to match peanut butter, no matter how much he likes it.
Obscure corner case
(Yes, I will agree that this is an obscure case. Probably one that is not likely to be tested. We wouldn't expect a wildcard in a column value. We may assume that the application prevents such a value from being stored. But in my experience, I've rarely seen a database constraint that specifically disallowed characters or patterns that would be considered wildcards on the right side of a LIKE comparison operator.
Patching a hole
One approach to patching this hole is to escape the % wildcard character. (For anyone not familiar with the escape clause on the operator, here's a link to the SQL Server documentation.
select ...
where '|peanut|butter|'
like '%|' + 'pe\%ter' + '|%' escape '\'
Now we can match the literal %. Of course, when we have a column name, we're going to need to dynamically escape the wildcard. We can use the REPLACE function to find occurrences of the % character and insert a backslash character in front of each one, like this:
select ...
where '|pe%ter|'
like '%|' + REPLACE( 'pe%ter' ,'%','\%') + '|%' escape '\'
So that solves the problem with the % wildcard. Almost.
Escape the escape
We recognize that our solution has introduced another problem. The escape character. We see that we're also going to need to escape any occurrences of escape character itself. This time, we use the ! as the escape character:
select ...
where '|pe%t!r|'
like '%|' + REPLACE(REPLACE( 'pe%t!r' ,'!','!!'),'%','!%') + '|%' escape '!'
The underscore too
Now that we're on a roll, we can add another REPLACE handle the underscore wildcard. And just for fun, this time, we'll use $ as the escape character.
select ...
where '|p_%t!r|'
like '%|' + REPLACE(REPLACE(REPLACE( 'p_%t!r' ,'$','$$'),'%','$%'),'_','$_') + '|%' escape '$'
I prefer this approach to escaping because it works in Oracle and MySQL as well as SQL Server. (I usually use the \ backslash as the escape character, since that's the character we use in regular expressions. But why be constrained by convention!
Those pesky brackets
SQL Server also allows for wildcard characters to be treated as literals by enclosing them in brackets []. So we're not done fixing yet, at least for SQL Server. Since pairs of brackets have special meaning, we'll need to escape those as well. If we manage to properly escape the brackets, then at least we won't have to bother with the hyphen - and the carat ^ within the brackets. And we can leave any % and _ characters inside the brackets escaped, since we'll have basically disabled the special meaning of the brackets.
Finding matching pairs of brackets shouldn't be that hard. It's a little more difficult than handling the occurrences of singleton % and _. (Note that it's not sufficient to just escape all occurrences of brackets, because a singleton bracket is considered to be a literal, and doesn't need to be escaped. The logic is getting a little fuzzier than I can handle without running more test cases.)
Inline expression gets messy
That inline expression in the SQL is getting longer and uglier. We can probably make it work, but heaven help the poor soul that comes behind and has to decipher it. As much of a fan I am for inline expressions, I'm inclined not use one here, mainly because I don't want to have to leave a comment explaining the reason for the mess, and apologizing for it.
A function where ?
Okay, so, if we don't handle that as an inline expression in the SQL, the closest alternative we have is a user defined function. And we know that won't speed things up any (unless we can define an index on it, like we could with Oracle.) If we've got to create a function, we might better do that in the code that calls the SQL statement.
And that function may have some differences in behavior, dependent on the DBMS and version. (A shout out to all you Java developers so keen on being able to use any database engine interchangeably.)
Domain knowledge
We may have specialized knowledge of the domain for the column, (that is, the set of allowable values enforced for the column. We may know a priori that the values stored in the column will never contain a percent sign, an underscore, or bracket pairs. In that case, we just include a quick comment that those cases are covered.
The values stored in the column may allow for % or _ characters, but a constraint may require those values to be escaped, perhaps using a defined character, such that the values are LIKE comparison "safe". Again, a quick comment about the allowed set of values, and in particular which character is used as an escape character, and go with Joel Spolsky's approach.
But, absent the specialized knowledge and a guarantee, it's important for us to at least consider handling those obscure corner cases, and consider whether the behavior is reasonable and "per the specification".
Other issues recapitulated
I believe others have already sufficiently pointed out some of the other commonly considered areas of concern:
SQL injection (taking what would appear to be user supplied information, and including that in the SQL text rather than supplying them through bind variables. Using bind variables isn't required, it's just one convenient approach to thwart with SQL injection. There are other ways to deal with it:
optimizer plan using index scan rather than index seeks, possible need for an expression or function for escaping wildcards (possible index on expression or function)
using literal values in place of bind variables impacts scalability
Conclusion
I like Joel Spolsky's approach. It's clever. And it works.
But as soon as I saw it, I immediately saw a potential problem with it, and it's not my nature to let it slide. I don't mean to be critical of the efforts of others. I know many developers take their work very personally, because they invest so much into it and they care so much about it. So please understand, this is not a personal attack. What I'm identifying here is the type of problem that crops up in production rather than testing.
You can pass the parameter as a string
So you have the string
DECLARE #tags
SET #tags = ‘ruby|rails|scruffy|rubyonrails’
select * from Tags
where Name in (SELECT item from fnSplit(#tags, ‘|’))
order by Count desc
Then all you have to do is pass the string as 1 parameter.
Here is the split function I use.
CREATE FUNCTION [dbo].[fnSplit](
#sInputList VARCHAR(8000) -- List of delimited items
, #sDelimiter VARCHAR(8000) = ',' -- delimiter that separates items
) RETURNS #List TABLE (item VARCHAR(8000))
BEGIN
DECLARE #sItem VARCHAR(8000)
WHILE CHARINDEX(#sDelimiter,#sInputList,0) <> 0
BEGIN
SELECT
#sItem=RTRIM(LTRIM(SUBSTRING(#sInputList,1,CHARINDEX(#sDelimiter,#sInputList,0)-1))),
#sInputList=RTRIM(LTRIM(SUBSTRING(#sInputList,CHARINDEX(#sDelimiter,#sInputList,0)+LEN(#sDelimiter),LEN(#sInputList))))
IF LEN(#sItem) > 0
INSERT INTO #List SELECT #sItem
END
IF LEN(#sInputList) > 0
INSERT INTO #List SELECT #sInputList -- Put the last item in
RETURN
END
I heard Jeff/Joel talk about this on the podcast today (episode 34, 2008-12-16 (MP3, 31 MB), 1 h 03 min 38 secs - 1 h 06 min 45 secs), and I thought I recalled Stack Overflow was using LINQ to SQL, but maybe it was ditched. Here's the same thing in LINQ to SQL.
var inValues = new [] { "ruby","rails","scruffy","rubyonrails" };
var results = from tag in Tags
where inValues.Contains(tag.Name)
select tag;
That's it. And, yes, LINQ already looks backwards enough, but the Contains clause seems extra backwards to me. When I had to do a similar query for a project at work, I naturally tried to do this the wrong way by doing a join between the local array and the SQL Server table, figuring the LINQ to SQL translator would be smart enough to handle the translation somehow. It didn't, but it did provide an error message that was descriptive and pointed me towards using Contains.
Anyway, if you run this in the highly recommended LINQPad, and run this query, you can view the actual SQL that the SQL LINQ provider generated. It'll show you each of the values getting parameterized into an IN clause.
If you are calling from .NET, you could use Dapper dot net:
string[] names = new string[] {"ruby","rails","scruffy","rubyonrails"};
var tags = dataContext.Query<Tags>(#"
select * from Tags
where Name in #names
order by Count desc", new {names});
Here Dapper does the thinking, so you don't have to. Something similar is possible with LINQ to SQL, of course:
string[] names = new string[] {"ruby","rails","scruffy","rubyonrails"};
var tags = from tag in dataContext.Tags
where names.Contains(tag.Name)
orderby tag.Count descending
select tag;
In SQL Server 2016+ you could use STRING_SPLIT function:
DECLARE #names NVARCHAR(MAX) = 'ruby,rails,scruffy,rubyonrails';
SELECT *
FROM Tags
WHERE Name IN (SELECT [value] FROM STRING_SPLIT(#names, ','))
ORDER BY [Count] DESC;
or:
DECLARE #names NVARCHAR(MAX) = 'ruby,rails,scruffy,rubyonrails';
SELECT t.*
FROM Tags t
JOIN STRING_SPLIT(#names,',')
ON t.Name = [value]
ORDER BY [Count] DESC;
LiveDemo
The accepted answer will of course work and it is one of the way to go, but it is anti-pattern.
E. Find rows by list of values
This is replacement for common anti-pattern such as creating a dynamic SQL string in application layer or Transact-SQL, or by using LIKE operator:
SELECT ProductId, Name, Tags
FROM Product
WHERE ',1,2,3,' LIKE '%,' + CAST(ProductId AS VARCHAR(20)) + ',%';
Addendum:
To improve the STRING_SPLIT table function row estimation, it is a good idea to materialize splitted values as temporary table/table variable:
DECLARE #names NVARCHAR(MAX) = 'ruby,rails,scruffy,rubyonrails,sql';
CREATE TABLE #t(val NVARCHAR(120));
INSERT INTO #t(val) SELECT s.[value] FROM STRING_SPLIT(#names, ',') s;
SELECT *
FROM Tags tg
JOIN #t t
ON t.val = tg.TagName
ORDER BY [Count] DESC;
SEDE - Live Demo
Related: How to Pass a List of Values Into a Stored Procedure
Original question has requirement SQL Server 2008. Because this question is often used as duplicate, I've added this answer as reference.
This is possibly a half nasty way of doing it, I used it once, was rather effective.
Depending on your goals it might be of use.
Create a temp table with one column.
INSERT each look-up value into that column.
Instead of using an IN, you can then just use your standard JOIN rules. ( Flexibility++ )
This has a bit of added flexibility in what you can do, but it's more suited for situations where you have a large table to query, with good indexing, and you want to use the parametrized list more than once. Saves having to execute it twice and have all the sanitation done manually.
I never got around to profiling exactly how fast it was, but in my situation it was needed.
We have function that creates a table variable that you can join to:
ALTER FUNCTION [dbo].[Fn_sqllist_to_table](#list AS VARCHAR(8000),
#delim AS VARCHAR(10))
RETURNS #listTable TABLE(
Position INT,
Value VARCHAR(8000))
AS
BEGIN
DECLARE #myPos INT
SET #myPos = 1
WHILE Charindex(#delim, #list) > 0
BEGIN
INSERT INTO #listTable
(Position,Value)
VALUES (#myPos,LEFT(#list, Charindex(#delim, #list) - 1))
SET #myPos = #myPos + 1
IF Charindex(#delim, #list) = Len(#list)
INSERT INTO #listTable
(Position,Value)
VALUES (#myPos,'')
SET #list = RIGHT(#list, Len(#list) - Charindex(#delim, #list))
END
IF Len(#list) > 0
INSERT INTO #listTable
(Position,Value)
VALUES (#myPos,#list)
RETURN
END
So:
#Name varchar(8000) = null // parameter for search values
select * from Tags
where Name in (SELECT value From fn_sqllist_to_table(#Name,',')))
order by Count desc
This is gross, but if you are guaranteed to have at least one, you could do:
SELECT ...
...
WHERE tag IN( #tag1, ISNULL( #tag2, #tag1 ), ISNULL( #tag3, #tag1 ), etc. )
Having IN( 'tag1', 'tag2', 'tag1', 'tag1', 'tag1' ) will be easily optimized away by SQL Server. Plus, you get direct index seeks
I would pass a table type parameter (since it's SQL Server 2008), and do a where exists, or inner join. You may also use XML, using sp_xml_preparedocument, and then even index that temporary table.
In my opinion, the best source to solve this problem, is what has been posted on this site:
Syscomments. Dinakar Nethi
CREATE FUNCTION dbo.fnParseArray (#Array VARCHAR(1000),#separator CHAR(1))
RETURNS #T Table (col1 varchar(50))
AS
BEGIN
--DECLARE #T Table (col1 varchar(50))
-- #Array is the array we wish to parse
-- #Separator is the separator charactor such as a comma
DECLARE #separator_position INT -- This is used to locate each separator character
DECLARE #array_value VARCHAR(1000) -- this holds each array value as it is returned
-- For my loop to work I need an extra separator at the end. I always look to the
-- left of the separator character for each array value
SET #array = #array + #separator
-- Loop through the string searching for separtor characters
WHILE PATINDEX('%' + #separator + '%', #array) <> 0
BEGIN
-- patindex matches the a pattern against a string
SELECT #separator_position = PATINDEX('%' + #separator + '%',#array)
SELECT #array_value = LEFT(#array, #separator_position - 1)
-- This is where you process the values passed.
INSERT into #T VALUES (#array_value)
-- Replace this select statement with your processing
-- #array_value holds the value of this element of the array
-- This replaces what we just processed with and empty string
SELECT #array = STUFF(#array, 1, #separator_position, '')
END
RETURN
END
Use:
SELECT * FROM dbo.fnParseArray('a,b,c,d,e,f', ',')
CREDITS FOR: Dinakar Nethi
The proper way IMHO is to store the list in a character string (limited in length by what the DBMS support); the only trick is that (in order to simplify processing) I have a separator (a comma in my example) at the beginning and at the end of the string. The idea is to "normalize on the fly", turning the list into a one-column table that contains one row per value. This allows you to turn
in (ct1,ct2, ct3 ... ctn)
into an
in (select ...)
or (the solution I'd probably prefer) a regular join, if you just add a "distinct" to avoid problems with duplicate values in the list.
Unfortunately, the techniques to slice a string are fairly product-specific.
Here is the SQL Server version:
with qry(n, names) as
(select len(list.names) - len(replace(list.names, ',', '')) - 1 as n,
substring(list.names, 2, len(list.names)) as names
from (select ',Doc,Grumpy,Happy,Sneezy,Bashful,Sleepy,Dopey,' names) as list
union all
select (n - 1) as n,
substring(names, 1 + charindex(',', names), len(names)) as names
from qry
where n > 1)
select n, substring(names, 1, charindex(',', names) - 1) dwarf
from qry;
The Oracle version:
select n, substr(name, 1, instr(name, ',') - 1) dwarf
from (select n,
substr(val, 1 + instr(val, ',', 1, n)) name
from (select rownum as n,
list.val
from (select ',Doc,Grumpy,Happy,Sneezy,Bashful,Sleepy,Dopey,' val
from dual) list
connect by level < length(list.val) -
length(replace(list.val, ',', ''))));
and the MySQL version:
select pivot.n,
substring_index(substring_index(list.val, ',', 1 + pivot.n), ',', -1) from (select 1 as n
union all
select 2 as n
union all
select 3 as n
union all
select 4 as n
union all
select 5 as n
union all
select 6 as n
union all
select 7 as n
union all
select 8 as n
union all
select 9 as n
union all
select 10 as n) pivot, (select ',Doc,Grumpy,Happy,Sneezy,Bashful,Sleepy,Dopey,' val) as list where pivot.n < length(list.val) -
length(replace(list.val, ',', ''));
(Of course, "pivot" must return as many rows as the maximum number of
items we can find in the list)
If you've got SQL Server 2008 or later I'd use a Table Valued Parameter.
If you're unlucky enough to be stuck on SQL Server 2005 you could add a CLR function like this,
[SqlFunction(
DataAccessKind.None,
IsDeterministic = true,
SystemDataAccess = SystemDataAccessKind.None,
IsPrecise = true,
FillRowMethodName = "SplitFillRow",
TableDefinintion = "s NVARCHAR(MAX)"]
public static IEnumerable Split(SqlChars seperator, SqlString s)
{
if (s.IsNull)
return new string[0];
return s.ToString().Split(seperator.Buffer);
}
public static void SplitFillRow(object row, out SqlString s)
{
s = new SqlString(row.ToString());
}
Which you could use like this,
declare #desiredTags nvarchar(MAX);
set #desiredTags = 'ruby,rails,scruffy,rubyonrails';
select * from Tags
where Name in [dbo].[Split] (',', #desiredTags)
order by Count desc
I think this is a case when a static query is just not the way to go. Dynamically build the list for your in clause, escape your single quotes, and dynamically build SQL. In this case you probably won't see much of a difference with any method due to the small list, but the most efficient method really is to send the SQL exactly as it is written in your post. I think it is a good habit to write it the most efficient way, rather than to do what makes the prettiest code, or consider it bad practice to dynamically build SQL.
I have seen the split functions take longer to execute than the query themselves in many cases where the parameters get large. A stored procedure with table valued parameters in SQL 2008 is the only other option I would consider, although this will probably be slower in your case. TVP will probably only be faster for large lists if you are searching on the primary key of the TVP, because SQL will build a temporary table for the list anyway (if the list is large). You won't know for sure unless you test it.
I have also seen stored procedures that had 500 parameters with default values of null, and having WHERE Column1 IN (#Param1, #Param2, #Param3, ..., #Param500). This caused SQL to build a temp table, do a sort/distinct, and then do a table scan instead of an index seek. That is essentially what you would be doing by parameterizing that query, although on a small enough scale that it won't make a noticeable difference. I highly recommend against having NULL in your IN lists, as if that gets changed to a NOT IN it will not act as intended. You could dynamically build the parameter list, but the only obvious thing that you would gain is that the objects would escape the single quotes for you. That approach is also slightly slower on the application end since the objects have to parse the query to find the parameters. It may or may not be faster on SQL, as parameterized queries call sp_prepare, sp_execute for as many times you execute the query, followed by sp_unprepare.
The reuse of execution plans for stored procedures or parameterized queries may give you a performance gain, but it will lock you in to one execution plan determined by the first query that is executed. That may be less than ideal for subsequent queries in many cases. In your case, reuse of execution plans will probably be a plus, but it might not make any difference at all as the example is a really simple query.
Cliffs notes:
For your case anything you do, be it parameterization with a fixed number of items in the list (null if not used), dynamically building the query with or without parameters, or using stored procedures with table valued parameters will not make much of a difference. However, my general recommendations are as follows:
Your case/simple queries with few parameters:
Dynamic SQL, maybe with parameters if testing shows better performance.
Queries with reusable execution plans, called multiple times by simply changing the parameters or if the query is complicated:
SQL with dynamic parameters.
Queries with large lists:
Stored procedure with table valued parameters. If the list can vary by a large amount use WITH RECOMPILE on the stored procedure, or simply use dynamic SQL without parameters to generate a new execution plan for each query.
May be we can use XML here:
declare #x xml
set #x='<items>
<item myvalue="29790" />
<item myvalue="31250" />
</items>
';
With CTE AS (
SELECT
x.item.value('#myvalue[1]', 'decimal') AS myvalue
FROM #x.nodes('//items/item') AS x(item) )
select * from YourTable where tableColumnName in (select myvalue from cte)
If we have strings stored inside the IN clause with the comma(,) delimited, we can use the charindex function to get the values. If you use .NET, then you can map with SqlParameters.
DDL Script:
CREATE TABLE Tags
([ID] int, [Name] varchar(20))
;
INSERT INTO Tags
([ID], [Name])
VALUES
(1, 'ruby'),
(2, 'rails'),
(3, 'scruffy'),
(4, 'rubyonrails')
;
T-SQL:
DECLARE #Param nvarchar(max)
SET #Param = 'ruby,rails,scruffy,rubyonrails'
SELECT * FROM Tags
WHERE CharIndex(Name,#Param)>0
You can use the above statement in your .NET code and map the parameter with SqlParameter.
Fiddler demo
EDIT:
Create the table called SelectedTags using the following script.
DDL Script:
Create table SelectedTags
(Name nvarchar(20));
INSERT INTO SelectedTags values ('ruby'),('rails')
T-SQL:
DECLARE #list nvarchar(max)
SELECT #list=coalesce(#list+',','')+st.Name FROM SelectedTags st
SELECT * FROM Tags
WHERE CharIndex(Name,#Param)>0
I'd approach this by default with passing a table valued function (that returns a table from a string) to the IN condition.
Here is the code for the UDF (I got it from Stack Overflow somewhere, i can't find the source right now)
CREATE FUNCTION [dbo].[Split] (#sep char(1), #s varchar(8000))
RETURNS table
AS
RETURN (
WITH Pieces(pn, start, stop) AS (
SELECT 1, 1, CHARINDEX(#sep, #s)
UNION ALL
SELECT pn + 1, stop + 1, CHARINDEX(#sep, #s, stop + 1)
FROM Pieces
WHERE stop > 0
)
SELECT
SUBSTRING(#s, start, CASE WHEN stop > 0 THEN stop-start ELSE 512 END) AS s
FROM Pieces
)
Once you got this your code would be as simple as this:
select * from Tags
where Name in (select s from dbo.split(';','ruby;rails;scruffy;rubyonrails'))
order by Count desc
Unless you have a ridiculously long string, this should work well with the table index.
If needed you can insert it into a temp table, index it, then run a join...
For a variable number of arguments like this the only way I'm aware of is to either generate the SQL explicitly or do something that involves populating a temporary table with the items you want and joining against the temp table.
Another possible solution is instead of passing a variable number of arguments to a stored procedure, pass a single string containing the names you're after, but make them unique by surrounding them with '<>'. Then use PATINDEX to find the names:
SELECT *
FROM Tags
WHERE PATINDEX('%<' + Name + '>%','<jo>,<john>,<scruffy>,<rubyonrails>') > 0
Use the following stored procedure. It uses a custom split function, which can be found here.
create stored procedure GetSearchMachingTagNames
#PipeDelimitedTagNames varchar(max),
#delimiter char(1)
as
begin
select * from Tags
where Name in (select data from [dbo].[Split](#PipeDelimitedTagNames,#delimiter)
end
Here is another alternative. Just pass a comma-delimited list as a string parameter to the stored procedure and:
CREATE PROCEDURE [dbo].[sp_myproc]
#UnitList varchar(MAX) = '1,2,3'
AS
select column from table
where ph.UnitID in (select * from CsvToInt(#UnitList))
And the function:
CREATE Function [dbo].[CsvToInt] ( #Array varchar(MAX))
returns #IntTable table
(IntValue int)
AS
begin
declare #separator char(1)
set #separator = ','
declare #separator_position int
declare #array_value varchar(MAX)
set #array = #array + ','
while patindex('%,%' , #array) <> 0
begin
select #separator_position = patindex('%,%' , #array)
select #array_value = left(#array, #separator_position - 1)
Insert #IntTable
Values (Cast(#array_value as int))
select #array = stuff(#array, 1, #separator_position, '')
end
return
end
In ColdFusion we just do:
<cfset myvalues = "ruby|rails|scruffy|rubyonrails">
<cfquery name="q">
select * from sometable where values in <cfqueryparam value="#myvalues#" list="true">
</cfquery>
Here's a technique that recreates a local table to be used in a query string. Doing it this way eliminates all parsing problems.
The string can be built in any language. In this example I used SQL since that was the original problem I was trying to solve. I needed a clean way to pass in table data on the fly in a string to be executed later.
Using a user defined type is optional. Creating the type is only created once and can be done ahead of time. Otherwise just add a full table type to the declaration in the string.
The general pattern is easy to extend and can be used for passing more complex tables.
-- Create a user defined type for the list.
CREATE TYPE [dbo].[StringList] AS TABLE(
[StringValue] [nvarchar](max) NOT NULL
)
-- Create a sample list using the list table type.
DECLARE #list [dbo].[StringList];
INSERT INTO #list VALUES ('one'), ('two'), ('three'), ('four')
-- Build a string in which we recreate the list so we can pass it to exec
-- This can be done in any language since we're just building a string.
DECLARE #str nvarchar(max);
SET #str = 'DECLARE #list [dbo].[StringList]; INSERT INTO #list VALUES '
-- Add all the values we want to the string. This would be a loop in C++.
SELECT #str = #str + '(''' + StringValue + '''),' FROM #list
-- Remove the trailing comma so the query is valid sql.
SET #str = substring(#str, 1, len(#str)-1)
-- Add a select to test the string.
SET #str = #str + '; SELECT * FROM #list;'
-- Execute the string and see we've pass the table correctly.
EXEC(#str)
In SQL Server 2016+ another possibility is to use the OPENJSON function.
This approach is blogged about in OPENJSON - one of best ways to select rows by list of ids.
A full worked example below
CREATE TABLE dbo.Tags
(
Name VARCHAR(50),
Count INT
)
INSERT INTO dbo.Tags
VALUES ('VB',982), ('ruby',1306), ('rails',1478), ('scruffy',1), ('C#',1784)
GO
CREATE PROC dbo.SomeProc
#Tags VARCHAR(MAX)
AS
SELECT T.*
FROM dbo.Tags T
WHERE T.Name IN (SELECT J.Value COLLATE Latin1_General_CI_AS
FROM OPENJSON(CONCAT('[', #Tags, ']')) J)
ORDER BY T.Count DESC
GO
EXEC dbo.SomeProc #Tags = '"ruby","rails","scruffy","rubyonrails"'
DROP TABLE dbo.Tags
I have an answer that doesn't require a UDF, XML
Because IN accepts a select statement
e.g. SELECT * FROM Test where Data IN (SELECT Value FROM TABLE)
You really only need a way to convert the string into a table.
This can be done with a recursive CTE, or a query with a number table (or Master..spt_value)
Here's the CTE version.
DECLARE #InputString varchar(8000) = 'ruby,rails,scruffy,rubyonrails'
SELECT #InputString = #InputString + ','
;WITH RecursiveCSV(x,y)
AS
(
SELECT
x = SUBSTRING(#InputString,0,CHARINDEX(',',#InputString,0)),
y = SUBSTRING(#InputString,CHARINDEX(',',#InputString,0)+1,LEN(#InputString))
UNION ALL
SELECT
x = SUBSTRING(y,0,CHARINDEX(',',y,0)),
y = SUBSTRING(y,CHARINDEX(',',y,0)+1,LEN(y))
FROM
RecursiveCSV
WHERE
SUBSTRING(y,CHARINDEX(',',y,0)+1,LEN(y)) <> '' OR
SUBSTRING(y,0,CHARINDEX(',',y,0)) <> ''
)
SELECT
*
FROM
Tags
WHERE
Name IN (select x FROM RecursiveCSV)
OPTION (MAXRECURSION 32767);
I use a more concise version of the top voted answer:
List<SqlParameter> parameters = tags.Select((s, i) => new SqlParameter("#tag" + i.ToString(), SqlDbType.NVarChar(50)) { Value = s}).ToList();
var whereCondition = string.Format("tags in ({0})", String.Join(",",parameters.Select(s => s.ParameterName)));
It does loop through the tag parameters twice; but that doesn't matter most of the time (it won't be your bottleneck; if it is, unroll the loop).
If you're really interested in performance and don't want to iterate through the loop twice, here's a less beautiful version:
var parameters = new List<SqlParameter>();
var paramNames = new List<string>();
for (var i = 0; i < tags.Length; i++)
{
var paramName = "#tag" + i;
//Include size and set value explicitly (not AddWithValue)
//Because SQL Server may use an implicit conversion if it doesn't know
//the actual size.
var p = new SqlParameter(paramName, SqlDbType.NVarChar(50) { Value = tags[i]; }
paramNames.Add(paramName);
parameters.Add(p);
}
var inClause = string.Join(",", paramNames);
Here is another answer to this problem.
(new version posted on 6/4/13).
private static DataSet GetDataSet(SqlConnectionStringBuilder scsb, string strSql, params object[] pars)
{
var ds = new DataSet();
using (var sqlConn = new SqlConnection(scsb.ConnectionString))
{
var sqlParameters = new List<SqlParameter>();
var replacementStrings = new Dictionary<string, string>();
if (pars != null)
{
for (int i = 0; i < pars.Length; i++)
{
if (pars[i] is IEnumerable<object>)
{
List<object> enumerable = (pars[i] as IEnumerable<object>).ToList();
replacementStrings.Add("#" + i, String.Join(",", enumerable.Select((value, pos) => String.Format("#_{0}_{1}", i, pos))));
sqlParameters.AddRange(enumerable.Select((value, pos) => new SqlParameter(String.Format("#_{0}_{1}", i, pos), value ?? DBNull.Value)).ToArray());
}
else
{
sqlParameters.Add(new SqlParameter(String.Format("#{0}", i), pars[i] ?? DBNull.Value));
}
}
}
strSql = replacementStrings.Aggregate(strSql, (current, replacementString) => current.Replace(replacementString.Key, replacementString.Value));
using (var sqlCommand = new SqlCommand(strSql, sqlConn))
{
if (pars != null)
{
sqlCommand.Parameters.AddRange(sqlParameters.ToArray());
}
else
{
//Fail-safe, just in case a user intends to pass a single null parameter
sqlCommand.Parameters.Add(new SqlParameter("#0", DBNull.Value));
}
using (var sqlDataAdapter = new SqlDataAdapter(sqlCommand))
{
sqlDataAdapter.Fill(ds);
}
}
}
return ds;
}
Cheers.
The only winning move is not to play.
No infinite variability for you. Only finite variability.
In the SQL you have a clause like this:
and ( {1}==0 or b.CompanyId in ({2},{3},{4},{5},{6}) )
In the C# code you do something like this:
int origCount = idList.Count;
if (origCount > 5) {
throw new Exception("You may only specify up to five originators to filter on.");
}
while (idList.Count < 5) { idList.Add(-1); } // -1 is an impossible value
return ExecuteQuery<PublishDate>(getValuesInListSQL,
origCount,
idList[0], idList[1], idList[2], idList[3], idList[4]);
So basically if the count is 0 then there is no filter and everything goes through. If the count is higher than 0 the then the value must be in the list, but the list has been padded out to five with impossible values (so that the SQL still makes sense)
Sometimes the lame solution is the only one that actually works.

Querying all records are true in sql-server - is casting expensive performance wise

I have a table with a column of bit values. I want to write a function that returns true if all records of an associated item are true.
One way I found of doing it is:
Select #Ret = CAST(MIN(CAST(IsCapped as tinyInt)) As Bit)
from ContractCover cc
Inner join ContractRiskVersion crv on cc.ContractRiskId = crv.ContractRiskId
WHERE crv.ContractVersionId = #ContractVersionId
AND cc.IsActive = 1
return #ret
But is the casting to int to get the minimum expensive? Should I instead just be querying based on say:
(count(Id) where IsCapped = 0 > 0) returning false rather than doing the multiple casts?
In the execution plan it doesn't seem like calling this function is heavy in the execution (but I'm not too familiar with analysing query plans - it just seems to have the same % cost as another section of the stored proc of like 2%).
Edit - when I execute the stored proc which calls the function and look at the execution plan - the part where it calls the function has a query cost (relative to the batch) : 1% which is comparable to other sections of the stored proc. Unless I'm looking at the wrong thing :)
Thanks!!
I would do this with an exists statement as it will jump out of the query from the moment it finds 1 record where IsCapped = 0 where as your query will always read all data.
CREATE FUNCTION dbo.fn_are_contracts_capped(#ContractVersionId int)
RETURNS bit
WITH SCHEMABINDING
AS
BEGIN
DECLARE #return_value bit
IF EXISTS(
SELECT 1
FROM dbo.ContractCover cc
JOIN dbo.ContractRiskVersion crv
ON cc.ContractRiskId = crv.ContractRiskId
WHERE crv.ContractVersionId = #ContractVersionId
AND cc.IsActive = 1
AND IsCapped = 0)
BEGIN
SET #return_value = 0
END
ELSE
BEGIN
SET #return_value = 1
END
RETURN #return_value
END
Compared to the IO required to read the data, the cast will not add a lot of overhead.
Edit: wrapped code in a scalar function.
Casting in the SELECT would be CPU and memory bound. Not sure how much in this case--under normal circumstances we usually try to optimize for IO first, and then worry about CPU and memory second. So I don't have a definite answer for you there.
That said, the problem with this particular solution to your problem is that it won't short-circuit. SQL Server will read out all rows where ContractVersionId = #ContractVersionId and IsActive = 1, convert IsCapped to an INT, and take the min, where really, you can quit as soon as you find a single row where IsCapped = 0. It won't matter much if ContactVersionId is highly selective, and only returns a very small fraction of the table, or if most rows are capped. But if ContactVersionId is not highly selective, or if a high percentage of the rows are uncapped, then you are asking SQL Server to do too much work.
Second consideration is that scalar-valued functions are a notorious performance drag in SQL Server. It is better to create as an in-line table function if possible, eg:
create function AreAllCapped(#ContractVersionId int)
returns table as return (
select
ContractVersionId = #ContractVersionId
, AreAllCapped = case when exists (
select *
from ContractCover cc
join ContractRiskVersion crv on cc.ContractRiskId = crv.ContractRiskId
where crv.ContractVersionId = #ContractVersionId
and cc.IsActive = 1
and IsCapped = 0
)
then 0 else 1 end
)
Which you then can call using CROSS APPLY in the FROM clause (assuming SQL 2005 or later).
Final note: taking the count where IsCapped = 0 has similar problems. It's like the difference between Any() and Count() in LINQ, if you are familiar. Any() will short-circuit, Count() has to actually count all the elements. SELECT COUNT(*) ... WHERE IsCapped = 0 still has to count all the rows, even though a single row is all you need to move on.
Of course, it is a known fact that a bit column can't be passed as an argument to an aggregate function (and thus, if it needs to be passed, you have to cast it as an integer first), but bit columns can be sorted by. Your query, therefore, could be rewritten like this:
SELECT TOP 1 #Ret = IsCapped
FROM ContractCover cc
INNER JOIN ContractRiskVersion crv on cc.ContractRiskId = crv.ContractRiskId
WHERE crv.ContractVersionId = #ContractVersionId
AND cc.IsActive = 1
ORDER BY IsCapped;
Note that in this particular query it is assumed that IsCapped can't be NULL. If it can, you'll need to add an additional filter to the WHERE clause:
AND IsCapped IS NOT NULL
Unless, of course, you would actually prefer to return NULL instead of 0, if any.
As for the cost of casting, I don't really have anything to add to what has already been said by Filip and Peter. I do find it a nuisance that bit data require casting before aggregating, but that's never something of a primary concern.

T-sql - determine if value is integer

I want to determine if a value is integer (like TryParse in .NET). Unfortunatelly ISNUMERIC does not fit me because I want to parse only integers and not every kind of number. Is there such thing as ISINT or something?
Here is some code to make things clear. If MY_FIELD is not int, this code would fail:
SELECT #MY_VAR = CAST(MY_FIELD AS INT)
FROM MY_TABLE
WHERE MY_OTHER_FIELD = 'MY_FILTER'
Thank you
Here's a blog post describing the creation of an IsInteger UDF.
Basically, it recommends adding '.e0' to the value and using IsNumeric. In this way, anything that already had a decimal point now has two decimal points, causing IsNumeric to be false, and anything already expressed in scientific notation is invalidated by the e0.
In his article Can I convert this string to an integer?, Itzik Ben-Gan provides a solution in pure T-SQL and another that uses the CLR.
Which solution should you choose?
Is the T-SQL or CLR Solution Better? The advantage of using the T-SQL
solution is that you don’t need to go outside the domain of T-SQL
programming. However, the CLR solution has two important advantages:
It's simpler and faster. When I tested both solutions against a table
that had 1,000,000 rows, the CLR solution took two seconds, rather
than seven seconds (for the T-SQL solution), to run on my laptop. So
the next time you need to check whether a given string can be
converted to an integer, you can include the T-SQL or CLR solution
that I provided in this article.
If you only want to maintain T-SQL, then use the pure T-SQL solution. If performance is more important than convenience, then use the CLR solution.
The pure T-SQL Solution is tricky. It combines the built-in ISNUMERIC function with pattern-matching and casting to check if the string represents an int.
SELECT keycol, string, ISNUMERIC(string) AS is_numeric,
CASE
WHEN ISNUMERIC(string) = 0 THEN 0
WHEN string LIKE '%[^-+ 0-9]%' THEN 0
WHEN CAST(string AS NUMERIC(38, 0))
NOT BETWEEN -2147483648. AND 2147483647. THEN 0
ELSE 1
END AS is_int
FROM dbo.T1;
The T-SQL part of the CLR solution is simpler. You call the fn_IsInt function just like you would call ISNUMERIC.
SELECT keycol, string, ISNUMERIC(string) AS is_numeric,
dbo.fn_IsInt(string) AS is_int
FROM dbo.T1;
The C# part is simply a wrapper for the .NET's parsing function Int32.TryParse. This works because the SQL Server int and the .NET Int32 are both 32-bit signed integers.
using System;
using System.Data.SqlTypes;
public partial class UserDefinedFunctions
{
[Microsoft.SqlServer.Server.SqlFunction]
public static SqlBoolean fn_IsInt(SqlString s)
{
if (s.IsNull)
return SqlBoolean.False;
else
{
Int32 i = 0;
return Int32.TryParse(s.Value, out i);
}
}
};
Please read Itzik's article for a full explanation of these code samples.
With sqlserver 2005 and later you can use regex-like character classes with LIKE operator. See here.
To check if a string is a non-negative integer (it is a sequence of decimal digits) you can test that it doesn't contain other characters.
SELECT numstr
FROM table
WHERE numstr NOT LIKE '%[^0-9]%'
Note1: This will return empty strings too.
Note2: Using LIKE '%[0-9]%' will return any string that contains at least a digit.
See fiddle
WHERE IsNumeric(MY_FIELD) = 1 AND CAST(MY_FIELD as VARCHAR(5)) NOT LIKE '%.%'
That is probably the simplest solution. Unless your MY_FIELD contains .00 or something of that sort. In which case, cast it to a float to remove any trailing .00s
Necromancing.
As of SQL-Server 2012+, you can use TRY_CAST, which returns NULL if the cast wasn't successful.
Example:
DECLARE #foo varchar(200)
SET #foo = '0123'
-- SET #foo = '-0123'
-- SET #foo = '+0123'
-- SET #foo = '+-0123'
-- SET #foo = '+-0123'
-- SET #foo = '.123'
-- SET #foo = '1.23'
-- SET #foo = '.'
-- SET #foo = '..'
-- SET #foo = '0123e10'
SELECT CASE WHEN TRY_CAST(#foo AS integer) IS NULL AND #foo IS NOT NULL THEN 0 ELSE 1 END AS isInteger
This is the only really reliable way.
Should you need support for SQL-Server 2008, then fall back to Sam DeHaan's answer:
SELECT CASE WHEN ISNUMERIC(#foo + '.e0') = 1 THEN 1 ELSE 0 END AS isInteger
SQL-Server < 2012 (aka 2008R2) will reach end of (extended) support by 2019-07-09.
At this time, which is very soon, support for < 2012 can be dropped.
I wouldn't use any of the other hacks at this point in time anymore.
Just tell your frugal customers to update - it's been over 10 years since 2008.
See whether the below query will help
SELECT *
FROM MY_TABLE
WHERE CHARINDEX('.',MY_FIELD) = 0 AND CHARINDEX(',',MY_FIELD) = 0
AND ISNUMERIC(MY_FIELD) = 1 AND CONVERT(FLOAT,MY_FIELD) / 2147483647 <= 1
The following is correct for a WHERE clause; to make a function wrap it in CASE WHEN.
ISNUMERIC(table.field) > 0 AND PATINDEX('%[^0123456789]%', table.field) = 0
This work around with IsNumeric function will work:
select * from A where ISNUMERIC(x) =1 and X not like '%.%'
or Use
select * from A where x **not like** '%[^0-9]%'
declare #i numeric(28,5) = 12.0001
if (#i/cast(#i as int) > 1)
begin
select 'this is not int'
end
else
begin
select 'this is int'
end
As of SQL Server 2012, the TRY_CONVERT and TRY_CAST functions were implemented. Thise are vast improvements over the ISNUMERIC solution, which can (and does) give false positives (or negatives). For example if you run the below:
SELECT CONVERT(int,V.S)
FROM (VALUES('1'),
('900'),
('hello'),
('12b'),
('1.1'),
('')) V(S)
WHERE ISNUMERIC(V.S) = 1;
Using TRY_CONVERT (or TRY_CAST) avoids that:
SELECT TRY_CONVERT(int,V.S),
V.S,
ISNUMERIC(V.S)
FROM (VALUES('1'),
('900'),
('hello'),
('12b'),
('1.1'),
('')) V(S)
--WHERE TRY_CONVERT(int,V.S) IS NOT NULL; --To filter to only convertable values
Notice that '1.1' returned NULL, which cause the error before (as a string represtation of a decimal cannot be converted to an int) but also that '' returned 0, even though ISNUMERIC states the value "can't be converted".
Use TRY_CONVERT which is an SQL alternative to TryParse in .NET. IsNumeric() isn’t aware that empty strings are counted as (integer)zero, and that some perfectly valid money symbols, by themselves, are not converted to (money)zero. reference
SELECT #MY_VAR = CASE WHEN TRY_CONVERT(INT,MY_FIELD) IS NOT NULL THEN MY_FIELD
ELSE 0
END
FROM MY_TABLE
WHERE MY_OTHER_FIELD = 'MY_FILTER'
I think that there is something wrong with your database design. I think it is a really bad idea to mix varchar and numbers in one column? What is the reason for that?
Of course you can check if there are any chars other than [0-9], but imagine you have a 1m rows in table and your are checking every row. I think it won't work well.
Anyway if you really want to do it I suggest doing it on the client side.
I have a feeling doing it this way is the work of satan, but as an alternative:
How about a TRY - CATCH?
DECLARE #Converted as INT
DECLARE #IsNumeric BIT
BEGIN TRY
SET #Converted = cast(#ValueToCheck as int)
SET #IsNumeric=1
END TRY
BEGIN CATCH
SET #IsNumeric=0
END CATCH
select IIF(#IsNumeric=1,'Integer','Not integer') as IsInteger
This works, though only in SQL Server 2008 and up.
I tried this script and got the answer
ISNUMERIC(Replace(Replace([enter_your_number],'+','A'),'-','A') + '.0e0')
for example for up question this is answer:
SELECT #MY_VAR = CAST(MY_FIELD AS INT)
FROM MY_TABLE
WHERE MY_OTHER_FIELD = 'MY_FILTER' and ISNUMERIC(Replace(Replace(MY_FIELD,'+','A'),'-','A') + '.0e0') = 1
Why not just do something like:
CASE
WHEN ROUND(MY_FIELD,0)=MY_FIELD THEN CAST(MY_FIELD AS INT)
ELSE MY_FIELD
END
as MY_FIELD2
Sometimes you don't get to design the database, you just have to work with what you are given. In my case it's a database located on a computer that I only have read access to which has been around since 2008.
I need to select from a column in a poorly designed database which is a varchar with numbers 1-100 but sometimes a random string. I used the following to get around it (although I wish I could have re designed the entire database).
SELECT A from TABLE where isnumeric(A)=1
I am not a Pro in SQL but what about checking if it is devideable by 1 ?
For me it does the job.
SELECT *
FROM table
WHERE fieldname % 1 = 0
Use PATINDEX
DECLARE #input VARCHAR(10)='102030.40'
SELECT PATINDEX('%[^0-9]%',RTRIM(LTRIM(#input))) AS IsNumber
reference
http://www.intellectsql.com/post-how-to-check-if-the-input-is-numeric/
Had the same question. I finally used
where ATTRIBUTE != round(ATTRIBUTE)
and it worked for me
WHERE IsNumeric(value + 'e0') = 1 AND CONVERT(FLOAT, value) BETWEEN -2147483648 AND 2147483647
Seeing as this is quite old, but my solution isn't here, i thought to add another possible way to do this:
--This query only returns values with decimals
SELECT ActualCost
FROM TransactionHistory
where cast(ActualCost as int) != ActualCost
--This query only returns values without decimals
SELECT ActualCost
FROM TransactionHistory
where cast(ActualCost as int) = ActualCost
The easy part here is checking if the selected value is the same when cast as an integer.
we can check if its a non integer by
SELECT number2
FROM table
WHERE number2 LIKE '%[^0-9]%' and (( right(number2 ,len(number2)-1) LIKE '%[^0-9]%' and lefT(number2 ,1) <> '-') or ( right(number2 ,len(number2)-1) LIKE '%[^0-9]%' and lefT(number2 ,1) in ( '-','+') ) )
DECLARE #zip_code NCHAR(10)
SET #zip_code = '1239'
IF TRY_PARSE( #zip_code AS INT) / TRY_PARSE( #zip_code AS INT) = 1 PRINT 'integer'
ELSE PRINT 'not integer'
This works fine in SQL Server
SELECT (SELECT ISNUMERIC(2) WHERE ISNUMERIC(2)=1 AND 2 NOT LIKE '%.%')
Case
When (LNSEQNBR / 16384)%1 = 0 then 1
else 0
end

StringToDecimal Function - T-SQL Question

I have some dirty input data that is being imported into a raw source table within SQL Server (2008 R2). Fields that are defined as decimal(9,2) or decimal(4,2) by the input provider are coming in as strings, however, the strings do not always conform to the data definition (go figure!).
We import the data from flat files into the raw tables,then apply some conversion scripts to insert the 'cleaned' data into tables with the proper data types assigned to columns.
For instance:
raw_table
TotalAmount varchar(12)
clean_table
TotalAmount decimal(9,2)
Now, my question is this. If I want to do some 'basic' cleanup on this, I would want to do it in a function along the lines of:
CREATE FUNCTION [dbo].[StringToDecimal]
(
#conversionString VARCHAR(12)
)
RETURNS DECIMAL(9,2)
AS
BEGIN
DECLARE #rsp DECIMAL(9,2)
IF ISNUMERIC( LTRIM(RTRIM(REPLACE(#conversionString,' ',''))) ) = 1
BEGIN
SET #rsp = ISNULL( CONVERT( decimal(17,6), NULLIF( LTRIM(RTRIM(REPLACE(#conversionString,' ',''))),'') ), 0 )
END
ELSE
BEGIN
SET #rsp = 0 -- or we can return NULL here
END
RETURN #rsp
END
However, how could one go about supporting various sized decimals in this mix? Is there a way to parametrize the response type? I considered just returning a decimal of the largest size we generally see, then converting it again on the other end, however, you run into arithmetic overflow issues.
Would appreciate any thoughts/insight into solving this one!
Is there a way to parametrize the response type?
It's simpler than you think. Just return as a VARCHAR and do the casting to decimal(x,y) from the VARCHAR. You don't even need to cast - you can directly assign a VARCHAR (as long as it holds valid decimal data) to a decimal column/variable.
I will create 2 functions instead. StringToDecimal2 does the actual conversion, but returns one of 6 "error codes". You can use it to check why a string is invalid. Or use the wrapper dbo.StringToDecimal which just turns the invalid codes into NULL.
CREATE FUNCTION [dbo].[StringToDecimal2]
(
#conversionString VARCHAR(12),
#precision int, -- total digits
#scale int -- after decimal point
)
RETURNS VARCHAR(100)
AS
BEGIN
-- remove spaces, we'll allow this error. no need to trim
set #conversionString = REPLACE(#conversionString,' ','')
-- note: 1,234.56 (thousands separated) will be invalid, so will 1,234,56 (European decimals)
-- well, ok, let's clean up the thousands separators. BUT! It will incorrectly scale European decimals
set #conversionString = REPLACE(#conversionString,',','')
-- we don't support scientific notation either, so 1e4 (10,000) is out
if #conversionString like '%[^0-9.+-]%' return 'INVALID1' -- only digits and decimal are valid (plus +-)
if #conversionString like '%.%.%' return 'INVALID2' -- too many decimals
if #conversionString like '_%[+-]%' return 'INVALID3' -- +- symbol not in the first position
if #conversionString like '[.+-]' return 'INVALID4' -- a single character from "+-."
if #conversionString like '[+-].' return 'INVALID5' -- symbol and decimal only
-- add a decimal place so it is easier to work with below
if #conversionString not like '%.%'
set #conversionString = #conversionString + '.'
-- allow decimal places to go only as far as scale
set #conversionString = left(#conversionString, charindex('.', #conversionString)+#scale)
-- ensure the data is within precision number of digits in total
if charindex('.', #conversionString) > #precision - #scale + 1
return 'INVALID6' -- too many digits before decimal
RETURN #conversionString
END
GO
CREATE FUNCTION [dbo].[StringToDecimal]
(
#conversionString VARCHAR(12),
#precision int, -- total digits
#scale int -- after decimal point
)
RETURNS VARCHAR(100)
AS
BEGIN
RETURN case when [dbo].[StringToDecimal2](#conversionString, #precision, #scale) like 'INVALID%'
then null else [dbo].[StringToDecimal2](#conversionString, #precision, #scale) end
END
GO
Some tests:
select [dbo].[StringToDecimal2]('12342342', 9,2)
select convert(decimal(9,2),[dbo].[StringToDecimal]('1234234', 9,2))
select convert(decimal(9,2),[dbo].[StringToDecimal]('12342342', 9,2))
select convert(decimal(9,2),[dbo].[StringToDecimal]('123423.3333', 9,2))
select convert(decimal(20,10),[dbo].[StringToDecimal]('123423sd.3333', 20,10))
select convert(decimal(20,10),[dbo].[StringToDecimal]('123423sd..3333', 20,10))
select convert(decimal(20,10),[dbo].[StringToDecimal]('-123423.3333', 20,10))
select convert(decimal(20,10),[dbo].[StringToDecimal]('+123423..3333', 20,10))
Thanks for the extra information. It sounds like you have three steps:
Remove all characters from the string that are not digits or a decimal point (do you ever get multiple points in one string?)
Convert to (9,5) or (4,1) as appropriate (how do you decide this? is there rounding? does 10X.781 become 10.78100 or 10.7 or 10.8?)
Insert/update the final value somewhere
Based on point 1 alone, I would immediately avoid TSQL and think about an external script or CLR procedure. A CLR function could do the parsing, but you still have the problem of returning different data types.
Since this appears to be some kind of ETL task, in my environment I would probably implement it as a script component in an SSIS package. The component would do the parsing and send the clean data to different outputs for further processing. If it was a one-time task I would use a Python script to parse the input data and generate INSERT or UPDATE statements.
I don't know if any of those solutions are suitable for you, but maybe it'll give you some ideas. And you should probably avoid the ISNUMERIC() function; search this site or Google to find some of the 'strange' input that it considers to be numeric.

I need to know how i can write IF statements and CASE break statements that use and execute queries, etc in MySQL?

I need to execute a query that is highly dependent on several conditions what need to be checked by analyzing that data in other tables, the base goal is to return the rows from the tables if all of the conditions in the other tables are true, I am fed up with INNER joins LEFT joins and WHERE statement, i need to look up one table, if the returned value is 1, 0 or 4 or a set of values, i need to execute an other statement, and based on the resuts of that i need to execute one last statement which is my final result.
as far as functions are procedures are concerned, i studies the MySQL documentation like hell and all it gives me is this ::
DELIMITER $$
CREATE DEFINER=`root`#`localhost` FUNCTION `SimpleCompare`(n INT, m INT) RETURNS varchar(20) CHARSET latin1
BEGIN
DECLARE s VARCHAR(20);
IF n > m THEN SET s = '>';
ELSEIF n = m THEN SET s = '=';
ELSE SET s = '<';
END IF;
SET s = CONCAT(n, ' ', s, ' ', m);
RETURN s;
END
Now this is so plain, i dont even know where to start, I the "returns varchar(20)" what does it need to be if im expecting it to return a table of 10 rows and not a VARCHAR(20), what do I declare "DECLARE s VARCHAR(20);" as if i want it to be a table not a VARCHAR(20).
the (n > m) after the "IF" how to i replace it with my own query ,
and after I do that, the "THEN SET s = '>'" how do i set s = to the query results ?, this is driving me crazy the syntax is beyond me, and the documentation does not explain anything.
Thanks in advance
To my knowledge, MySQL doesn't support a table valued data type. The use of the function you posted would be:
SELECT simplecompare(yt.n, yt.m) AS eval
FROM YOUR_TABE yt
...which would return:
eval
--------
1 = 1
2 < 3
etc.
SQL is set based, which is different from typical programming (procedural or OO).

Resources