Android Room subquery in FROM clause not working as expected - android-sqlite

I tested my query using https://sqliteonline.com/ but cannot get it to work with Android Room and I cannot understand why (believe me I've tried). Why is the query in my DAO not working? On the other hand, I'm not sure this is the best way to achieve what I'm trying to do so please do tell me if I should change my approach completely.
Also, it's my first time posting a question, I apologize if I'm omitting something important.
I made a test database with sqliteonline and verified my query is working there. I'm using Android Room in my project, with the following lines in my build.gradle (app):
// Room components
implementation 'androidx.room:room-runtime:2.1.0-beta01'
annotationProcessor 'androidx.room:room-compiler:2.1.0-beta01'
androidTestImplementation 'androidx.room:room-testing:2.1.0-beta01'
The query that works in the testing environment:
SELECT *,
(SELECT COUNT(*) FROM groups WHERE parent_id=a.id) AS gCount,
(SELECT COUNT(*) FROM relays WHERE parent_id=a.id) AS rCount
FROM (SELECT DISTINCT * FROM worksites) a ;
The query in my DAO:
#Query("SELECT *, " +
"(SELECT COUNT(*) FROM relay_groups WHERE worksite_id=a.w_id) AS amountRelayGroups," +
"(SELECT COUNT(*) FROM relays_table WHERE worksite_id=a.w_id) AS amountRelays" +
"FROM (SELECT DISTINCT * FROM worksites_table) a")
LiveData<List<Worksites>> fetchAllWorksites();
My tables:
#Entity(tableName = "worksites_table")
public class Worksites {
#PrimaryKey(autoGenerate = true)
#ColumnInfo(name = "w_id")
private long worksite_id;
#NonNull
#ColumnInfo(name = "w_name")
private String worksiteName;
#ColumnInfo(name = "w_description")
private String worksiteDescription;
#Ignore
#ColumnInfo(name = "amountRelays")
private long amountRelays;
#Ignore
#ColumnInfo(name = "amountRelayGroups")
private long amountRelayGroups;
#Entity (tableName = "relay_groups",
foreignKeys = #ForeignKey(entity = Worksites.class,
parentColumns = "w_id",
childColumns = "worksite_id",
onDelete = CASCADE))
public class RelayGroups {
public RelayGroups(){
}
public RelayGroups(String name){
this.groupName = name;
}
#PrimaryKey(autoGenerate = true)
#ColumnInfo(name = "group_id")
private long group_id;
#NonNull
#ColumnInfo(name = "group_name")
private String groupName;
#ColumnInfo(name = "description")
private String description;
#NonNull
#ColumnInfo(name = "worksite_id")
private long worksiteId;
#Entity(tableName = "relays_table",
foreignKeys = #ForeignKey(entity = Worksites.class,
parentColumns = "w_id",
childColumns = "worksite_id",
onDelete = CASCADE))
public class Relays implements Parcelable {
#PrimaryKey(autoGenerate = true)
#ColumnInfo(name = "relay_id")
private long relayId;
#NonNull
#ColumnInfo(name = "relay_name")
private String relayName;
#NonNull
#ColumnInfo(name = "relay_number")
private String relayNumber;
#ColumnInfo(name = "relay_started")
private boolean relayOnOff;
#NonNull
#ColumnInfo(name = "relay_type")
private String relayType;
#ColumnInfo(name = "registered_master")
private boolean registeredMaster;
#ColumnInfo(name = "registered_user")
private boolean registeredUser;
#ColumnInfo(name = "security_code")
private String securityCode;
#ColumnInfo(name = "groups_string")
private String groupsString;
#NonNull
#ColumnInfo(name ="worksite_id")
private long worksiteId;
#ColumnInfo(name ="description")
private String relayDescription;
I'm trying to get rows with all columns from the table "worksites" AND a count of matching worksite_ids in "relay_groups" and "relays_table" -tables.
The Java compiler hits me with the following errors/warnings:
error: extraneous input '(' expecting {<EOF>, ';', K_ALTER, K_ANALYZE, K_ATTACH, K_BEGIN, K_COMMIT, K_CREATE, K_DELETE, K_DETACH, K_DROP, K_END, K_EXPLAIN, K_INSERT, K_PRAGMA, K_REINDEX, K_RELEASE, K_REPLACE, K_ROLLBACK, K_SAVEPOINT, K_SELECT, K_UPDATE, K_VACUUM, K_VALUES, K_WITH, UNEXPECTED_CHAR}
no viable alternative at input 'DISTINCT * FROM worksites_table)'
The query returns some columns [amountRelayGroups, amountRelays] which are not used by PACKAGE.Worksites. You can use #ColumnInfo annotation on the fields to specify the mapping. PACKAGE.Worksites has some fields [w_description] which are not returned by the query. If they are not supposed to be read from the result, you can mark them with #Ignore annotation. You can suppress this warning by annotating the method with #SuppressWarnings(RoomWarnings.CURSOR_MISMATCH). Columns returned by the query: w_name, w_id, amountRelayGroups, amountRelays. Fields in PACKAGE.Worksites: w_id, w_name, w_description.

I think you are missing a space before "FROM
it should be
#Query("SELECT *, " +
"(SELECT COUNT(*) FROM relay_groups WHERE worksite_id=a.w_id) AS amountRelayGroups," +
"(SELECT COUNT(*) FROM relays_table WHERE worksite_id=a.w_id) AS amountRelays" +
" FROM (SELECT DISTINCT * FROM worksites_table) a")

Related

Create List \ ArrayList from query

Entity
#Getter
#NoArgsConstructor
#AllArgsConstructor
#Immutable
#Entity
#Table(name = "payments")
public class ClientEntity {
#Id
private Long id;
#Column(name = "created_dt")
private LocalDate created_dt;
#Column(name = "username")
private String username;
#Column(name = "name")
private String name;
#Column(name = "amount")
private Integer amount;
#Column(name = "status")
private String status;
#Column(name = "account")
private String account;
#Column(name = "external_id")
private String external_id;
#Column(name = "external_status")
private String external_status;
}
Service
#Component
public class SchedulerService {
#Autowired
//private final AttachmentEmail attachmentEmail;
private final JdbcTemplate jdbc;
private static final String QUERY = "SELECT pp.id, pp.created_dt, au.username, ss.name, pp.amount,\n" +
"REPLACE(pp.status, 'SUCCESS', 'Success') AS status, pp.account,\n" +
"pp.external_id, COALESCE(pp.external_status, null, 'Indefined') AS external_status\n" +
"FROM payments AS pp\n" +
"INNER JOIN auth_user AS au ON au.id = pp.creator_id\n" +
"INNER JOIN services AS ss ON ss.id = pp.service_id\n" +
"WHERE pp.created_dt::date = (CURRENT_DATE - INTERVAL '1' day)::date\n" +
"AND ss.name = 'SomeName' AND pp.status = 'SUCCESS'";
private static final DateTimeFormatter date_format = DateTimeFormatter.ofPattern("dd.MM.yyyy");
private static final DateTimeFormatter time_format = DateTimeFormatter.ofPattern("HH:mm:ss");
public SchedulerService(AttachmentEmail attachmentEmail, JdbcTemplate jdbc) {
//this.attachmentEmail = attachmentEmail;
this.jdbc = jdbc;
}
#Scheduled(fixedRate = 20000)
public void sendMail() {
String filename = "select.csv";
try (FileWriter writer = new FileWriter(filename)) {
writer.append("id|Data|Time|Initiator|Service|Amout|Payment Status|Props|Identifier|External status").append("\n");
this.jdbc.query(QUERY, (ResultSet rs) -> writeLine(writer, rs));
} catch (Exception e) {
e.printStackTrace();
}
}
private void writeLine(FileWriter writer, ResultSet rs) {
try {
LocalDateTime ldt = rs.getTimestamp("created_dt").toLocalDateTime();
writer.append(String.valueOf(rs.getLong("id")));
writer.append('|');
writer.append(ldt.format(date_format));
writer.append('|');
writer.append(ldt.format(time_format));
writer.append('|');
writer.append(rs.getString("username"));
writer.append('|');
writer.append(rs.getString("name"));
writer.append('|');
writer.append(String.valueOf(rs.getBigDecimal("amount")));
writer.append('|');
writer.append(rs.getString("status"));
writer.append('|');
writer.append(rs.getString("props"));
writer.append('|');
writer.append(rs.getString("Identifier"));
writer.append('|');
writer.append(rs.getString("external_status"));
writer.append('\n');
} catch (Exception e) {
throw new IllegalStateException(e);
}
}
}
I am sending request data by mail, having previously formed this data in a csv file. However, for this data to be correct, I need to compare two queries from two databases with each other. In the example that I indicated above, there is only one request. The idea is that I would like to store my SELECT in a collection (List, ArrayList) and then I will compare these two objects with each other via Comparator. Can't find how I can convert the above SELECTs to List / ArrayList
To compare two result sets for equality, you can use set operations:
WITH q1 AS (/* first SELECT */),
q2 AS (/* second SELECT */)
SELECT NOT EXISTS (SELECT * FROM q1 EXCEPT SELECT * FROM q2)
AND NOT EXISTS (SELECT * FROM q2 EXCEPT SELECT * FROM q1);
This tests if there are any rows in the first result set that are not in the second and vice versa. If neither is the case, the two result sets must be identical.

How to return a count column not exists in table by JPA

I want find a way to get extra column that count my records and return it in 1 mapping entity with extra filed.
I tried #transient on field but it will not return value when query.
Then I remove #transient but get an exception when save.
Also I tried #Formula but received null pointer exception.
Here's my repository code:
#Query(value = "select id,account,session_id,create_time,count from query_history a join " +
"(select session_id sessionId,max(create_time) createTime,count(*) count from query_history group by session_id) b " +
"on a.session_id = b.sessionId and a.create_time = b.createTime where account = ?1 order by create_time desc",
countQuery = "select count(distinct(session_id)) from query_history where account = ?1",
nativeQuery = true)
Page<QueryHistory> findByNtAndGroupBySessionAndAction(String account, Pageable pageable);
entity code:
#Entity
#Table(name = "query_history")
#Data
public class QueryHistory {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column
private String account;
#Column
private Long sessionId;
#Column
private long createTime;
#Transient
private Integer count;
}
Sorry about my English and thanks a lot for any advice.
I solved the problem by projections spring-data-projections, in fact I tried this before but in my sql:
select id,account,session_id,create_time,count
which should be:
select id,account,session_id sessionId,create_time createTime,count
PS:
projection interface:
public interface QueryHistoryWithCountProjection {
Long getId();
String getAccount();
Long getSessionId();
long getCreateTime();
Integer getCount();
}

How to use a #ConstructorResult with a Set<SomeEnum> field

I'm trying to create a #NamedNativeQuery with a #ConstructorResult for a class that has a field with a Set of enum values.
VeterinarianJPA.java:
#Entity
#Table(name = "veterinarians")
#Setter
#Getter
#NoArgsConstructor
#NamedNativeQueries({
#NamedNativeQuery(
name = VeterinarianJPA.FIND_ALL_VETS,
query = "SELECT v.id, v.name, vs.specialisations " +
"FROM veterinarians v " +
"JOIN veterinarian_specialisations vs ON v.id = vs.vet_id",
resultSetMapping = VeterinarianJPA.VETERINARIAN_RESULT_MAPPER
)})
#SqlResultSetMappings({
#SqlResultSetMapping(
name = VeterinarianJPA.VETERINARIAN_RESULT_MAPPER,
classes = #ConstructorResult(
targetClass = Veterinarian.class,
columns = {
#ColumnResult(name = "id", type = Long.class),
#ColumnResult(name = "name"),
#ColumnResult(name = "specialisations", type = Set.class)
}
)
)})
class VeterinarianJPA {
static final String FIND_ALL_VETS = "net.kemitix.naolo.gateway.data.jpa.findAllVets";
static final String VETERINARIAN_RESULT_MAPPER = "net.kemitix.naolo.gateway.data.jpa.Veterinarian";
#Id
#GeneratedValue
private Long id;
private String name;
#ElementCollection
#Enumerated(EnumType.STRING)
#CollectionTable(
name = "veterinarian_specialisations",
joinColumns = #JoinColumn(name = "vet_id")
)
private final Set<VetSpecialisation> specialisations = new HashSet<>();
}
Veterinarian.java:
public final class Veterinarian {
private Long id;
private String name;
private Set<VetSpecialisation> specialisations;
public Veterinarian() {
}
public Veterinarian(final long id,
final String name,
final Set<VetSpecialisation> specialisations) {
this.id = id;
this.name = name;
this.specialisations = new HashSet<>(specialisations);
}
public long getId() {
return id;
}
public String getName() {
return name;
}
public Set<VetSpecialisation> getSpecialisations() {
return new HashSet<>(specialisations);
}
}
VetSpecialisation.java:
public enum VetSpecialisation {
RADIOLOGY,
DENTISTRY,
SURGERY
}
When I attempt to execute the named query:
entityManager.createNamedQuery(VeterinarianJPA.FIND_ALL_VETS, Veterinarian.class)
.getResultStream()
I get the following exception:
java.lang.IllegalArgumentException: Could not locate appropriate constructor on class : net.kemitix.naolo.entities.Veterinarian
at org.hibernate.loader.custom.ConstructorResultColumnProcessor.resolveConstructor(ConstructorResultColumnProcessor.java:92)
at org.hibernate.loader.custom.ConstructorResultColumnProcessor.performDiscovery(ConstructorResultColumnProcessor.java:45)
at org.hibernate.loader.custom.CustomLoader.autoDiscoverTypes(CustomLoader.java:494)
at org.hibernate.loader.Loader.processResultSet(Loader.java:2213)
at org.hibernate.loader.Loader.getResultSet(Loader.java:2169)
at org.hibernate.loader.Loader.executeQueryStatement(Loader.java:1930)
at org.hibernate.loader.Loader.executeQueryStatement(Loader.java:1892)
at org.hibernate.loader.Loader.scroll(Loader.java:2765)
at org.hibernate.loader.custom.CustomLoader.scroll(CustomLoader.java:383)
at org.hibernate.internal.SessionImpl.scrollCustomQuery(SessionImpl.java:2198)
at org.hibernate.internal.AbstractSharedSessionContract.scroll(AbstractSharedSessionContract.java:1058)
at org.hibernate.query.internal.NativeQueryImpl.doScroll(NativeQueryImpl.java:217)
at org.hibernate.query.internal.AbstractProducedQuery.scroll(AbstractProducedQuery.java:1462)
at org.hibernate.query.internal.AbstractProducedQuery.stream(AbstractProducedQuery.java:1486)
at org.hibernate.query.Query.getResultStream(Query.java:1110)
I expect that the SQL is returning multiple rows for a multi-valued Set rather than a single value, which is causing the constructor not to match. How do I change the SQL to produce the correct input to the constructor, or is there another configuration change I need to make?
Well, I'm not sure if that's even possible in the way you want to to this. But you can use LISTAGG function on specialisations table to inline the specialisations with veterinarians by using some kind of separator.
So the query should look like this:
SELECT v.id, v.name
(SELECT LISTAGG(vs.type, ';')
WITHIN GROUP (ORDER BY vs.type)
FROM veterinarian_specialisations vs
WHERE vs.vet_id = v.id) specialisations
FROM veterinarians v;
The query will return veterinarian and his semicolon separated specialisations:
1 NAME DENTISTRY;RADIOLOGY
And then in your Veterinarian class constructor you must remap String result back to Set of VetSpecialisation. I used Java 8 stream api just for convenience.
public final class Veterinarian {
private Long id;
private String name;
private Set<VetSpecialisation> specialisations;
public Veterinarian() {
}
public Veterinarian(final long id,
final String name,
final String specialisations) {
this.id = id;
this.name = name;
this.specialisations = Arrays.asList(specialisations.split(";"))
.stream()
.map(VetSpecialisation::valueOf) //Map string to VetSpecialisation enum.
.collect(Collectors.toSet());
}

Group By in Java Persistence/JPQL

I have an Entity class and it has #ManyToOne relationships. I need to use GROUP BY as in SQL query.
I have written a JPQL but its not working. My code is :
#NamedQuery(name = "AssetDepModel.findByAssedId",
query = "SELECT dep FROM AssetDepModel dep "
+ "JOIN dep.faDetails fad "
+ "WHERE fad.assetId.assId = :assetId_passed "
+ "GROUP BY dep.faDetails,dep.faDetails.id,dep.fiscalModel.fyId,dep.depAmt,dep.depId,dep.depMethodId,dep.depRate,dep.depTypeId,dep.quarterId,dep.createdDt,dep.createdBy,dep.updatedDt,dep.updatedby "
+ "ORDER BY fad.id")
public class AssetDepModel implements Serializable{
/**
*
*/
private static final long serialVersionUID = 1L;
public static final String FIND_BY_ASSET_ID = "AssetDepModel.findByAssedId";
public static final String FIND_BY_DETAIL_ID = "AssetDepModel.findByDetailId";
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "dep_id")
private int depId;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "fiscal_id", referencedColumnName = "fy_id")
private FiscalYrModel fiscalModel;
#Column(name = "quarter_id")
private int quarterId;
#ManyToOne
#JoinColumn(referencedColumnName = "id", name = "fa_details_id")
private FADetailsModel faDetails;
#Column(name = "dep_type_id")
private int depTypeId;
#Column(name = "dep_method_id")
private int depMethodId;
#Column(name = "dep_rate")
private Double depRate;
#Column(name = "dep_amt")
private Double depAmt;
#Column(name = "created_dt")
#Temporal(TemporalType.TIMESTAMP)
private Date createdDt;
#Column(name = "created_by")
private int createdBy;
#Column(name = "updated_dt")
#Temporal(TemporalType.TIMESTAMP)
private Date updatedDt;
#Column(name = "updated_by")
private int updatedby;
I tried this code but while calling the JPQL it always gives error saying that objects in Select is not included in Group By clause.
I need to GROUP BY according to a foreign key field.
I get following error :
Internal Exception: com.microsoft.sqlserver.jdbc.SQLServerException: Column
'inv_asset_depreciation.fa_details_id' is invalid in the select list because
it is not contained in either an aggregate function or the GROUP BY clause.
Error Code: 8120
Call: SELECT t0.dep_id, t0.created_by, t0.created_dt, t0.dep_amt, t0.dep_method_id,
t0.dep_rate, t0.dep_type_id, t0.quarter_id, t0.updated_dt, t0.updated_by,
t0.fa_details_id, t0.fiscal_id FROM inv_asset_depreciation t0, fiscal_yr t2,
inv_fixed_asset_detail_mcg t1 WHERE ((t1.asset_id = ?) AND ((t1.id = t0.fa_details_id)
AND (t2.fy_id = t0.fiscal_id))) GROUP BY t1.id, t1.asset_given_name,
t1.brand_name_description, t1.created_by, t1.created_date,
t1.dispose_dt_en,t1.dispose_dt_np, t1.dispose_value, t1.req_form_no,
t1.start_use_dt_en,t1.start_use_dt_np,t1.update_count, t1.updated_by,
t1.updated_date, t1.asset_id,t1.dept_id, t1.status, t1.id,t2.fy_id, t0.dep_amt,
t0.dep_id, t0.dep_method_id,t0.dep_rate, t0.dep_type_id,t0.quarter_id,
t0.created_dt, t0.created_by,t0.updated_dt, t0.updated_by
ORDER BY t1.id
bind => [1 parameter bound]
Query: ReportQuery(name="AssetDepModel.findByAssedId" referenceClass=AssetDepModel
sql="SELECT t0.dep_id, t0.created_by, t0.created_dt, t0.dep_amt,
t0.dep_method_id,t0.dep_rate,t0.dep_type_id, t0.quarter_id, t0.updated_dt,
t0.updated_by, t0.fa_details_id,t0.fiscal_id FROM inv_asset_depreciation t0,
fiscal_yr t2, inv_fixed_asset_detail_mcg t1 WHERE ((t1.asset_id = ?)
AND ((t1.id = t0.fa_details_id) AND (t2.fy_id = t0.fiscal_id)))
GROUP BY t1.id, t1.asset_given_name, t1.brand_name_description,
t1.created_by,t1.created_date, t1.dispose_dt_en, t1.dispose_dt_np,
t1.dispose_value, t1.req_form_no, t1.start_use_dt_en, t1.start_use_dt_np,
t1.update_count, t1.updated_by, t1.updated_date,t1.asset_id, t1.dept_id,
t1.status, t1.id, t2.fy_id, t0.dep_amt, t0.dep_id, t0.dep_method_id,
t0.dep_rate, t0.dep_type_id, t0.quarter_id, t0.created_dt,
t0.created_by, t0.updated_dt,t0.updated_by ORDER BY t1.id")
I modified a little bit like this :
#SuppressWarnings("unchecked")
public List<Object> findByAssetIdForSaleWriteOff(int assetId){
Query query = getEntityManager().createQuery("SELECT fad.id,dep.depAmt FROM AssetDepModel dep "
+ "JOIN dep.faDetails fad "
+ "WHERE fad.assetId.assId = "+assetId+" "
+ "GROUP BY fad.id,dep.depAmt "
+ "ORDER BY fad.id",AssetDepModel.class);
return (List<Object>)query.getResultList();
}
List<Object> objList = assetDepEJB.findByAssetIdForSaleWriteOff(faObj.getAssId());
Double amountDepTillNow = 0.0;
int fadId = 0;
int i=0;
for (Iterator<Object> iterator3 = objList.iterator(); iterator3
.hasNext();) {
Object[] obj = (Object[]) iterator3
.next();
if (i>0) {
if (fadId != (Integer) obj[0]) {
break;
}
}
fadId = (Integer) obj[0];
amountDepTillNow += (Double)obj[1];
i++;
}
It worked for me but If there is another efficient way, PLEASE DO SUGGEST ME.

Using COUNT in JPQL Query

I have the following JPQL query:
List<DestinationInfo> destinations = em.createQuery("SELECT NEW com.realdolmen.patuva.dto.DestinationInfo(d.name, d.continent, MIN(t.departureDate), MIN(t.pricePerDay), COUNT(t.id))" +
" FROM Destination d, Trip t" +
" WHERE d.continent = :continent " +
" GROUP BY d.name, d.continent").setParameter("continent", searchedContinent).getResultList();
If I run this I get the error:
javax.ejb.EJBTransactionRolledbackException: org.hibernate.hql.internal.ast.QuerySyntaxException: Unable to locate appropriate constructor on class [com.realdolmen.patuva.dto.DestinationsList]
If I leave out the COUNT(t.id) and remove that parameter from my DestinationInfo constructor it works fine. Why can't I map the COUNT(t.id) to my DestinationInfo DTO.
This is my DestinationInfo class:
public class DestinationInfo {
private String name;
private Continent continent;
private Date earliestDeparture;
private Integer totalNumTrips;
private BigDecimal lowestPrice;
public DestinationInfo(String name, Continent continent, Date earliestDeparture, BigDecimal lowestPrice, Integer totalNumTrips) {
this.name = name;
this.continent = continent;
this.earliestDeparture = earliestDeparture;
this.totalNumTrips = totalNumTrips;
this.lowestPrice = lowestPrice;
}
// getters and setters
}
Apparently COUNT(t.id) returns a number of type long. Changing the DestinationInfo class to the following makes it work:
public class DestinationInfo {
private String name;
private Continent continent;
private Date earliestDeparture;
private long totalNumTrips;
private BigDecimal lowestPrice;
public DestinationInfo(String name, Continent continent, Date earliestDeparture, BigDecimal lowestPrice, long totalNumTrips) {
this.name = name;
this.continent = continent;
this.earliestDeparture = earliestDeparture;
this.totalNumTrips = totalNumTrips;
this.lowestPrice = lowestPrice;
}
// getters and setters
}