Custom Sorting Function - of SQL Server - sql-server

I have a column in SQL Server 2005 that stores a version number as a string that i would like to sort by. I have been unable to find out how to sort this column, although i am guessing it would be some kind of custom function or compare algorithm.
Can anyone point me in the right direction of where to start? I may be googling the wrong stuff.
Cheers
Tris

I'd use separate int columns (e.g. MajorCol + MinorCol if you are tracking major + minor versions) and run something like
order by MajorCol, MinorCol
in my query.

I would look at using a persisted computed column which processes the string into an int or string or something appropriate for sorting in the native SQL Server sort - i.e.
'1.1.1.1' -> '001.001.001.001'
'10.10.10.10' -> '010.010.010.010'
'1.10.1.10' -> '001.010.001.010'
So that you can sort by the computed column and get expected results.
Alternatively, you can use such an operation inline, but it might be very slow. In addition scalar UDFs are extremely slow.

Creating an SQL CLR function is the way to go. They're extremely fast and powerful. It would be quick and effective as you wouldn't have to change any existing code, and you could specify all the information you need right in your SQL statements.
The SQL CLR function could accept an input string, as well as other parameters specifying which piece of information you'd like to extract from the input string. You could then sort on the return values of the function.
Specifically, I'd create a generic function that accepts three parameters: an input string, a regular expression, and a group name. That function would allow you to pass your database field and a regular expression with named groups right in the SQL statement.
The SQL CLR function would create a Regex, test it against the string, and would ultimately return the matched group's value or null if there was no match or the group was not matched (if the group was optional). Ideally, you'd want to pass the same regular expression to each call (perhaps as a variable like #regex), to take advantage of any CLR caching of the compiled regular expression. The end result would be very flexible and fast.
Regular expression options can be specified inline in the pattern like so: "(?imnsx-imnsx:subexpression)". See: msdn.microsoft.com/en-us/library/yd1hzczs.aspx
The code for such a function would look something like this:
[SqlFunction(IsDeterministic=true,IsPrecise=true,DataAccess=DataAccessKind.None,SystemDataAccess=SystemDataAccessKind.None)]
public static SqlString RegexMatchNamedGroup( SqlChars input, SqlString pattern, SqlString group_name )
{
Regex regex = new Regex( pattern.Value );
Match match = regex.Match( new string( input.Value ) );
if (!match.Success) //return null if match failed
return SqlString.Null;
if (group_name.IsNull) //return entire string if matched when no group name is specified
return match.Value;
Group group = match.Groups[group_name.Value];
if (group.Success)
return group.Value; //return matched group value
else
return SqlString.Null; //return null if named group was not matched
}
Your SQL statement could then sort on pieces of your information like so:
declare #regex nvarchar(2000) = '^(?<Major>\d{1,3})\.(?<Minor>\d{1,3})';
select VersionNumber
from YourTable
order by
Cast(RegexMatchNamedGroup( VersionNumber, #regex, 'Major') as int),
Cast(RegexMatchNamedGroup( VersionNumber, #regex, 'Minor') as int)

Related

How can you create a table (or other object) that always returns the value passed to its WHERE-clause, like a mirror

There is a legacy application that uses a table to translate job names to filenames. This legacy application queries it as follows:
SELECT filename FROM aJobTable WHERE jobname = 'myJobName'
But in reality those jobnames always match the filenames (e.g. 'myJobName.job' is the jobname but also the filename) That makes this table appear unnecessary. But unfortunately, we cannot change the code of this program, and the program just needs to select it from a table.
That's actually a bit annoying. Because we do need to keep this database in sync. If a jobname is not in the table, then it cannot be used. So, as our only way out, right now we have some vbscripts to synchronize this table, adding records for each possible filename. As a result, the table just 2 columns with identical values. -- We want to get rid of this.
So, we have been dreaming about some hack that queries the data with the jobname, but just always returns the jobname again, like a copy/mirror query. Then we don't actually have to populate a table at all.
"Exploits"
The following can be configured in this legacy application. My hunch is that these may open the door for some tricks/hacks.
use of either MS Access or SQL Server (we prefer sql server)
The name of the table (e.g. aJobTable)
The name of the filename column (e.g. filename)
The name of the jobname column (e.g. jobname)
Here is what I came up with:
If I create a table-valued function mirror(a) then I get pretty close to what I want. Then I could use it like
SELECT filename FROM mirror('MyJobName.job')
But that's just not good enough, it would be if I could force it to be like
SELECT filename FROM mirror WHERE param1 = 'MyJobName.job'
Unfortunately, I don't think it's possible to call functions like that.
So, I was wondering if perhaps somebody else knows how to get it working.
So my question is: "How can you create a table (or other object) that always returns the value passed to its WHERE-clause, like a mirror."
It's kinda hard to answer not knowing the code that the application use, but if we assume it only takes strings and concatenate them without any tests whatsoever, I would assume code like this: (translated to c#)
var sql = "SELECT "+ field +" FROM "+ table +" WHERE "+ conditionColumn +" = '"+ searchValue +"'";
As this is an open door for SQL injection, and given the fact that SQL Server allows you two ways of creating an alias - value as alias and alias = value,
you can take advantage of that and try to generate an SQL statement like this:
SELECT field /* FROM table WHERE conditionColumn */ = 'searchValue'
So field should be "field /* ",
and conditionColumn should be "conditionColumn */"
table name doesn't matter, you could leave an empty string for it.

Multi value parameter not working in SSRS report

I have a SSRS report. there is a long SQL query on the main query, in the last SELECT I want to filter the results with WHERE expression, the filter should be with a multi value parameter.
I set the parameter in this way:
Create a new Dataset with a query.
Add a new parameter to the Parameters folder (with name NewParam).
Check the "Allow multiple values" checkbox.
Add the parameter to the "Main Query" and set the value with this expression:
=Join(Parameters!NewParam.Value,",")
At the end of the Main Query I filter the results:
select *
from #FinalStatusTbl
where Test_Number in (#NewParam)
order by Priority
The problem is:
On the report when I choose one value from the list I got expected results, but If I choose multi values the results are empty (not got an error.)
Do you have any idea why?
(When I try this: where Test_Number in ('Test 1', 'Test 2') it works well).
When you create a dataset with a sql query, multi valued parameters work with the in(#ParamName) without any changes.
Replace your =Join(Parameters!NewParam.Value,",") with just =Parameters!NewParam.Value and you should be fine.
That said, the reason you see people using that join expression is because sometimes your query will slow down considerably if your parameter has a lot of potential selections and you data is reasonably large. What is done here is to combine the join expression with a string splitting function in the dataset that converts the resulting Value1,Value2,Value3 string value in a table that can be used in the query via inner join.
This is also a requirement if passing multiple values as a parameter to a stored procedure, as you can't use the in(#ParamName) syntax.
You could try taking the parameter out of the where clause and use the parameter in the filters section of the dataset properties.
This will effectively shift the filtering from the SQL to SSRS.
What you need to do is split your string in the database. What is being passed to your query is 'Test 1, Test 2' as a complete string, NOT 'Test 1' and 'Test 2'. This is why a single value works, and multiple values do not.
Here is a really good link on how to split strings, in preparation for your scenario. The function I most often use is the CTE example, which returns a table of my split strings. Then I change my SQL query to use IN on the returned table.
In your example, you will want to write WHERE Test_Number IN (SELECT Item FROM dbo.ufn_SplitStrings(#NewParam) , where ufn_SplitString is the function you create from the link previously mentioned.
This is what I did and it works well to me. You can try it also.
=sum(if(Fields!Business_Code.Value = "PH"
and (Fields!Vendor_Code.Value = "5563"
and Fields!Vendor_Code.Value = "5564"
and Fields!Vendor_Code.Value = "5565"
and Fields!Vendor_Code.Value = "5551")
, Fields!TDY_Ordered_Value.Value , nothing ))

Use String parameter for RegEx in query

In my query (the database is a sql server) I use a RegEx for a select command like this:
SELECT * FROM test WHERE id LIKE '1[2,3]'
(This query is tested and returns the data I want)
I want to use a paramter for this RegEx. For that I definded the Paramter in iReport $P{id} as a string and the value is "1[2,3]".
In my query I use now this parameter like this:
SELECT * FROM test WHERE id LIKE $P{id}
As result I get a blank page. I think the problem is that the value of the parameter is defined with " ". But with ' ' I get a compiler error that the paramter isn't a string.
I hope someone can help me.
LIKE applies to text values, not to numeric values. Since id is numeric use something like this:
SELECT * FROM test WHERE id IN (12, 13)
with the parameter
SELECT * FROM test WHERE id IN ($P!{id_list})
and supply a comma separated list of ids for the parameter. The bang (!) makes sure that the parameter will be inserted as-is, without string delimiters.
Btw: LIKE (Transact-SQL) uses wildcards, not regex.
You can still use LIKE since there exists an implicit conversion from numeric types to text in T-SQL, but this will result in a (table or index) scan, where as the IN clause can take advantage of indexes.
The accepted answer works but it is using String replacement, read more about sql-injection, to understand why this is not good practice.
The correct way to execute this IN query in jasper report (using prepared statement) is:
SELECT * FROM test WHERE $X{IN, id, id_list}
For more information as the use of NOTIN, BETWEEN ecc. see JasperReports sample reference for query

How to get data from database according to string length without using any string function

I have to get the records from a table field where Length of record/data/string is greater then 8 characters. I cannot use any string function as the query has to be used on (MySQL, MSSQL, Oracle).
I don't want to do the below EXAMPLE:
List<String> names = new ArrayList<String>();
String st = select 'name' from table;
rs = executeSQL(st);
if ( rs != null )
{
rs.next();
names.add(rs.getString(1));
}
for(String name : names)
{
if(name.length() > 8)
result.add(name);
}
Any idea other then the one coded above? A query that can get the required result instead of processing on the retrieved data and then getting the required result.
Thank you for any help / clue.
JDBC Drivers may implement a JDBC escapes for the functions listed in appendix D (Scalar Functions) of the JDBC specification. A driver should convert the scalar functions it supports to the appropriate function on the database side. A list of the supported functions can be queried using 'DatabaseMetaData.getStringFunctions()'
To use this in a query you would then either use CHAR_LENGTH(string) or LENGTH(string) like :
SELECT * FROM table WHERE {fn CHAR_LENGTH(field)} > 8
You can replace CHAR_LENGTH with LENGTH. The driver (if it supports this function) will then convert it to the appropriate function in the underlying database.
From section 13.4.1 Scalar Functions of the JDBC 4.1 specification:
Appendix D “Scalar Functions" provides a list of the scalar functions
a driver is expected to support. A driver is required to implement
these functions only if the data source supports them, however.
The escape syntax for scalar functions must only be used to invoke the
scalar functions defined in Appendix D “Scalar Functions". The escape
syntax is not intended to be used to invoke user-defined or vendor
specific scalar functions.
I think you may be better off leveraging the power of the database and implementing a factory for your SQL statements (or perhaps for objects encapsulating your SQL functionality).
That way you can configure your factory with the name/type of the database, and it'll give you the appropriate SQL statements for that database. It gives you a clean means of parameterising this info, whilst allowing you to leverage the functionality of your databases and not having to replicate the database functionality in a suboptimal fashion in your code.
e.g.
DabaseStatementFactory fac = DatabaseStatementFactory.for(NAME_OF_DATABASE);
String statement = fac.getLongNames();
// then use this statement. It'll be configured for each db type
It's probably wise to encapsulate further and use something like:
DabaseStatementFactory fac = DatabaseStatementFactory.for(NAME_OF_DATABASE);
List<String> names = fac.getLongNames();
such that you're not making assumptions re. common schema and means of queries etc.
Another solution that I found is:
Select name from table where name like '________';
SQL counts the underscore (_) characters and return a name of length equal to number of underscore characters.

SQL Server ISDATE In Indexed View

I have a indexed view where I basically need to do this
SELECT ...
CASE
WHEN ISDATE(ColumnName) = 1 THEN CONVERT(datetime, ColumnName, 103)
ELSE NULL
END AS ViewColumn
....
Trying to create the index yields:
Cannot create index on view
'....'. The function
'isdate' yields nondeterministic results. Use a deterministic system
function, or modify the user-defined function to return deterministic
results.
MSDN says
ISDATE is deterministic only if you use it with the CONVERT function,
if the CONVERT style parameter is specified, and style is
not equal to 0, 100, 9, or 109.
here http://msdn.microsoft.com/en-us/library/ms187347.aspx.
But I don't know what that means at all. As far as I can tell, I am using it with a CONVERT function....
Any way to work around this?
It should be, if at all:
SELECT ...
CASE
WHEN ISDATE(ColumnName) = 1 THEN CONVERT(datetime, ColumnName, 103)
ELSE NULL
END
....
but, you are not using ISDATE WITH CONVERT, since there is no expression like
ISDATE(CONVERT(varchar,ColumnName,112))
without the nested convert the return value is dependend on things like language settings, hence it's nondeterministic behaviour. Without "external" knowledge, it's not possible to predict the result one is getting, based on the input alone.
Reference
What are the requirements for Indexed views?
There are several requirements that you must take into consideration when using Indexed views.
1. View definition must always return the same results from the same underlying data.
2. Views cannot use non-deterministic functions.
3. The first index on a View must be a clustered, UNIQUE index.
4. If you use Group By, you must include the new COUNT_BIG(*) in the select list.
5. View definition cannot contain the following
(A) TOP
(B) Text, ntext or image columns
(C)DISTINCT
(d)MIN, MAX, COUNT, STDEV, VARIANCE, AVG
(E)SUM on a nullable expression
(F)A derived table
(G)Rowset function
(H)Another view
(I)UNION
(J)Subqueries, outer joins, self joins
(K)Full-text predicates like CONTAIN or FREETEXT
(L)COMPUTE or COMPUTE BY
(M)Cannot include order by in view definition

Resources