Grails sql queries - sql

Imagine I have something like this:
def example = {
def temp = ConferenceUser.findAllByUser(User.get(session.user))
[temp: temp]
}
Explaining my problem:
Although dynamic finders are very easy to use and fast to learn, I must replace dynamic finders of my website for sql queries because it is a requirement. As I don't understand SQL that much, my main questions are:
a) I am using an SQLS database, with the drivers and datasource good configured and my website works as it is right now. If I want to replace the "findAllByUser" for an sql statement, should i do something like this:
def dataSource
...
def db = new Sql(dataSource)
def temp = db.rows("SELECT ... ")
b) And that will work? I mean, the temp object will be a list as it is if I use "findAllByUser", and do I need to open a connection to the database =?

With Grails you can use Dynamic Finders, Criteria Builders, Hibernate Query Language (HQL), or Groovy SQL.
To use Groovy SQL:
import groovy.sql.Sql
Request a reference to the datasource with def dataSource or def sessionFactory for transactions
Create an Sql object using def sql = new Sql(dataSource) or def sql = new Sql(sessionFactory.currentSession.connection())
Use Groovy SQL as required
Grails will manage the connection to the datasource automatically.
Sql.rows returns a list that can be passed to your view.
For example:
import groovy.sql.Sql
class MyController {
def dataSource
def example = {
def sql = new Sql(dataSource)
[ temp: sql.rows("SELECT . . .") ]
}
}
And within a transaction:
import groovy.sql.Sql
class MyController {
def sessionFactory
def example = {
def sql = new Sql(sessionFactory.currentSession.connection())
[ temp: sql.rows("SELECT . . .") ]
}
}
I recommend the book Grails Persistence with GORM and GSQL for a lot of great tips and techniques.

yes, with grails you can do both plain sql and hql queries. HQL is 'hibernate query language' and allows you to write sql-like statements, but use your domain classes and properties instead of the table names and column names. To do an hql query, do something like
def UserList = ConferenceUser.executeQuery('from ConferenceUser cu where cu.user = ?', [user]),
what you have here is a parameterized query -- executeQuery sees the ? in the hql string and substitutes the arguments in the array that is the second parameter to the method([user] in this case) for you.
See
http://grails.org/doc/latest/ref/Domain%20Classes/executeQuery.html
and you can see this on how to do sql queries with Grails
Sql query for insert in grails

Going Further / Tips
Use Spring beans
You can make the groovy.sql.Sql instance a Spring bean in your Grails application. In grails-app/conf/spring/resources.groovy define the Sql bean:
// File: grails-app/conf/spring/resources.groovy
beans = {
// Create Spring bean for Groovy SQL.
// groovySql is the name of the bean and can be used
// for injection.
sql(groovy.sql.Sql, ref('dataSource'))
}
Next inject the Sql instance in your your class.
package com.example
import groovy.sql.GroovyRowResult
class CarService {
// Reference to sql defined in resources.groovy.
def sql
List<GroovyRowResult> allCars(final String searchQuery) {
final String searchString = "%${searchQuery.toUpperCase()}%"
final String query = '''\
select id, make, model
from car
where ...
'''
// Use groovySql bean to execute the query.
final results = sql.rows(query, search: searchString)
results
}
}
Multiple Datasources
adminSql(groovy.sql.Sql, ref("dataSource_admin"))
userSql(groovy.sql.Sql, ref("dataSource_user"))
and inject the beans
def userSql
def adminSql
Into the services that need them.
or without injection
import groovy.sql.Sql
// ...
// inject the datasource bean
def dataSource_admin
// ...
// in a method
Sql sql = new Sql(dataSource_admin)
Early Grails Version
Looping through GORM result sets in early grails versions can cause needless queries in the middle of template loops. Using groovy SQL can help with this.

Related

How should I pass a raw query response to a Serializer class?

I am trying to fetch data using raw sql query but I am facing issues when I am trying to pass the raw sql response to the Serializer class.
Serializer
class User_Serializer(serializers.ModelSerializer):
class Meta:
model = Users
fields = '__all__'
View
class UserView(APIView):
def get(self, request, emailId, format=None):
with connection.cursor() as cursor:
cursor.execute("SELECT * FROM users")
resRow = cursor.fetchone()
serializerResponse = User_Serializer(resRow)
return Response(serializerResponse.data)
I realise that the Serializer class cannot work with the ModelSerialzier class in this scenerio. How should I build my Serializer considering the fact that I need to post and save data to the concerned model using this Serializer class.
Instead of creating a connection manually use your model manager's raw method. It will return a RawQuerySet instance and will be iterable just like regular QuerySet. Also all the records that was fetched from db will be a model instance. You can use it like:
users = User.objects.raw("SELECT * FROM users_user")
serializer = UserSerializer(users, many=True)
serializer.data
Don't forget to check out raw sql documentation.

Compare two database fields in extbase repository

I am using TYPO3 8. In my extension I have a database table "company" in which I store for each company the total number of places (number_places) and the number of occupied places (occupied_places).
Now I want to limit the search to companies which have available places left.
In MySQL it would be like this:
SELECT * FROM company WHERE number_places > occupied_places;
How can I create this query in the extbase repository?
I tried to introduce the virtual property placesLeft in my model but it did not work.
I don't want to use a raw SQL statement as mentioned below, because I already have implemented a filter which uses plenty of different constraints.
Extbase query to compare two fields in same table
You can do it like this in your repository class, please note the comments inside the code:
class CompanyRepository extends \TYPO3\CMS\Extbase\Persistence\Repository
{
public function findWithAvailablePlaces(bool $returnRawQueryResult = false)
{
// Create a QueryBuilder instance
$queryBuilder = $this->objectManager->get(\TYPO3\CMS\Core\Database\ConnectionPool::class)
->getConnectionForTable('company')->createQueryBuilder();
// Create the query
$queryBuilder
->select('*')
->from('company')
->where(
// Note: this string concatenation is needed, because TYPO3's
// QueryBuilder always escapes the value in the ExpressionBuilder's
// methods (eq(), lt(), gt(), ...) and thus render it impossible to
// compare against an identifier.
$queryBuilder->quoteIdentifier('number_places')
. \TYPO3\CMS\Core\Database\Query\Expression\ExpressionBuilder::GT
. $queryBuilder->quoteIdentifier('occupied_places')
);
// Execute the query
$result = $queryBuilder->execute()->fetchAll();
// Note: this switch is not needed in fact. I just put it here, if you
// like to get the Company model objects instead of an array.
if ($returnRawQueryResult) {
$dataMapper = $this->objectManager->get(\TYPO3\CMS\Extbase\Persistence\Generic\Mapper\DataMapper::class);
return $dataMapper->map($this->objectType, $result);
}
return $result;
}
}
Notes:
If you have lots of records to deal with, I would - for performance reasons - not use the data mapping feature and work with arrays.
If you want to use the fluid pagination widget, be sure you don't and build your own pagination. Because of the way this works (extbase-internally), you'd get a huge system load overhead when the table grows. Better add the support for limited db queries to the repository method, for example:
class CompanyRepository extends \TYPO3\CMS\Extbase\Persistence\Repository
{
public function findWithAvailablePlaces(
int $limit = 10,
int $offset = 0,
bool $returnRawQueryResult = false
) {
// ...
$queryBuilder
->setMaxResults($limit)
->setFirstResult($offset);
$result = $queryBuilder->execute()->fetchAll();
// ...
}
}
I think you cant do this using the default Extbase Query methods like equals() and so on. You may use the function $query->statement() for your specific queries like this.
You also can use the QueryBuilder since TYPO3 8 which has functions to compare fields to each other:
https://docs.typo3.org/typo3cms/CoreApiReference/latest/ApiOverview/Database/QueryBuilder/Index.html#quoteidentifier-and-quoteidentifiers
It's fine to use this QueryBuilder inside Extbase repositories. After this you can use the DataMapper to map the query results to Extbase models.
In case of using "statement()" be aware of escaping every value which may cause any kind of SQL injections.
Based on the current architecture of TYPO3, the data structure is such that comparing of two tables or, mixing results from two tables ought to be done from within the controller, by injecting the two repositories. Optionally, you can construct a Domain Service that can work on the data from the two repositories from within the action itself, in the case of a routine. The service will also have to be injected.
Note:
If you have a foreign relation defined in your table configuration, the results of that foreign relation will show in your defined table repository. So, there's that too.

How to use group_concat in hibernate criteria?

I wrote a query in mysql using group_concat like
SELECT c1,group_concat(c2) FROM table1 where sno in(1,4,8,10) group by c1;
and gives my expected result.
Now the same query I want to write using hibernate criteria.
You have two options (depending on your hibernate version).
Override the dialect class
any hibernate version
You will need to subclass your dialect to add group_concat()
Introduce the dialect override class
Create the following class somewhere in your app (e.g. util package)
package com.myapp.util;
import org.hibernate.dialect.MySQL5Dialect;
import org.hibernate.dialect.function.StandardSQLFunction;
import org.hibernate.type.StandardBasicTypes;
public class MySQLCustomDialect extends MySQL5Dialect {
public MySQLCustomDialect() {
super();
registerFunction("group_concat",
new StandardSQLFunction("group_concat",
StandardBasicTypes.STRING));
}
}
Map the dialect override class to boot properties
Add the following property to your application.properities
spring.jpa.properties.hibernate.dialect = com.myapp.util.MySQLCustomDialect
Use JPA Metadata Builder Contributor
hibernate 5.2.18 or newer only
Introduce metadata builder class
Create the following class, remember to add package & resolve imports.
public class SqlFunctions implements MetadataBuilderContributor {
#Override
public void contribute(MetadataBuilder metadataBuilder) {
metadataBuilder.applySqlFunction( "group_concat",
new StandardSQLFunction( "group_concat",
StandardBasicTypes.STRING ) ); }
}
Map new class in application boot properties
Leave the dialect properties as is
spring.jpa.properties.hibernate.dialect = org.hibernate.dialect.MySQL5Dialect
spring.jpa.properties.hibernate.metadata_builder_contributor = com.myapp.util.SqlFunctions
Simple answer is No
Why?
Hibernate support only common function/syntax used in multiple database. There ain't any group_concat function in Microsoft SQL Server and may be in other database as well.
Solution:
You have to execute it as Simple SQL Query.
Finally i go through like below code and got expected result
String query="select c1,group_concat(c2) from table1 where sno in (:pageIds) group by c1";
SQLQuery sqlQuery= session.createSQLQuery(query);
sqlQuery.setParameterList("pageIds", myList);
List list= sqlQuery.list();
c1 group_concat(c2)
aaa valu1,value2
bbb value3
ccc value4,value5,value6
Please refer following code snippets
Criteria cr = session.createCriteria(table1.class);
cr.add(Restrictions.in("sno",snoarray));
criteria.setProjection("c1");
criteria.setProjection(Projections.groupProperty("c1"));

Grails query LEFT JOIN on table with no domain relations

I'm new to Grails and I'm stuck with this problem for hours. Thanks in advance for you help!
Here is my question:
I have a database with two tables
PROJECT
LIKES
As you can guess a user has the right to Like a Project.
In the Domain Class Project there is NO relations (belongsTO, hasOne, etc..)
Same in the Domain Class Likes
In the database the table LIKES has a field project_id but it is not set as a foreign_key. It is this made this way for right purpose.
Now I need to execute a native SQL query with grails which is really simple and returns the result expected.
The result is all projects that have Likes or not
Here is the query :
SELECT project.name, Likes.likes
FROM project
LEFT JOIN Likes
ON project.id = likes.project_id;
I cant find a way to convert this SQL query to HQL.
It seems that HQL works on Domain instance and the fact that there is no relation between the domains return an error like "the Domain Project has no Likes attribute" which is correct.
Is there a way to get the right result with one query or do I need to do two query and build an Array with the result programmatically ?
Thanks for you help
If your domain classes don't have relationships you cannot do it using HQL, as you noticed.
But in Grails you can access the database directly, using groovy.sql.Sql. Example of service:
class MyService {
def dataSource
void addNewRecord(String data) {
groovy.sql.Sql sql = new Sql(dataSource)
sql.execute("insert into my_table(my_anydata_clomn) values(sys.anyData.convertVarchar2(?))",[data])
}
}
Ok it works like this :)
def dataSource
def getProjectList() {
groovy.sql.Sql sql = new groovy.sql.Sql(dataSource)
log.info(sql)
log.info("datasource " + dataSource)
def t = sql.rows("SELECT * \n" +
"FROM project\n" +
"LEFT JOIN Likes\n" +
"ON project.id=likes.project_id\n" +
"ORDER BY likes.likes")
sql.close()
return t;
}

Groovy SQL + Gorm under same session/transaction

Please go through the simple scenario below, I couldn't find a better way to ask the question in textual form:
Two domain objects and a transactional service:
A {
int id
String prop1
B b
static constraints = {b nullable:true}
}
B {
int id
String prop1
// not worring about belongsTo here
}
SomeService {
def transactional = true
def sessionFactory
def aTransactionalMethod() {
Sql sql = new Sql(sessionFactory.currentSession.connection())
sql# create A some how with sql query leaving property b as null.
A a = A.findById(...)
//a.b must be null here, never mind
sql# create B object somehow with sql query.
// should a.b be available now? I'm getting null here.. session.currentSession.refresh(a) resolves the issue but why is that?
}
}
Hibernate can't parse SQL, so it doesn't "know" what you write to database. Hibernate will not reload all session objects - that would definitely be huge overhead.
Maybe if you rewrite your query to HQL the objects will become available to session in immediately.