How to duplicate and modify table rows using Jooq insertInto - java

I'm using Jooq and am trying to generate a near copy of a data set within the same table. In the process I want to update the value of one field to a known value. I've been looking at the docs & trying variations with no luck yet. Here is my approach updating the REGISTRATION table and setting the 'stage' field to the value 6 (where it was 5). So I'll end up with the original data plus a duplicate set with just the different stage value.
in pseudo code
insert into Registration (select * from Registration where stage=5) set stage=6
I tried this code below and thinking I could add a ".set(...)" method to set the value but that doesn't seem to be valid.
create.insertInto(REGISTRATION)
.select(
(selectFrom(REGISTRATION)
.where(REGISTRATION.STAGE.eq(5))
)
).execute();

I'm not aware of a database that supports an INSERT .. SELECT .. SET syntax, and if there were such a syntax, it certainly isn't SQL standards compliant. The way forward here would be to write:
In SQL:
INSERT INTO registration (col1, col2, col3, stage, col4, col5)
SELECT col1, col2, col3, 6, col4, col5
FROM registration
WHERE stage = 5;
In jOOQ:
create.insertInto(REGISTRATION)
.columns(
REGISTRATION.COL1,
REGISTRATION.COL2,
REGISTRATION.COL3,
REGISTRATION.STAGE,
REGISTRATION.COL4,
REGISTRATION.COL5)
.select(
select(
REGISTRATION.COL1,
REGISTRATION.COL2,
REGISTRATION.COL3,
val(6),
REGISTRATION.COL4,
REGISTRATION.COL5)
.from(REGISTRATION)
.where(REGISTRATION.STAGE.eq(5)))
.execute();
The following static import is implied:
import static org.jooq.impl.DSL.*;
In jOOQ, dynamically
Since you're looking for a dynamic SQL solution, here's how this could be done:
static <T> int copy(
DSLContext create, Table<?> table, Field<T> field,
T oldValue, T newValue
) {
List<Field<?>> into = new ArrayList<>();
List<Field<?>> from = new ArrayList<>();
into.addAll(Stream.of(table.fields())
.filter(f -> !field.equals(f))
.collect(toList()));
from.addAll(into);
into.add(field);
from.add(val(newValue));
return
create.insertInto(table)
.columns(into)
.select(
select(from)
.from(table)
.where(field.eq(oldValue))
.execute();
}

Thanks Lukas for your answer which I'll use a version of as it's nice and general. My own answer which I just got to work is less general but might be a useful reference for other people who come this way especially as it takes account of the identity field "id" which can otherwise cause problems.
public void duplicate(int baseStage, int newStage) {
Field<?>[] allFieldsExceptId = Stream.of(REGISTRATION.fields())
.filter(field -> !field.getName().equals("id"))
.toArray(Field[]::new);
Field<?>[] newFields = Stream.of(allFieldsExceptId).map(field -> {
if (field.getName().contentEquals("stage")) {
return val(newStage);
} else {
return field;
}
}).toArray(Field[]::new);
create.insertInto(REGISTRATION)
.columns(allFieldsExceptId)
.select(
select(newFields)
.from(REGISTRATION)
.where(REGISTRATION.STAGE.eq(baseStage)))
.execute();
}

Related

sql, javax.persistence.criteria: check a database table column with comma separated string values against a list of strings

I have a database table column with comma separated string values like this:
id mycolumn
-- --------
1 A
2 A, B
3 A, B, C
I can't change this, because it's an old customizer database.
I want to check mycolumn against a list of strings:
first query against the list ['A'] should be return the record 1
second query against the list ['A','B'] should be return records 1,2
third query against the list ['A','B','C'] should be return records 1,2,3
I need a sql (postgres) statement and further a java implementation with javax.persistence.criteria.CriteriaBuilder and javax.persistence.criteria.Predicate.
Thank you very much for your hints, Ann
edit:
Here my solution for the javax.persitence part:
public static void arrayContains(final CriteriaBuilder cb, final List<Predicate> where,
final Expression<String> expression, final String values) {
Expression<String> expressionValues = cb.function("string_to_array", String.class, cb.literal(values), cb.literal(", "));
Expression<String> expressionDb = cb.function("string_to_array", String.class, expression, cb.literal(", "));
where.add(cb.isTrue(cb.function("arraycontains", Boolean.class, expressionValues, expressionDb)));
}
In PostgreSQL use string_to_array(mycolumn,',') in the WHERE clause with the containment operator <#, e.g.
SELECT * FROM t
WHERE string_to_array(mycolumn,',') <# ARRAY['A','B','C']
Keep in mind that you have to create an index that correspond to this condition, in case you're dealing with a large table, e.g. a GIN index.
CREATE INDEX idx_t_mycolumn_gin ON t USING gin (string_to_array(mycolumn,','));
Demo: db<>fiddle

How to write dynamic query in JPA repository? Using Java springboot. Is Specification the answer?

I have a project in JPA repository. I want to retrieve values from two tables using UNIONS and a response parameter and I am having success with the following Native Query.
Public interface resultRepository extends JpaRepository<Result, String>{
#Query(value= “SELECT ‘This is from Table 1’ AS MSG, COLUMN1, COLUMN2 COLUMN3, COLUMN4, COLUMN5 FROM TABLE1
WHERE COLUMN1 = :column1 AND COLUMN2 = :column2 AND COLUMN3 = :column3
UNION
SELECT ‘This is from Table 2’ AS MSG, COLUMN1, COLUMN2 COLUMN3, COLUMN4, COLUMN5 FROM TABLE2
WHERE COLUMN1 = :column1 AND COLUMN2 = :column2 AND COLUMN3 = :column3 ”, nativeQuery = true)
List<Result> getResultByParameters(#Param(“column1”) String column1,
#Param(“column2’)String column2,#Param(“column3’) String column3);
}
My problem is. I want the “Where” clause to be dynamic. So that they can search by any of these parameters. Whether its column1 and column2 or column2 and column3. In my frontend, these column parameters will be NULL. Using native Query I can't decide to choose what parameters are using what.
I am reading up on Specifications in this link.
https://dimitr.im/writing-dynamic-queries-with-spring-data-jpa
But I just don’t understand it at all. Will I still be able to have a custom Message (MSG)? Or even search on more than one table using a union?
So there are many ways to create dynamic queries in JPA. Let's first list few of them down:
Custom Repository
Specification
Custom query in JPA repository itself (which you have already written, but I will show you more flexible workaround for this)
QueryDSL
Custom Repository
In this approach, basically you create an interface, and declare custom method into it. Then extend this interface to actual repository interface, and implement it manually. This is the most flexible way of working with custom queries. Below is the example:
public interface CustomRepository {
public Result myCustomQueryMethod(String params);
}
public interface ResultRepository extends JpaRepository<Result, Long>, CustomRepository { ... }
public class CustomRepositoryImpl implements CustomRepository {
#Autowired //#PersistentContext
private EntityManager entityManager;
public Result myCustomQueryMethod(String params) {
String nativeSql = "...";
// create query, execute it, and transform the results to object using object mapper or manually
}
}
Specifications
Next one is the specifications (as you have already mentioned). I will not explain very deep about specifications here, as it is already explained in the tutorial link which you have shared. But yes you can create dynamic queries using specification, if you do not want to write SQLs by yourself. But I guess you cannot have custom projections (custom select clause as you want for MSG parameter) with specifications.
Custom Query in JPA repository
Below is the slight modified version of your query, which can serve your purpose if the number of parameters which you want to search (in where clause) are fixed:
#Query(value= “SELECT ‘This is from Table 1’ AS MSG, COLUMN1, COLUMN2 COLUMN3, COLUMN4, COLUMN5 FROM TABLE1
WHERE (:column1 is null or COLUMN1 = :column1) AND (:column2 is null or COLUMN2 = :column2) AND (:column3 is null or COLUMN3 = :column3)
UNION
SELECT ‘This is from Table 2’ AS MSG, COLUMN1, COLUMN2 COLUMN3, COLUMN4, COLUMN5
FROM TABLE2
WHERE (:column1 is null or COLUMN1 = :column1) AND (:column2 is null or COLUMN2 = :column2) AND (:column3 is null or COLUMN3 = :column3) ”, nativeQuery = true)
List<Result> getResultByParameters(#Param(“column1”) String column1,
#Param(“column2’)String column2,#Param(“column3’) String column3);
See what I have done with the where clause there.
QueryDSL
It is again similar to Criteria API, but different and more simple syntax. You can create type safe dynamic queries with QueryDSL too. Here is the link which explains QueryDSL in more detail - https://www.baeldung.com/querydsl-with-jpa-tutorial
e.g. you can change your SQL request where clause as follows
WHERE COLUMN1 IN (:column1, :column3, :column3) OR COLUMN2 IN (:column1, :column3, :column3) OR COLUMN3IN (:column1, :column3, :column3)
or
WHERE (:column1 IS NULL OR COLUMN1 = :column1) OR (:column1 IS NULL OR COLUMN2 = :column2) OR (:column1 IS NULL OR COLUMN3 = :column3)
It depends on what logic do you want

How to allow more than 6 arguments in Quarkus Tuple.of() method?

As I am new to Quarkus, I am stuck at once place and it would be great, if anyone can help me out.
I am creating a application with Quarkus reactive PgPool (basically of vertx), and I have to inset records in table which have columns more than 6
import io.smallrye.mutiny.Uni;
import io.vertx.mutiny.sqlclient.Tuple;
import io.vertx.mutiny.pgclient.PgPool;
public Uni<Long> addRequest(PgPool client) {
String sql = "Insert into request (column1, column2, column3, column4, column5, column6, column7, column8, column9"
+", column10, column11, column12, column13, column14, column15)"+
" values ($1, $2, $3, $4, $5, $6, $7, $8, $9"
+", $10, $11, $12, $13, $14, $15) RETURNING id";
**//Here I am adding 15 columns to Tuple but it's allowing**
Tuple t = Tuple.of(column1, column2, column3, column4, column5, column6, column7, column8, column9
, column10, column11, column12, column13, column14, column15);
return client.preparedQuery(sql).execute(t).onItem().transform(pgRowSet -> pgRowSet.iterator().next().getLong("id"));
}
Please let me know how to do it, correctly or if there is any better way to do it like directly passing POJO class instance or using NamedJdbcTemplate because some of my table contains above 50 columns and to maintain number not so good?
You could store all columns into a List and pass it as argument, using this Tuple.tuple(List) method.
List<Column> columns = new ArrayList<>();
columns.add(column1);
//...
Tuple t = Tuple.tuple(columns);
Do not confuse with Tuple.of(), the method is Tuple.tuple()!
I'm using quarkus 2.0.2.Final:
<quarkus.platform.version>2.0.2.Final</quarkus.platform.version>
And seems only wrap() method works for me:
The Tuple source code shows that the wrap method accept List type:

Spark Cassandra connector filtering with IN clause

I am facing some issues with spark cassandra connector filtering for java. Cassandra allows the filtering by last column of the partition key with IN clause.
e.g
create table cf_text
(a varchar,b varchar,c varchar, primary key((a,b),c))
Query : select * from cf_text where a ='asdf' and b in ('af','sd');
sc.cassandraTable("test", "cf_text").where("a = ?", "af").toArray.foreach(println)
How count I specify the IN clause which is used in the CQL query in spark? How range queries can be specified as well?
Just wondering, but does your Spark code above work? I thought that Spark won't allow a WHERE on partition keys (a and b in your case), since it uses them under the hood (see last answer to this question): Spark Datastax Java API Select statements
In any case, with the Cassandra Spark connector, you are allowed to stack your WHERE clauses, and an IN can be specified with a List<String>.
List<String> valuesList = new ArrayList<String>();
valuesList.Add("value2");
valuesList.Add("value3");
sc.cassandraTable("test", "cf")
.where("column1 = ?", "value1")
.where("column2 IN ?", valuesList)
.keyBy(new Function<MyCFClass, String>() {
public String call(MyCFClass _myCF) throws Exception {
return _myCF.getId();
}
});
Note that the normal rules of using IN with Cassandra/CQL still apply here.
Range queries function in a similar manner:
sc.cassandraTable("test", "person")
.where("age > ?", "15")
.where("age < ?", "20")
.keyBy(new Function<Person, String>() {
public String call(Person _person) throws Exception {
return _person.getPersonid();
}
});

How to query for a List<String> in JdbcTemplate?

I'm using Spring's JdbcTemplate and running a query like this:
SELECT COLNAME FROM TABLEA GROUP BY COLNAME
There are no named parameters being passed, however, column name, COLNAME, will be passed by the user.
Questions
Is there a way to have placeholders, like ? for column names? For example SELECT ? FROM TABLEA GROUP BY ?
If I want to simply run the above query and get a List<String> what is the best way?
Currently I'm doing:
List<Map<String, Object>> data = getJdbcTemplate().queryForList(query);
for (Map m : data) {
System.out.println(m.get("COLNAME"));
}
To populate a List of String, you need not use custom row mapper. Implement it using queryForList.
List<String>data=jdbcTemplate.queryForList(query,String.class)
Use following code
List data = getJdbcTemplate().queryForList(query,String.class)
Is there a way to have placeholders, like ? for column names? For example SELECT ? FROM TABLEA GROUP BY ?
Use dynamic query as below:
String queryString = "SELECT "+ colName+ " FROM TABLEA GROUP BY "+ colName;
If I want to simply run the above query and get a List what is the best way?
List<String> data = getJdbcTemplate().query(query, new RowMapper<String>(){
public String mapRow(ResultSet rs, int rowNum)
throws SQLException {
return rs.getString(1);
}
});
EDIT: To Stop SQL Injection, check for non word characters in the colName as :
Pattern pattern = Pattern.compile("\\W");
if(pattern.matcher(str).find()){
//throw exception as invalid column name
}
You can't use placeholders for column names, table names, data type names, or basically anything that isn't data.

Categories

Resources