Query for rows with combination of fields and in statement - java

On a database level I have a table product that has two columns: gtin and market along with other columns.
I want to write a java code that would be transformed to the following query:
select * from product where (gtin, market) IN (('USA', 1), ('CA',2), ('PL', 3), ('CZ, 4))
On a java code level I have special entity GtinMarket that is dedicated to store these two gtin and market values:
GtinMarket {
String gtin;
String market;
}
On a code level I would expect something like:
using(configuration)
.select()
.from(table(PRODUCT_TABLE))
.where((field(GTIN, Long.class),field(MARKET, Long.class)).in(gtinMarketList))
.fetch()
After some investigation I was unable to find a way to write such a code,
I would really really appreciate any help,
Thanks,

Use DSL.row(Field, Field) for type safe row value expression construction:
using(configuration)
.select()
.from(table(PRODUCT_TABLE))
.where(row(field(GTIN, Long.class), field(MARKET, Long.class)).in(gtinMarketList))
.fetch()
This is assuming that your gtinMarketList is a Collection<? extends Row2<Long, Long>> as expected by the relevant Row2.in() method. The in() method has several overloads, so you can pick whatever suits you.
More information here:
https://www.jooq.org/doc/latest/manual/sql-building/conditional-expressions/in-predicate-degree-n

Related

Case When or iif syntax expression in jooq

I am trying to reproduce this MySQL query in jooq.
messageTable1, messageTable2 same column
select
msg.index as index,
left(msg.message, if(msg.type = 1, 10, msg.maxLength)) as message
from
messageTable1 as msg
select
msg.index as index,
left(msg.message, if(msg.type = 1, 10, msg.maxLength)) as message
from
messageTable2 as msg
making function java
public Message getMessageTest(String tableName) {
return roDslContext.select(
DSL.field("msg.index").as("index"),
left(DSL.field( "{0}", String.class, "msg.message"),
when(DSL.field("msg.type").eq(1), 10, DSL.field( "{0}", String.class, "msg.maxLength").as("message)
.from(tableName)
.fetchOneInto(Message.class);
}
but....
when(DSL.field("msg.type").eq(1), 10, DSL.field( "{0}", String.class, "msg.maxLength").as("message)
jooq is field msg.maxLength ... not accept
also so iif
how case, when, iif insert field column data??
Using the code generator
I'm assuming you're not using the code generator in this example, because you want to make your column references dynamic, depending on the table name.
There's a possibility of generating common interfaces onto your tables, which would abstract over the two table types. Also, there are Table.rename(String) methods generated on those tables, which allow for renaming a table MESSAGE_TABLE_1.rename("message_table_2"). So your query would look like this:
public Message getMessageTest(String tableName) {
MessageTable1 msg = MESSAGE_TABLE_1.rename(tableName);
return roDslContext
.select(
msg.INDEX,
left(msg.MESSAGE, if_(msg.TYPE.eq(1), val(10), msg.MAX_LENGTH))
)
.from(msg)
.fetchOneInto(Message.class);
}
As always, this is assuming the following static import:
import static org.jooq.impl.DSL.*;
The actual problem
You seem to be attempting to construct field references using plain SQL templating. You did it right for msg.index via DSL.field("msg.index"), but somehow tripped over the templating syntax for msg.message. This doesn't make sense:
field("{0}", String.class, "msg.message");
When using a template, the placeholder {0} needs to be replaced by a QueryPart instance. You can't just pass a String instead (which would be interpreted as a bind value). What you meant to do here is any of these:
// If you really need templating, though here, you don't:
field("{0}", String.class, name("msg", "message"));
// Instead, just do this, if you want quoted names:
field(name("msg", "message"), String.class);
// Or this, if you don't care about quoting the name
field("msg.message", String.class);

Alias with two Fields using JOOQ

i have a nasty SQl that i want transform in JOOQ
Here are the Query:
SELECT
SUM(dpr.dpr_bruttopraemie_prt + dpr.dpr_sofortrabatt_prt)
, MAX(TO_NUMBER(DP1.dp_wert))
FROM deckungen deck, deckungspraemien dpr,
(SELECT dp.dp_id, dp.dp_wert
FROM textbausteine txb, druckparameter dp
WHERE dp.dp_txb_id = txb.txb_id
) DP1
WHERE DP1.dp_id = :druckparameter_id;
As you can see, i need to make alias from a select with two Fields.
dp.dp_id, dp.dp_wert
that im going to used it on some other parts.
How i can i get it done?
i've seen
.asField()
Funktion but it only make alias for one column.
PS: The actual Query are a lot more complicated. So i wrote a simpler one.
With hoping that it's satisfied the SQL ORACLE Dialect.
I'm assuming that you're using the code generator, so you have generated objects available for your tables like DECKUNGEN. I'm also assuming you're using these static imports:
import static org.jooq.impl.DSL.*; // The jOOQ API
import static com.example.myapp.generated.Tables.*; // Your generated tables
You can then write:
Deckungen deck = DECKUNGEN.as("deck");
Deckungspraemien dpr = DECKUNGSPRAEMIEN.as("dpr");
Textbausteine txb = TEXTBAUSTEINE.as("txb");
Druckparameter dp = DRUCKPARAMETER.as("dp");
Table<?> dp1 = table(
select(dp.DP_ID, dp.DP_WERT)
.from(txb, dp)
.where(dp.DP_TXB_ID.eq(txb.TXB_ID))
).as("dp1");
Record2<BigDecimal, BigDecimal> result =
using(configuration)
.select(
sum(dpr.DPR_BRUTTOPRAEMIE_PRT.plus(dpr.DPR_SOFORTRABATT_PRT)),
max(field("to_number({0})", BigDecimal.class, dp1.field(dp.DP_WERT))))
.from(deck, dpr, dp1)
.where(dp1.field(dp.DP_ID).eq(druckparameterId))
.fetchOne();
Some explanations
jOOQ currently doesn't have a built-in TO_NUMBER() function, but you can easily roll your own using DSL.field(String) and similar overloads. For more detail, refer to the manual's section about plain SQL
A derived table can be created most easily by using the DSL.table(Select) operator.
Columns from a derived table can be dereferenced using Table.field() methods, in particular the Table.field(Field), which tries to find a field by the same name as the argument field, retaining the argument field's type information.
Side-note
I don't think your query is correct as you're simply creating cartesian products between your tables deck, dpr, and dp1. Specifically, the SUM() is quite likely to be wrong, whereas MAX() is simply calculated inefficiently.

Map from Object[] to CustomObject. Using findBySQL query

Having a simple SQL Table like:
NOTIFICATION (ID, NAME, NOTIF_DATE, ...)
I am excecuting a SQL query from my back-end app using code like:
this.findBySQL(MY_SQL_QUERY, params...);
The SQL SELECT QUERY is excecuted correctly and returns the rows expected. The problem i am facing is that the returned rows are wrapped to Object[], but after this query, i want to filter the results using Java 8 stream and filter functionality.
I want the results to be mapped to my custom Object Notification, which look like:
Notification { long id, String name, Date notif_date, ... }
I could create my own mapper, getting the result values from the Object i get from the query, example:
Notification not = new Notification();
not.setName(obj[1]);
...
But i think that this approach is not a good implementation beacause it depends on the order of the columns returned, and i have to code for every attribute to be setted.
Is there any way of mapping from this Object[] to my Notification?
Thanks in advance and sorry for the poor code but I have no better approach for this issue.
It’s easiest if your Notification class has a constructor that accepts the fields from the database (or you can add such a constructor):
List<Object[]> rows = this.findBySQL(MY_SQL_QUERY, params);
List<Notification> notifs = rows.stream()
.map(r -> new Notification((Long) r[0], (String) r[1], (Date) r[2]))
.filter(n -> n.getName().startsWith("m"))
.collect(Collectors.toList());
You haven’t told us the exact return type of findBySQL(), so I am guessing a bit. I hope you will be able to tailor the code to your exact situation.
I put in a fairly meaningless filter() in the stream just for the sake of the example. I would find it more convenient to filter after you have created the Notification objects. You can also filter before if you prefer. I didn’t really understand why you didn’t do your filtering in SQL.
If you cannot add a good constructor for the purpose, it’s a bit longer, but really not very much more complicated:
List<Notification> notifs = rows.stream()
.map(r -> {
Notification notif = new Notification();
notif.setId((Long) r[0]);
notif.setName((String) r[1]);
notif.setNotif_date((Date) r[2]);
return notif;
})
.filter(n -> n.getName().startsWith("m"))
.collect(Collectors.toList());

Convert Integer to String in Spring Data JPA Query

I need to optimize a query that iterates over several objects and I wanted Spring Data to let the database handle it. I want to end up with a HashMap<String,String> that looks like
2134_9877, 9877
2134_2344, 2344
3298_9437, 9437
The SQL would be select convert(varchar,b.id)+'_'+convert(varchar,a.id)',a.id from t1 a join t2 b on a.jc = b.jc
So far, I've got Whatever-QL in the repository that looks like:
#Query("SELECT new map (a.bkey, a.akey) FROM mergeTable a WHERE a.discr= ?1")
The problem is, bkey is not unique, it is only unique when paired with akey and the monstrosity that I have to feed it to wants them combined with an underscore: 2345_2177.
I have tried a.bkey.toString and ''+a.bkey and new String(a.bkey) and just string(a.bkey) (that last gives a new exception but still doesn't work) but Spring doesn't like any of these. I can find no questions asking this and it seems I cannot use SQLServer's convert() function as this ain't SQL.
How can I concatenate the Integers as Strings with an underscore in this #Query?
PS: Using the native query that's been debugged in SQLServer throws some weird alias exception in Hibernate so I think 'going native' is predetermined to be a dead end.
If I have understood it right, the 'Whatever-QL' is called JPQL, and the operator CONCAT can be used. Only the use of it, as it accepts two or more parameters depends on the JPA version you are running.
Here is the answer.
JPA concat operator
You could add a getter to your entity like this:
public String getCombinedKey(){
return a.akey + "_" + a.bkey;
}
The advantage is you could handle here null's and other things if you want and it's more reusable in case you need this in another place. If you do it just in the repository you will have to copy it everytime.
Your query would then be:
#Query("SELECT new map (a.combinedKey, a.akey) FROM mergeTable a WHERE a.discr= ?1")

how to use Oracle's regexp_like in Hibernate HQL?

I am using oracle 10g and hibernate 3.3.2. I have used regular expression in sql before, now for the first time I am using it in HQL.
Query query = getSession().createQuery("From Company company
where company.id!=:companyId and
regexp_like(upper(rtrim(ltrim(company.num))), '^0*514619915$' )");
This is my hql, when i run it without regex_like function it runs as expected. But I am not able to execute it with regex_like expression.
It says..
nested exception is org.hibernate.hql.ast.QuerySyntaxException:
unexpected AST node: ( near line 1, column 66.....
Kindly help, how can I use regex_like in hibernate native query? OR some other alternative to do so.
Actually, you can't compare the result of REGEXP_LIKE to anything except in conditional statements in PL/SQL.
Hibernate seems to not accept a custom function without a returnType, as you always need to compare the output to something, i.e:
REGEXP_LIKE('bananas', 'a', 'i') = 1
As Oracle doesn't allow you to compare this function's result to nothing, I came up with a solution using case condition:
public class Oracle10gExtendedDialect extends Oracle10gDialect {
public Oracle10gExtendedDialect() {
super();
registerFunction(
"regexp_like", new SQLFunctionTemplate(StandardBasicTypes.BOOLEAN,
"(case when (regexp_like(?1, ?2, ?3)) then 1 else 0 end)")
);
}
}
And your HQL should look like this:
REGEXP_LIKE('bananas', 'a', 'i') = 1
It will work :)
You can most definitely use any type of database-specific function you wish with Hibernate HQL (and JPQL as long as Hibernate is the provider). You simply have to tell Hibernate about those functions. In 3.3 the only option for that is to provide a custom Dialect and register the function from the Dialect's constructor. If you take a look at the base Dialect class you will see lots of examples of registering functions. Usually best to extend the exact Dialect you currently use and simply provide your extensions (here, registering the function).
An interesting note is that Oracle does not classify regexp_like as a function. They classify it as a condition/predicate. I think this is mostly because Oracle SQL does not define a BOOLEAN datatype, even though their PL/SQL does and I would bet regexp_like is defined as a PL/SQL function returning BOOLEAN...
Assuming you currently use Oracle10gDialect, you would do:
public class MyOracle10gDialect extends Oracle10gDialect {
public Oracle10gDialect() {
super();
registerFunction(
"regexp_like",
new StandardSQLFunction( "regexp_like", StandardBasicTypes.BOOLEAN )
);
}
}
I cannot remember if the HQL parser likes functions returning booleans however in terms of being a predicate all by itself. You may instead have to convert true/false to something else and check against that return:
public class MyOracle10gDialect extends Oracle10gDialect {
public Oracle10gDialect() {
super();
registerFunction(
"regexp_like",
new StandardSQLFunction( "regexp_like", StandardBasicTypes.INTEGER ) {
#Override
public String render(
Type firstArgumentType,
List arguments,
SessionFactoryImplementor factory) {
return "some_conversion_from_boolean_to_int(" +
super.render( firstArgumentType, arguments, factory ) +
")";
}
}
);
}
}
You can't access specific database functions unless JPAQL/HQL provide a way to do so, and neither provide anything for regular expressions. So you'll need to write a native SQL query to use regexes.
On another, and very important point, a few colleagues (Oracle DBAs) told me to never use regexes in oracle, as they can't be indexed, which ends up in the DB performing a full DB scan. If the table has a few entries, then it's ok, but if it has lots of rows, it might cripple the performance.
For those using Hibernate criterion with sqlRestriction (Hibernate Version 4.2.7)
Criterion someCriterion = Restrictions.sqlRestriction("regexp_like (column_name, ?, 'i')", "(^|\\s)"+searchValue+"($|\\s|.$)", StringType.INSTANCE);
Or another option is to create similar function in oracle which will return numeric value based on operation result. Something like that
CREATE OR REPLACE FUNCTION MY_REGEXP_LIKE(text VARCHAR2, pattern VARCHAR2)
RETURN NUMBER
IS function_result NUMBER;
BEGIN
function_result := CASE WHEN REGEXP_LIKE(text, pattern)
THEN 1
ELSE 0
END;
RETURN(function_result);
END MY_REGEXP_LIKE;
and you will be able to use
MY_REGEXP_LIKE('bananas', 'a') = 1
You can use Specification.
Specification<YourEntity> specification = (root, query, builder) -> builder.equal(builder.selectCase()
.when(builder.function("regexp_like", Boolean.class, root.get("your_field"), builder.literal("^0*514619915$")), 1)
.otherwise(0), 1);
List<YourEntity> yourEntities = yourEntityRepository.findAll(specification);
i found the solution Accessing REGEXP_LIKE function in CriteriaBuilder useful for this. Add the dialect based on your Oracle version.

Categories

Resources