NHibernate LIKE query on date column - c#

I'm using the following code to dynamically filter data (I'm using MySQL as a DB engine):
var filter = string.Empty;
if (!string.IsNullOrWhiteSpace(filterNumber))
{
filter = "t.Number LIKE :filterNumber";
}
var query = string.Format("SELECT t FROM Table t WHERE 1=1 AND {0} ORDER BY {1} {2}", filter, orderBy, orderDirection);
var q = Session.CreateQuery(query);
if (!string.IsNullOrWhiteSpace(filterNumber))
{
q.SetParameter<string>("filterNumber", "%" + filterNumber + "%");
}
q.SetFirstResult(offset);
q.SetMaxResults(limit);
return q.List<Table>();
(assume that filterNumber, orderBy, orderDirection, offset and limit are method's parameters and 1=1 was added only for the sake of this question so the query always work)
It does work when Number is a string (VARCHAR in MySQL) column but it does not work when it is a datetime or integer column.
For datetime it raises the exception:
could not execute query […] Name:filterDate - Value:%2012% and inner exception is Specified cast is not valid.
For integer/float columns the exception is:
could not execute query […] Name:filterPrice - Value:%100% and inner exception is Input string was not in a correct format.
(Date and Price are another columns in the table)
How to dynamically create such a LIKE query on date/numbers columns? This kind of query works well in MySQL:
SELECT * FROM `Table` WHERE `Date` LIKE '%2012%'

Based on #usr comment I managed to do casting in HQL:
filter = "CAST(t.Date AS String) LIKE :filterDate";
It works both for dates and numbers.

It is probably not such a good idea to use LIKE with dates/numbers if you can avoid it. If the intention is to match on year, you should use the year(alias.property) HQL function (no need to convert your query to the Criteria API, as this is the HQL syntax) and the regular equals operator. Or less/greater than.
I suspect the SQL works through automatic cast to varchar, but unless you really do need pattern matching, I would think this has significantly worse performance compared to working with date extraction or BETWEEN.
If you do need pattern matching, you should cast the date/number to string.

Related

How do I compress a SQL query string to a small string less than 258 chars?

I want to create a unique small string <= 258 chars that is suitable as a windows filename.
This is to uniquely label a Xml query result.
Here is a sample query:
SELECT * FROM ( SELECT [utcDT],
MAX(CASE WHEN[Symbol] = 'fish' THEN[Close] END) AS [fish],
MAX(CASE WHEN[Symbol] = 'chips' THEN[Close] END) AS [chips]
FROM [DATA].[1M].[ASTS_NOGAP]
WHERE [Date] >= '2011-12-27'
AND [Date] <= '2012-07-01'
AND [Symbol] IN ('fish','chips')
GROUP BY [utcDT] ) AS A
WHERE [utcDT] IS NOT NULL AND [fish] IS NOT NULL AND [chips] IS NOT NULL
ORDER BY [utcDT]
BUT is could be a longer query.
The compress is one way only, i.e. I do NOT need to decompress.
I want to end up with a unique file name like:
ksdgfsbhdfjksgdjbajysjdgyasagfdjahgdkjasgjgfjkgjkgdjkfgjskdjfgsajgdjfgjsgy.xml
EDIT1:
The generated filename must be unique to the query - such that another
app would generate the same filename for the same query.
How can I achieve this?
There is a small risk for collisions, but this should do what you need:
public string GetUniqueFileNameForQuery(string sql)
{
using (var hasher = SHA256.Create())
{
var queryBytes = Encoding.UTF8.GetBytes(sql);
var queryHash = hasher.ComputeHash(queryBytes);
// "/" may be included, but is not legal for file names
return Convert.ToBase64String(queryHash).Replace("/", "-")+".xml";
}
}
This needs using System.Security.Cryptography; at the top of the file.
I also need to add a note about working with SQL from client code languages like C#.
Most queries are going to need input of some kind: an ID field for a lookup, a date range, a username, something to tell the query which records you need out of a larger set. It's very poor practice to substitute these inputs directly into the SQL string in your C# (or other language) code. That opens you up to an issue known as SQL Injection, and it's kind of a big deal.
Instead, for most all queries, there will be a placeholder variable name for each input argument. It matters for this question because you'll have the same SQL query text for two queries that differ only by arguments.
For example, say you have this query:
SELECT * FROM Users WHERE Username = #Username
You run this query twice, once with 'jsmith' as the input, and once with 'jdoe'. The SQL didn't change, and therefore the encoded file name didn't change.
You maybe be inclined to ask to get the value of the SQL after the parameter inputs are substituted into the query, but this misunderstands what happens. The parameter inputs are never, at any time, substituted into the sql query. That's the whole point. Even the database server will instead treat them as procedure variables.
The point here is you also need a way to encode any parameter data used with your query. Here's one basic naive option:
public string GetUniqueFileNameForQuery(DbCommand query)
{
var sql = query.CommandText;
foreach(var p in query.Parameters)
{
sql = sql.Replace(p.Name, p.Value.ToString());
}
using (var hasher = SHA256.Create())
{
var queryBytes = Encoding.UTF8.GetBytes(sql);
var queryHash = hasher.ComputeHash(queryBytes);
// "/" may be included, but is not legal for file names
return Convert.ToBase64String(queryHash).Replace("/", "-")+".xml";
}
}
Note: this code could produce invalid SQL. For example, you might end up with something like this:
SELECT * FROM Users WHERE LastName = O'Brien
But since you're not actually trying to run the query, that should be okay. You also need to be careful with systems like OleDB, which uses positional matching and ? for all parameter placeholders. In this case, the parameter name won't match the placeholder, or even if it did, the first parameter would match the placeholder for all the others.

How to order by concatenated values in Entity Framework / Linq?

I have a situation where the records in a parent table (Record) can have one of two related records in child tables (PhysicalPerson and Company). One of the columns will be always empty.
When displaying the records in a UI grid, user should see only one of the two names in OwnerName column and user should be able to sort the column OwnerName without any knowledge if the OwnerName for any record comes from Company or from PhysicalPerson.
To avoid denormalizing data and copying and maintaining Name column, I attempted to do it all in a Linq query.
Basically, the desired SQL order by expression that works should look like this:
ORDER BY CONCAT(Record.PhysicalPerson.Name,
Record.PhysicalPerson.Surname,
Record.Company.Name)
This would automatically ignore NULL values and results look acceptable.
So I tried to implement it in Linq:
query = query.OrderBy(x => x.PhysicalPerson.Name +
x.PhysicalPerson.Surname +
x.Company.Name);
but the resulting query generated by Entity Framework looks like this:
[Extent4].[Name] + [Extent6].[Surname] + [Extent8].[Name] AS [C1]
...
ORDER BY [Project1].[C1] ASC
...
Obviously, + does not work as a substitute for CONCAT in SQL.
Is there any way to make EntityFramework generate CONCAT instead of + for string concatenation in OrderBy?
If not, then I guess I'll have to create a separate SQL view with calculated column for this specific UI grid (which might be more correct solution anyway).
Try this:
query = query.OrderBy(x => x.PhysicalPerson.Name).ThenBy(x.PhysicalPerson.Surname).ThenBy(x.Company.Name);
Unfortunately the LINQ translators for SQL/EF don't use CONCAT to translate string concatenation, which is inconsistent with the way other translations expect SQL to handle null automatically (or maybe the SQL definition of + as different from CONCAT is where the issue lies). In any case, you can make the null test explicit like so:
query = query.OrderBy(x => String.Concat(x.PhysicalPerson.Name ?? "", x.PhysicalPerson.Surname ?? "", x.Company.Name ?? ""));
You could also use + instead of String.Concat, but I think the intent is easier to see with String.Concat.

Querying Numeric TableName fails

I am able to query the table named "012012" within SQL, but when attempting to query it from a C# application, it will say incorrect syntax near '012012'. Within SQL I would use double quotes to query this table as it doesn't work without them. Here is the code I am using:
string query = string.format("SELECT rec FROM '"+012012+"' WHERE cust = 'custname';");
If you're using SQL Server, then it does not allow un-escaped identifiers to start with numbers. So, you must use brackets like so [012012]. However, this is only part of your problem. The other part is that you are trying to use a numeric literal, and convert it to a string, but this number starts with a 0. This will get truncated by default and just become 12012. So, your best bet is to just do this:
string query = string.format("SELECT rec FROM [{0:D6}] WHERE cust = 'custname';", 012012);
The {0:D6} tells string.format to make the decimal field 6 characters wide, and pad 0's if it's shorter (which it would otherwise be 5 characters).
In this case, however, you probably don't even need to do that.. unless you actually need to derive the table name from a number, and you can just do this:
string query = "SELECT rec FROM [012012] WHERE cust = 'custname';";
I would also strongly advise against even starting to write code like this, as it is prone to SQL Injection vulnerability, you should always use parameterized queries and prepared statements. They're more work, but they are far safer.
Learning to write SQL code like this will form bad habits, which can be very dangerous later in your career.
You can't have a table name as '012012'. You don't even to use string.Format in your case. It will be useless.
If you wanna use string.Format with your table name, you can do it like;
string query = string.format("SELECT rec FROM [{0}] WHERE cust = 'custname';", "012012");
Try using square brackets:
string query = string.format("SELECT rec FROM ["+012012+"] WHERE cust = 'custname';");
Brackets are required if you use keywords or special chars in the column names or identifiers.

How can i find rows in DataTables according to row value

I've got a DataTable in which there could be values in a column which looks like x:1 x:2 a:1 a:2 etc... but they could also look like x* or a*.
In my code I'm getting a full value to search for (for example x:1), but the row itself can contain a value like x* in that column.
can i somehow use the Select method to search for the row?
for now it looks something like this:
strSelect = string.Format("[{0}]='{1}'", colName, ValueToSearch);
rows = tempTable.Select(strSelect);
but of course that like that the only rows I'll get are those that look EXACTLY like the one in the table. meaning that when searching for x:1, i won't get the row with x*
The code strSelect = string.Format("[{0}]='{1}'", colName, ValueToSearch); will select the same values. If you want search for subset you must use LIKE operator:
strSelect = string.Format("[{0}] LIKE '{1}'", colName, ValueToSearch.Replace("*", "%");
I'm assuming for the moment that your database includes 4 rows, with the following values in a given column that you're wanting to query against:
x:1
x:2
x*
a:1
a:2
a*
You state that you're being handed a value such as 'x:1' which you need to use in your query, but you're implying that the query should end up return the first three records - those with values of 'x:1', 'x:2', and 'x*'. In other words, although you're being handed 'x:1', you're actually want to search for any records that have a value that begins with 'x'.
If that's the scenario, you're probably best off modifying the value in your C# code before issuing the query. If your search value is genuinely of the form 'x:1', you could just chop off the last two characters before handing it to the SQL query:
string searchValue = "x:1"; // this presumably actually comes from user input
searchValue = searchValue.Substring(0, searchValue.Length - 2);
// Now searchValue is just "x", so go ahead and create your SQL query using the 'LIKE' operator
I have the feeling this is just a simplification of your actual data though, which makes it hard to be precise & also makes it harder to provide an example that includes error-checking.
For a slightly more complex example, perhaps the search-value your user gives you can either be a string of letters, or a string of letters followed by a colon followed by more letters. In that case, you need to check whether the string you've been given contains a colon, and if it does you need to chop off the colon and anything following it:
string searchValue = "abc:def";
if (searchValue.Contains(":"))
searchValue = searchValue.Substring(0, searchValue.IndexOf(":"));
// Having stripped off ":def", you're left with "abc"
Now you can go ahead and issue a query, using the LIKE operator, as TcKs already showed in his answer. For example you could modify the query code you already have as follows:
strSelect = string.Format("[{0}] LIKE '{1}'", colName, ValueToSearch);
rows = tempTable.Select(strSelect);
By using the LIKE operator, you're now looking for any records that have a value which starts with "abc".

LINQ to SQL Equivalent of ISDATE() in T-SQL and casting?

Does anyone know the equivalent of ISDATE() in LINQ to SQL query syntax? I've got a varchar field in SQL that contains dates and I need to filter out the non-date rows.
was hoping for something like this:
var query = SomeDataContext;
query = from p in query
where p.ISDATE(field1) == true;
select p;
also, how would one cast something for SQL in Linq syntax?
CAST(SomeDate AS SMALLDATETIME)
The trick to doing this is to create a user function in the database which merely calls ISDATE as returns the value
CREATE FUNCTION My_ISDATE(#maybeDate varchar(max))
returns bit
as return ISDATE(#maybeDate);
Then add My_IsDate to you database context, and use it in you query:
var db = SomeDataContext;
var query = from p in db.MyTable
where db.My_ISDATE(p.field1)
select p;
I don't think there is an extension method that maps to ISDATE when using LINQ to SQL.
If you are ok with loading all the data and then doing the filtering in the client space, then use TryParse on the field and compare to the date.
Otherwise, I would create a stored procedure which would return the data you want (so that you can use ISDATE), and then execute that through the context.
Also, your syntax for the use of ISDATE is incorrect. ISDATE just tells you if the expression is a valid date format. Your query would look like this:
var query = SomeDataContext;
query = from p in query
where ISDATE(field1) != 0 && CONVERT(datetime, field1) > some date
select p;
The syntax isn't valid, but it gives you an idea of how to form the query.
A problem with SO is that an increasing number of threads on answers of problems contain outdated answers, mostly because they are not touched in years. However in combination with a search engine (e.g. Google) who then returns as #1 a link (for instance this post) when you query "How should i use IsDate in Linq" it needs someone to then add one answer (at the bottom with 0 votes) of the latest correct answer.
:: https://github.com/dotnet/efcore/issues/8488 >
It now has been added to EF.Functions so you can simply do:
Where(x=> EF.Functions.IsDate(x.somefield))
Looks like you can do it. Links:
http://msdn.microsoft.com/en-us/library/bb386973.aspx
this might be of help but I haven't watched it:
http://mtaulty.com/videos/nuggets/l2s/15_mt_l2s_callingsqlfunctions.wmv
Full list here:
http://mtaulty.com/CommunityServer/blogs/mike_taultys_blog/archive/2007/05/10/9322.aspx

Categories