Using Hibernate DetachedCriteria for calling aggregate functions - sql

I have a DetachedCriteria which I am using to search a table based on a name field. I want to make the search case-insensitive, and am wondering if there is a way to do this without using HQL. Something like:
private void searchByFullName(DetachedCriteria criteria, String searchCriteria) {
criteria.add(Restrictions.like("fullName", "%" + searchCriteria.toLowerCase() + "%"));
criteria.addOrder(Order.asc("fullName"));
}
But I want to make sure that it will ignore the case when it does the search (it has to search for both the upper and lower case), so the SQL it generates should look something like:
SELECT * FROM Student WHERE ? LIKE toLower(FULL_NAME);

What database are you using? MySQL LIKE is case-insensitive for CHAR, VARCHAR, and TEXT columns (I believe the same is true for SQL Server).
http://dev.mysql.com/doc/refman/5.5/en/case-sensitivity.html
If you're using PostgreSQL, you'll want to use the ILIKE operator, so you'll want to use Restrictions.ilike("fullName", name).

I see two options,
Option 1:
private void searchByFullName(DetachedCriteria criteria, String searchCriteria) {
criteria.add(Restrictions.like("toLower(fullName)", "%" + searchCriteria.toLowerCase() + "%"));
criteria.addOrder(Order.asc("fullName"));
}
Option 2:
private void searchByFullName(DetachedCriteria criteria, String searchCriteria) {
criteria.add(Restrictions.sqlRestriction("toLower({alias}.fullName) LIKE '%" + searchCriteria.toLowerCase() + "%'"));
criteria.addOrder(Order.asc("fullName"));
}
I am not very optimistic about Option 1. Option 2 should work for sure. {alias} is placeholder to let Hibernate know that it needs to add the appropriate alias for the table when it creates the SQL. More info http://docs.jboss.org/hibernate/core/3.5/api/org/hibernate/criterion/Restrictions.html#sqlRestriction%28java.lang.String%29

Related

How best to process dynamic query parameters using Java & Spring

The problem:
I have an api endpoint which handles multiple query parameters. It is implemented using Spring, and the query parameters are used to query data from a postgres database, which I query with a JDBC Template.
I am searching for a mature query builder technology to solve my problem.
Example:
A trivial query could look something like this:
api/book?name=LOTR&cover=hardback
The query parameters are added to a map, and a query string is build from the maps data:
String sqlQuery += (String) map.entrySet().stream()
.map(entry -> entry.getKey() + "='" + entry.getValue() + "' AND ")
.collect(Collectors.joining());
Its not the most efficient, as I must always remove the trailing "AND" clause from the string, but it works.
However, if the query where to look something like
api/book?name=LOTR&name=Ulysses&cover=hardback
there is now the addition of an "OR" clause, which the above code would not handle. I can see myself quickly getting into the territory of with tedious string parsing to create SQL statements.
So now that I have presented my problem, I wonder if there is a technology I can use which handles this kind of problem nicely?
I would like to avoid the use of any ORM for this project, so Hibernate and MyBatis are out of the question. I have looked at some JOOQ examples, but they do not look compatible with JDBC Template.
For trivial implementations like your first case where you have to remove last AND after your query is built there is a simple hack - immediately after WHERE you add 1 = 1 and then for every WHERE predicate you add AND [COLUMN] = [VALUE].
Note: most databases optimise use of constants in WHERE clause before execution, so performance will not be an issue
/*
select <columns> from <tables> where 1 = 1
[dynamically built Where predicates will come here from following code]
*/
String sqlQuery += (String) map.entrySet().stream()
.map(entry -> "AND " + entry.getKey() + "='" + entry.getValue() + "'")
.collect(Collectors.joining());
However for serious production implementations you may want to use frameworks like myBatis that gives you possibilities of templating a query and then passing parameters at runtime to build final queries.
You can find a good tutorial here.
/* An example */
<select id = "getName_Id_phone" parameterType = "Student" resultType = "Student">
SELECT * FROM STUDENT
<where>
<if test = "id != null">
id = #{id}
</if>
<if test = "name != null">
AND name LIKE #{name}
</if>
</where>
</select>
Came across here with the same question. Maybe you want to take a look at RSQL 1

Nhibernate Linq & operator RegisterFunction Firebird

I am using NHibernate with Firebird and would like to create the bitwise and operator to the Firebird function bin_and(a, b)
Something like this:
var result = customers.Where(c => (c.StatusValue & 3) > 0);
The above query will result in something like that:
select * from customers where (StatusValue & 3) > 0
Which is not valid in Firebird, the result should be:
select * from customers where bin_and(StatusValue,3) > 0
Is there a possibility to overwrite this translated result ?
Update
By declaring a function this is possible:
[LinqExtensionMethod("BIN_AND")]
public static int BinAnd(int a, int b)
{
return a & b;
}
var result = customers.Where(c => BinAnd(c.StatusValue, 3) > 0);
This works, but I am searching for a more generic way wizh the '&' or '|' operator...
Update:
# Fédéric:
I wrote my own Dialect class like this:
public class MyFirebirdDialect: FirebirdDialect {
public MyFirebirdDialect()
{
// Bitwise operations
RegisterFunction("band", new BitwiseFunctionOperation("bin_and"));
RegisterFunction("bor", new BitwiseFunctionOperation("bin_or"));
RegisterFunction("bxor", new BitwiseFunctionOperation("bin_xor"));
RegisterFunction("bnot", new BitwiseFunctionOperation("bin_not"));
}
}
I had to import the BitwiseFunctionOperation.cs too
If I debug the code I see that this class is used as Dialect, and I see that there is a custom function for the key 'band' that has a value 'bin_and' but a Query like this
var result = customers.Where(c => (c.StatusValue & 3) > 0);
ends up in an sql like this :
select * from customers where (StatusValue & 3) > 0
I think the linq parser does not its part...
Are you using the appropriate dialect? FirebirdDialect correctly defines bitwise and in HQL (RegisterFunction("band", new BitwiseFunctionOperation("bin_and")); and linq-to-nhibernate translate & (ExpressionType.And) to the appropriate HQL call.
If you are using an old NHibernate version, maybe you need to upgrade.
Firebird bitwise operators have been added with NH-3630 in NHibernate 4.1.
You may try to back-port them in your project by using a custom dialect deriving from FirebirdDialect and registering those additional functions as illustrated in the link above, within your custom dialect constructor.
But that will not work, because it requires some other changes in NHibernate internals, not available before NHibernate 4.1. Maybe by patching a local copy of NHibernate 3.4 sources may you succeed in doing that.

How to change sql generated by linq-to-entities?

I am querying a MS SQL database using Linq and Entity Framework Code First. The requirement is to be able to run a WHERE SomeColumn LIKE '%sometext'clause against the table.
This, on the surface, is a simple requirement that could be accomplished using a simple Linq query like this:
var results = new List<MyTable>();
using(var context = new MyContext())
{
results = context.MyTableQueryable
.Where(x => x.SomeColumn.EndsWith("sometext"))
.ToList();
}
// use results
However, this was not effective in practice. The problem seems to be that the column SomeColumn is not varchar, rather it's a char(31). This means that if a string is saved in the column that is less than 31 characters then there will be spaces added on the end of the string to ensure a length of 31 characters, and that fouls up the .EndsWith() query.
I used SQL Profiler to lookup the exact sql that was generated from the .EndsWith() method. Here is what I found:
--previous query code removed for brevity
WHERE [Extent1].[SomeColumn] LIKE N'%sometext'
So that is interesting. I'm not sure what the N means before '%sometext'. (I'll Google it later.) But I do know that if I take the same query and run it in SSMS without the N like this:
--previous query code removed for brevity
WHERE [Extent1].[SomeColumn] LIKE '%sometext'
Then the query works fine. Is there a way to get Linq and Entity Framework to drop that N from the query?
Please try this...
.Where(x => x.SomeColumn.Trim().EndsWith("sometext"))
Just spoke to my colleague who had a similar issue, see if the following works for you:
[Column(TypeName = "varchar")]
public string SomeColumn
{
get;
set;
}
Apparently setting the type on the column mapping will force the query to recognise it as a VARCHAR, where a string is normally interpreted as an NVARCHAR.

SQL Select Like Keywords in Any Order

I am building a Search function for a shopping cart site, which queries a SQL Server database. When the user enters "Hula Hoops" in the search box, I want results for all records containing both "Hula" and "Hoop", in any order. Furthermore, I need to search multiple columns (i.e. ProductName, Description, ShortName, MaufacturerName, etc.)
All of these product names should be returned, when searching for "Hula hoop":
Hula hoop
Hoop Hula
The Hoopity of xxhula sticks
(Bonus points if these can be ordered by relevance!)
It sounds like you're really looking for full-text search, especially since you want to weight the words.
In order to use LIKE, you'll have to use multiple expressions (one per word, per column), which means dynamic SQL. I don't know which language you're using, so I can't provide an example, but you'll have to produce a statement that's like this:
For "Hula Hoops":
where (ProductName like '%hula%' or ProductName like '%hoops%')
and (Description like '%hula%' or Description like '%hoops%')
and (ShortName like '%hula%' or ShortName like '%hoops%')
etc.
Unfortunately, that's really the only way to do it. Using Full Text Search would allow you to reduce your criteria to one per column, but you'll still have to specify the columns explicitly.
Since you're using SQL Server, I'm going to hazard a guess that this is a C# question. You'd have to do something like this (assuming you're constructing the SqlCommand or DbCommand object yourself; if you're using an ORM, all bets are off and you probably wouldn't be asking this anyway):
SqlCommand command = new SqlCommand();
int paramCount = 0;
string searchTerms = "Hula Hoops";
string commandPrefix = #"select *
from Products";
StringBuilder whereBuilder = new StringBuilder();
foreach(string term in searchTerms.Split(' '))
{
if(whereBuilder.Length == 0)
{
whereBuilder.Append(" where ");
}
else
{
whereBuilder.Append(" and ");
}
paramCount++;
SqlParameter param = new SqlParameter(string.Format("param{0}",paramCount), "%" + term + "%");
command.Parameters.Add(param);
whereBuilder.AppendFormat("(ProductName like #param{0} or Description like #param{0} or ShortName like #param{0})",paramCount);
}
command.CommandText = commandPrefix + whereBuilder.ToString();
SQL Server Full Text Search should help you out. You will basically create indexes on the columns you want to search. in the where clause of your query you will use the CONTAINS operator and pass it your search input.
you can start HERE or HERE to learn more
You might want to check out SOLR too - if you're going to be doing this type of searching. Super cool.
http://lucene.apache.org/solr/

Alias of joined table in SQLProjection

I have this query:
criteria = session.CreateCriteria(typeof (Building))
.CreateAlias("Estate", "estate")
.SetProjection(Projections.ProjectionList()
.Add(Property.ForName("Name"), "BuildingName")
.Add(Property.ForName("estate.Name"), "EstateName")
.Add(Projections.SqlProjection(
"(estate1_.BBRMunicipalityNumber + '-' + estate1_.BBREstateNumber + '-' + {alias}.BBRBuildingNumber)" + " as BBRNumber",
new[] { "BBRNumber" },
new[] { NHibernateUtil.String }),
"BBRNumber"))
Is there a way that I can get the SQL alias for "estate" like writing {estate} in the SQL string? {estate} does not work. Now I ended up hardcoding the alias in the SQL string, but that doesn't seem very solid.
If I understand the docs correctly this should be possible. I'm using NH2.0.1.
/Asger
Not a direct answer to your question, but:
Why don't you query the three values separately and do the concatenation in your code instead of using the database for that?
To answer your question: In Hibernate v3 (java, sorry) there is a getColumnAlias method on the Projection interface. I'm not able to find its counterpart in NHibernate.
Cheers
You can use {alias} - it will reference the alias of the current projection.