How to use mysql time functions inside grails executeupdate - mysql

I am trying to delete 1 month old records from my table using domain.executeUpdate as follows
Bugrerun.executeUpdate("delete Bugrerun b where b.complete = 1 and b.date
< date_sub(curdate(), INTERVAL 1 MONTH) ")
i am trying to use a MySQL date function inside the query.
But this fails with the error
org.hibernate.hql.ast.QuerySyntaxException: unexpected token: 1 near line 1
, column 97
How can we use the My SQL date time functions inside executeUpdate statements
Note that this table has lot of data so fetch and delete individual records will not work

You can try with the below query, just need to validate whether the HQL functions are supported in MySQL dialect:
Bugrerun.executeUpdate("delete Bugrerun b \
where b.complete = 1 \
and month(current_date()) > month(b.date) \
or year(current_date()) > year(b.date)")

You could implement your own Database dialect to include that functionality.
Another option is to do this:
Calendar cal = Calendar.getInstance();
cal.setTime(new Date());
cal.add(Calendar.MONTH, -1);
Bugrerun.executeUpdate("delete Bugrerun b where b.complete = 1 and b.date
< :oneMonthAgo", [oneMonthAgo: cal.time])

Not all mysql functions are available. You can take a look at MySQLDialect that is used by hibernate (and grails) to see the functions you have available for you:
http://grepcode.com/file/repository.springsource.com/org.hibernate/com.springsource.org.hibernate/3.3.1/org/hibernate/dialect/MySQLDialect.java#MySQLDialect
If you want, you can try to use Groovy SQL to execute a SQL statement instead of an HQL statement. If you do that, in your controller or service you should declare a dataSource attribute so you get DataSource injected:
class MyController {
DataSource dataSource
def execSql(){
def sql = new Sql(dataSource)
sql.execute("delete from bugrerun where complete = 1 and date < date_sub(curdate(), INTERVAL 1 MONTH) ")
render "done"
}
}

Related

SQL-Update under a external condition

Is it possible to limit the execution of an SQL CRUD-statement with a condition that is completely unrelated to the table it is working on?
E.g. execute the UPDATE only if there is a special date.
UPDATE tabfoo SET name="santa" where id="123" // only if day = 31.12.
I like to have this inside a single statement. I know how to do it inside a script.
Me platform would be MySql or SqLite.
For SQLite you can use the function strftime()
WHERE id = '123'
AND strftime('%d.%m', CURRENT_DATE) = '31.12'
and for MySql the function DATE_FORMAT():
WHERE id = '123'
AND DATE_FORMAT(CURRENT_DATE, '%d.%m') = '31.12'
In MySQL the problem can be solved next way:
UPDATE tabfoo
SET name="santa" WHERE
id="123" AND MONTH(CURDATE()) = 12 AND DAY(CURDATE()) = 31; // only if day = 31.12.
or
UPDATE tabfoo
SET name="santa" WHERE
id="123" AND DATE_FORMAT(CURDATE(), '%d.%m') = '31.12';
For SQLite you can find alternative function
You can try
UPDATE tabfoo SET name="santa" where id="123" and day = 31.12.
otherways you may try a sql stored procedure.
if-else doku

How to write a native SQL query in Grails 2.4.0?

I am working on Grails 2.4.0. I want to execute the native query in Groovy Controller. The query is as follow:
SELECT AVG(REPLACE(n.ep_text, 'PPM', '')), MONTH(n.date_creat)
from notification n
where n.type = 42
GROUP BY MONTH(n.date_creat)
Firstly, I execute the above query but it's have not found the REPLACE function like:
String query1 = "SELECT n.id, avg(REPLACE(n.epText, 'PPM', '')) FROM Notification as n";
def result = Notification.executeQuery(query1.toString())
How can I able to execute the REPLACE function in it?
And secondly, I have some R&D on it, but to execute the native query to required the sessionFactory. Unable to understand how to get the current session of Hibernate in Grails 2.4.0 to execute the native query?
Any help would be appreciated.
In order to use a native query, we can use SessionFactory, which is a bean and we can simply declare it to our Grails controller or service and dependency injection will handle it. Here is sample code using this bean to execute a native query.
class PublicService {
def sessionFactory
def getMatchedValue(){
def currentSession = sessionFactory.currentSession
def q = "select bank.id as id, bank.credit_amount as creditAmount, bank.debit_amount as debitAmount, bank.transaction_date as transactionDate, bank.transaction_name as transactionName, receipt.cr_date as crDate, receipt.picture_reference as pictureReference, receipt.receipt_date as receiptDate, receipt.reimbursment as reimbursment, receipt.total_amount as totalAmount, receipt.vendor as vendor " +
"from bank inner join receipt on bank.debit_amount=receipt.total_amount where ((bank.transaction_date >= receipt.receipt_date) and (bank.transaction_date <= DATE_ADD(receipt.receipt_date, INTERVAL 5 DAY) ))"//sample native query
def data = currentSession.createSQLQuery(q)
data.setResultTransformer(Criteria.ALIAS_TO_ENTITY_MAP);//if you are using alias for query e.g bank.credit_amount as creditAmount
final result = data.list()
return result
}
}

How to select DB Expression as value using knex/Bookshelf

I'm trying to execute the following query using knex.js and MySql
SELECT
m.id,
TIME(date_created) AS `timestamp`,
u.username,
m.`message`
FROM
`messages` AS m
INNER JOIN users AS u ON u.id = m.user_id
WHERE
m.game_id IS NULL
AND m.`date_created` > DATE_SUB(
CURRENT_TIMESTAMP (),
INTERVAL 12 HOUR
)
ORDER BY
m.`date_created` ASC
LIMIT 50
For proper handling expressions in where closure such as DATE_SUB(CURRENT_TIMESTAMP(), INTERVAL 12 HOUR) according to documentation there is whereRow() method in knex.
I tried to use select() method as
select('messages.id', 'TIME(date_created) AS timestamp', 'users.username', 'messages.message')
But knex masks TIME(date_created) expression in a way it should to be a column name. Does anybody know a way to use a custom expressions in selects?
I did not found exact answer on my question but I've found a solution which allows me to move forward. I created separate model which uses standard Bookshelf(knex) export:
var Bookshelf = require('bookshelf')(knex);
module.exports.DB = Bookshelf;
And created separate method in that model where I could use DB.knex.raw() for masking DB expressions in SELECT. So I became able to write the query above in the following way:
var DB = require('./../db').DB;
var Messages = DB.Model.extend({
tableName: 'messages',
hasTimestamps: false,
idAttribute: 'id'
},
{
getMessagesHistory: function (gameId) {
return this.query().select('messages.id', DB.knex.raw('TIME(date_created) AS timestamp'), 'users.username', 'messages.message')
.innerJoin('users', 'messages.user_id', 'users.id')
.whereRaw("messages.game_id " + (gameId == 0 ? "IS NULL" : "= " + gameId))
.andWhereRaw("messages.date_created > DATE_SUB(CURRENT_TIMESTAMP(), INTERVAL 12 HOUR)")
.orderBy('messages.date_created', 'ASC')
.limit(50);
}
}
);
module.exports = Messages;
You can wrap any argument with knex.raw() to tell the framework that it's a raw piece of SQL:
select(
'messages.id',
knex.raw('TIME(date_created) AS timestamp'),
'users.username',
'messages.message',
)
See http://knexjs.org/#Raw-Bindings

Converting SQL statement to equivalent SQLAlchemy

I have the following SQL query (MySQL database)
SELECT distinct filename, DATE(created)
FROM FileTable
WHERE created > DATE_SUB(NOW(), INTERVAL 7 DAY);
My table definition:
**FileTable**
id : PK
filename :varchar
created :date
How to write an equivalent SQLAlchemy query for the above SQL statement and fetch results?
I tried this
myvar=session.query(FileTable).filter(created > DATE_SUB(NOW(), INTERVAL 7 DAY)).first()
return x.filename,x.DATE(created) for x in myvar
But it says the syntax is invalid. Please help. Thanks and regards :)
I suggest you are looking for magic func of sqlalchemy.sql.functions. Here is an example:
from sqlalchemy import *
metadata = MetaData()
FileTable = Table(
'FileTable',
metadata,
Column('id', Integer, primary_key=True),
Column('filename', String),
Column('created', DateTime))
print select([
distinct(FileTable.columns.filename),
func.date(FileTable.columns.created)
]).where(
FileTable.columns.created > func.date_sub(func.now(), 7))
# Output:
# SELECT DISTINCT "FileTable".filename, date("FileTable".created) AS date_1
# FROM "FileTable"
# WHERE "FileTable".created > date_sub(now(), :date_sub_1)
You can try this but it may results the rows those filenames are distinct in each dates(I've tested in PostgreSQL).
query = session.query(FileTable.filename.distinct(),
func.date(FileTable.created))\
.filter(created > func.adddate(func.now(), -7))
return query.all()
If you find yourself wanting to retrieve only single row per single filename then you can transform query to fetch filenames and its (in example) latest created times like below:
query = session.query(FileTable.filename,
func.date(func.max(FileTable.created)))\
.filter(created > func.adddate(func.now(), -7))\
.group_by(FileTable.filename)
return query.all()

Illuminate database query with date_sub

I'm struggling with a query using the Illuminate database query builder.
When I use the query the result is not as I expected.
When using the query from the querylog directly with mysql cli, I get the expected result.
With query builder:
->table('CompanyTools')
->select(
'CompanyTools.toolId',
$db->raw('COUNT(CompanyTools.toolId) as count')
)
->whereYear('CompanyTools.date', '>', 'YEAR(DATE_SUB(CURDATE(), INTERVAL 1 YEAR))')
->groupBy('CompanyTools.toolId')
->orderBy('count', 'DESC')
->take(1)
->get();
Result:
Array ( [toolId] => 88 [count] => 55 )
With mysql cli:
select `CompanyTools`.`toolId`, COUNT(CompanyTools.toolId) as count from `CompanyTools`
where year(`CompanyTools`.`date`) > YEAR(DATE_SUB(CURDATE(), INTERVAL 1 YEAR))
group by `CompanyTools`.`toolId`
order by `count` desc
limit 1
Result:
ToolId: 88
count: 17
If I (in the query builder) replace 'YEAR(DATE_SUB(CURDATE(), INTERVAL 1 YEAR))'with 2013 I get:
Array ( [toolId] => 88 [count] => 17 )
Somehow the date_sub get ignored so the result includes all years
I tried with ->whereYear('CompanyTools.date', '>', $db->raw('YEAR(DATE_SUB(CURDATE(), INTERVAL 1 YEAR))')) without any luck.
I guess I could use php to calculate the desired year, but I would rather get the query right.
Thx in advance
/ j
UPDATE
Replacing
->whereYear('CompanyTools.date', '>', 'YEAR(DATE_SUB(CURDATE(), INTERVAL 1 YEAR))')
with
->where($db->raw('YEAR(CompanyTools.date)'), '>', $db->raw('YEAR(DATE_SUB(CURDATE(), INTERVAL 1 YEAR))'))
solves it. Not clever enough to figure out why, but perhaps the whereYear function is supposed to be used diffently
As you already found out using
->where($db->raw('YEAR(CompanyTools.date)'), '>', $db->raw('YEAR(DATE_SUB(CURDATE(), INTERVAL 1 YEAR))'))
Or alternatively
->whereRaw('YEAR(CompanyTools.date) > YEAR(DATE_SUB(CURDATE(), INTERVAL 1 YEAR))')
solves the problem.
But why is that?
For every "normal" query, Laravel uses bindings. Obviously SQL functions like YEAR(DATE_SUB(CURDATE(), INTERVAL 1 YEAR)) don't work with bindings.
Normally, you can use DB::raw('YEAR(DATE_SUB(CURDATE(), INTERVAL 1 YEAR))') and the Laravel won't use bindings. For example in where() (Expression is the class DB::raw() returns)
if ( ! $value instanceof Expression)
{
$this->addBinding($value, 'where');
}
But the whereYear() function doesn't do such a thing. It uses addDateBasedWhere() and just adds a binding without checking if the value is an instance of Expression
protected function addDateBasedWhere($type, $column, $operator, $value, $boolean = 'and')
{
$this->wheres[] = compact('column', 'type', 'boolean', 'operator', 'value');
$this->addBinding($value, 'where');
return $this;
}
This means the query will use bindings and therefore NOT execute the date calculation at all.