not allowed truncated value in group by [criteria API]? - jpa

I have a query like :
select name, trunc( date, 'MM' ), sum( number )
from t$mytable
group by name, trunc( date, 'MM' );
which runs perfectly, and gives expected result.
I implemented exactly the same query in JPA criteria API :
CriteriaBuilder criteriaBuilder = em.getCriteriaBuilder();
CriteriaQuery criteriaQuery = criteriaBuilder.createQuery( pClass );
Metamodel metamodel = em.getMetamodel();
EntityType entityType_ = metamodel.entity( pClass );
Root<?> root = criteriaQuery.from( pClass );
Expression pathName = root.get( entityType_.getSingularAttribute( "name" ));
Path pathDate = root.get( entityType_.getSingularAttribute( "date" ) );
Expression<Date> pathTruncatedDate = criteriaBuilder.function("trunc", Date.class , pathDate, 'MM');
Expression pathNumber = criteriaBuilder.sum( root.get( entityType_.getSingularAttribute("number") ));
criteriaQuery.select( criteriaBuilder.array( pathName, pathTruncatedDate, pathNumber) );
List<Expression> groupBy = new ArrayList<Expression>();
groupBy.add( pathName);
groupBy.add( pathTruncatedDate);
criteriaQuery.groupBy( groupBy );
TypedQuery<Object[]> q = em.createQuery( criteriaQuery );
<-- and surprisingly I got ORA-00979: not a GROUP BY expression by running it. What coused the problem, everything is on right place, i guess.
Thank You very much in advance!

it's not look like Oracle question but,
what about creating view by your statement and using view in your application?
something like
create view vw_my_table as
select name, trunc( date, 'MM' ), sum( number )
from t$mytable
group by name, trunc( date, 'MM' );

I have same issue and have found the root cause of this ORA-00979.
Oracle automatically handle aggregation of date in below query
select name, trunc( date, 'MM' ), sum( number )
from t$mytable
group by name, trunc( date, 'MM' );
With Criteria API you need to use criteria builder least method.
In your cas you just need to replace select line with:
criteriaQuery.select( criteriaBuilder.array( pathName, criteriaBuilder.least(pathTruncatedDate), pathNumber) );

Related

TYPO3 DBAL Querybuilder: Nested SELECT statements?

Is it possible to build nested SELECT statements like the one below using the DBAL QueryBuilder?
SELECT i.id, i.stable_id, i.version, i.title
FROM initiatives AS i
INNER JOIN (
SELECT stable_id, MAX(version) AS max_version FROM initiatives GROUP BY stable_id
) AS tbl1
ON i.stable_id = tbl1.stable_id AND i.version = tbl1.max_version
ORDER BY i.stable_id ASC
The goal is to query an external non TYPO3 table which contains different versions of each data set. Only the data set with the highest version number should be rendered. The database looks like this:
id, stable_id, version, [rest of the data row]
stable_id is the external id of the data set. id is the internal autoincrement id. And version is also incremented automatically.
Code example:
$queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable($this->table);
$result = $queryBuilder
->select(...$this->select)
->from($this->table)
->join(
'initiatives',
$queryBuilder
->select('stable_id, MAX(version) AS max_version' )
->from('initiatives')
->groupBy('stable_id'),
'tbl1',
$queryBuilder->and(
$queryBuilder->expr()->eq(
'initiatives.stable_id',
$queryBuilder->quoteIdentifier('tbl1.stable_id')
),
$queryBuilder->expr()->eq(
'initiatives.version',
$queryBuilder->quoteIdentifier('tbl1.max_version')
)
)
)
->orderBy('stable_id', 'DESC')
I cannot figure out the correct syntax for the ON ... AND statement. Any idea?
Extbase queries have JOIN capabilities but are otherwise very limited. You could use custom SQL (see ->statement() here), though.
A better API to build complex queries is the (Doctrine DBAL) QueryBuilder, including support for JOINs, database functions like MAX() and raw expressions (->addSelectLiteral()). Make sure to read until the ExpressionBuilder where it gets interesting.
So Extbase queries are useful in order to retrieve Extbase (model) objects. It can make implicit use of its knowledge of your data structure in order to save you some code but only supports rather simple queries.
The (Doctrine DBAL) QueryBuilder fulfills all other needs. If needed, you can convert the raw data to Extbase models, too. (for example $propertyMapper->convert($data, Job::class)).
I realize that we lack clear distinguishing between the two because they were both known at some time as "QueryBuilder", but they are totally different. That's why I like to add "Doctrine" when referring to the non-Extbase one.
An example with a JOIN ON multiple criteria.
$q = TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance(TYPO3\CMS\Core\Database\ConnectionPool::class)
->getQueryBuilderForTable('fe_users');
$res = $q->select('*')
->from('tt_content', 'c')
->join(
'c',
'be_users',
'bu',
$q->expr()->andX(
$q->expr()->eq(
'c.cruser_id', $q->quoteIdentifier('bu.uid')
),
$q->expr()->comparison(
'2', '=', '2'
)
)
)
->setMaxResults(5)
->execute()
->fetchAllAssociative();
Short answer: it is not possible because the table to be joined in is generated on the fly. The related expression is back-ticked and thus causes an SQL error.
But: The SQL query can be changed to the following SQL query which does basically the same:
SELECT i1.id,stable_id, version, title, p.name, l.name, s.name
FROM initiatives i1
WHERE version = (
SELECT MAX(i2.version)
FROM initiatives i2
WHERE i1.stable_id = i2.stable_id
)
ORDER BY stable_id ASC
And this can be rebuild with the DBAL queryBuilder:
$queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable($this->table);
$result = $queryBuilder
->select(...$this->select)
->from($this->table)
->where(
$queryBuilder->expr()->eq(
'initiatives.version',
'(SELECT MAX(i2.version) FROM initiatives i2 WHERE initiatives.stable_id = i2.stable_id)'
),
->orderBy('stable_id', 'DESC')
->setMaxResults( 50 )
->execute();

Covert SQL query to TYPO3 query builder?

How to convert SQL query to TYPO3 query builder.
SELECT * FROM tableName ORDER BY (CASE WHEN DATE(dateColumn) < DATE(GETDATE()) THEN 1 ELSE 0
END) DESC, dateColumn ASC
enter link description here
Same functionality i need in typo3 query builder.
To get the sql query you posted, you can do it like following:
// little helper function to debug querybuilder,
// queries with parameter placeholders
function debugQuery(QueryBuilder $builder)
{
$preparedStatement = $builder->getSQL();
$parameters = $builder->getParameters();
$stringParams = [];
foreach ($parameters as $key => $parameter) {
$stringParams[':' . $key] = $parameter;
}
return strtr($preparedStatement, $stringParams);
}
// get querybuilder
$queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
->getConnectionByName('Default')
->createQueryBuilder();
// build query
$queryBuilder
->select('*')
->from('table')
->getConcreteQueryBuilder()->orderBy(
'(CASE WHEN DATE('
.$queryBuilder->quoteIdentifier('dateColumn')
.') < DATE(GETDATE()) THEN 1 ELSE 0 END)',
'DESC'
)
;
$queryBuilder->addOrderBy('dateColumn', 'ASC');
// build query
$sql = debugQuery($queryBuilder);
The generates following sql query:
SELECT
FROM `table`
ORDER BY
(CASE WHEN DATE(`dateColumn`) < DATE(GETDATE()) THEN 1 ELSE 0 END) DESC,
`dateColumn` ASC
Some note beside:
To my knowlage GETDATE() is not a valid mysql method, more MSSQL. Eventually you want CURRENT_DATE() instead.
edit #1
Not tested/run .. just created the sql string to match what you provided. So don't blame me if provided sql query is wrong itself.

JPA Criteria orderBy: unexpected AST node

I have the following criteria query, which retrieves some fields from Anfrage and Sparte entities and also the translated string for the sparte.i18nKey.
This works as expected if I dont use orderBy.
Now I have the requirement to sort by the translated string for sparte.i18nKey and using the orderBy as shown below, results in QuerySyntaxException: unexpected AST node
So the problem must be the subselect in the orderBy clause!
select distinct new
my.domain.model.dto.AnfrageDTO(
anfrage0.id,
anfrage0.name,
anfrage0.sparte.id,
anfrage0.sparte.i18nKey,
-- retrieve translated string for sparte.i18nKey
(select rb0.value from at.luxbau.mis2.domain.model.ResourceBundleEntity as rb0
where (anfrage0.sparte.i18nKey = rb0.key) and (rb0.language = 'de'))
)
from my.domain.model.impl.Anfrage as anfrage0
left join anfrage0.sparte as sparte
order by (
-- sort by translated string for sparte.i18nKey
select rb1.value
from my.domain.model.ResourceBundleEntity as rb1
where (anfrage0.sparte.i18nKey = rb1.key) and (rb1.language = 'de')
) asc
My Java code looks like this:
private List<AnfrageDTO> getAnfragen() {
CriteriaBuilder cb = entityManager.getCriteriaBuilder();
CriteriaQuery<AnfrageDTO> query = cb.createQuery(AnfrageDTO.class);
Root<Anfrage> anfrage = query.from(Anfrage.class);
anfrage.join(Anfrage_.sparte, JoinType.LEFT);
query.select(cb.construct(AnfrageDTO.class,
anfrage.get(Anfrage_.id),
anfrage.get(Anfrage_.name),
anfrage.get(Anfrage_.sparte).get(Sparte_.id),
anfrage.get(Anfrage_.sparte).get(Sparte_.i18nKey),
// create subquery for translated sparte.i18nKey
createResourceBundleSubQuery(cb, query, anfrage.get(Anfrage_.sparte).get(Sparte_.i18nKey)).getSelection()));
TypedQuery<AnfrageDTO> tq = entityManager
.createQuery(query)
// use subquery to sort by translated sparte.i18nKey
.orderBy(cb.asc(createResourceBundleSubQuery(cb, query, anfrage.get(Anfrage_.sparte).get(Sparte_.i18nKey))));
tq.setMaxResults(10);
List<AnfrageDTO> anfragen = tq.getResultList();
return anfragen;
}
public Subquery<String> createResourceBundleSubQuery(CriteriaBuilder cb, CriteriaQuery<?> query, <String> expr) {
Subquery<String> subquery = query.subquery(String.class);
Root<ResourceBundleEntity> rb = subquery.from(ResourceBundleEntity.class);
subquery
.select(rb.get(ResourceBundleEntity_.value))
.where(cb.and(
cb.equal(expr, rb.get(ResourceBundleEntity_.key)),
cb.equal(rb.get(ResourceBundleEntity_.language), "de")));
return subquery;
}
Using a native SQL query with subselect in orderBy works also as expected.
select distinct
anfrage0_.id,
anfrage0_.name,
anfrage0_.sparte_id,
sparte4_.i18n_key,
(select rb3.i18n_value from resource_bundle rb3 where rb3.language_code = 'de' and rb3.i18n_key = sparte4_.i18n_key) as sparte_i18n_value
from
mis2.anfrage anfrage0_
left outer join mis2.sparte sparte4_ on anfrage0_.sparte_id = sparte4_.id
order by (
select rb.i18n_value
from mis2.resource_bundle rb
where sparte4_.i18n_key = rb.i18n_key and rb.language_code = 'de'
) asc
Also using an alias in the native SQL query works also as expected.
select distinct
anfrage0_.id,
anfrage0_.name,
anfrage0_.sparte_id,
sparte4_.i18n_key,
(select rb3.i18n_value from resource_bundle rb3 where rb3.language_code = 'de' and rb3.i18n_key = sparte4_.i18n_key) as sparte_i18n_value
from
mis2.anfrage anfrage0_
left outer join mis2.sparte sparte4_ on anfrage0_.sparte_id = sparte4_.id
order by sparte_i18n_value
asc
It would be great if JPA Criteria API would support using an alias in orderBy clause!
Any hints welcome - Thank you!
My environment is WildFly 11 and PostgreSQL 9.6.
JPA doesn't support passing parameter in order by clause, your problem has been asked before: Hibernate Named Query Order By parameter

mysql returned empty result set even records are existing

My query:
SELECT COUNT( bilno )
FROM trans_master
WHERE dt
BETWEEN 25-10-2017
AND 03-11-2017
AND refid=1
Below is table (trans_master):
Try add ' in front of and behind your date:
SELECT COUNT( bilno )
FROM trans_master
WHERE dt
BETWEEN '25-10-2017'
AND '03-11-2017'
AND refid=1

Run function with select statement

I have a tabe valued function called "GetLogInfosMsg" which I want to run provided with some Parameters from a specific table. The code below does not work; I get the message "Only one expression can be specified in the select list when the sub-query is not introduced with EXISTS.". Can somebody help me? Thanks.
SELECT
#pErrMsgID = [MsgID],
#pParams = [Params]
FROM
[dbo].[GetLogInfoMsg]((SELECT
[FIELD1],
[FIELD2],
[FIELD3]
FROM [dbo].[TABLE1]
WHERE [TABLE1].[UUID] = #pUUID));
try this
you have to pass parameter in separate not in table format
SELECT #pErrMsgID = [MsgID] ,
#pParams = [Params]
FROM [dbo].[GetLogInfoMsg](( SELECT [FIELD1]
FROM [dbo].[TABLE1]
WHERE [TABLE1].[UUID] = #pUUID
), ( SELECT [FIELD2]
FROM [dbo].[TABLE1]
WHERE [TABLE1].[UUID] = #pUUID
), ( SELECT [FIELD3]
FROM [dbo].[TABLE1]
WHERE [TABLE1].[UUID] = #pUUID
)) ;
If you are using mssql 2005+ consider using this:
SELECT
#pErrMsgID = t2.[MsgID],
#pParams = t2.[Params]
FROM [dbo].[TABLE1] t1
CROSS APPLY (SELECT [MsgID], [Params]
FROM [dbo].[GetLogInfoMsg] (t1.FIELD1, t1.FIELD2, t1.FIELD3) t2
WHERE t1.[UUID] = #pUUID