greenrobot/greenDAO · error · DaoException
JOINs are not supported for DELETE queries
Error message
JOINs are not supported for DELETE queries
What it means
DELETE statements in SQL cannot reference joined tables, so QueryBuilder.buildDelete() rejects any builder that has joins registered. The library throws a DaoException early rather than emitting invalid SQL. DeleteQuery only supports single-table deletes with where conditions.
Solutions
- Rewrite the delete using a subquery-style condition (e.g. where(Property.in(...query with join...))) — build the joined select as a separate query collecting keys, then delete with where(key.in(keys)).
- Execute raw SQL via the database handle if a correlated multi-table delete is required.
- Loop over the joined SELECT results and delete each entity individually (slower but simple).
Example fix
// before
QueryBuilder<User> qb = userDao.queryBuilder();
qb.join(DeptDao.class, UserDao.Properties.DeptId)
.where(DeptDao.Properties.Name.eq("old"));
qb.buildDelete(); // DaoException
// after
List<Long> ids = userDao.queryBuilder()
.join(DeptDao.class, UserDao.Properties.DeptId)
.where(DeptDao.Properties.Name.eq("old"))
.list() // collect matching users
.stream().map(User::getId).collect(toList());
userDao.deleteByKeyInTx(ids); Defensive patterns
Strategy: fallback
Try / catch
try { qb.buildDelete().executeDeleteWithoutDetachingEntities(); } catch (DaoException e) { deleteByKeyInTx(collectKeysViaJoinedSelect()); } Prevention
- Treat buildDelete() as single-table only; keep joins on select builders.
- Collect matching keys with a joined select, then deleteByKeyInTx().
- Document this constraint in query-building helpers.
When it happens
Trigger: Calling queryBuilder.join(...)...buildDelete() — i.e. buildDelete() on a QueryBuilder where joins is non-empty (from join(Class, Property) calls).
Common situations: Developers write a SELECT with a JOIN to filter rows, then call buildDelete() on the same builder expecting it to delete the matched rows; joins are valid for select queries only.
Understand the failure class
Background: UnsupportedOperationException and "is not supported" errors: when a library deliberately refuses a call — this error's family across 30 libraries.
Related errors
- Offset cannot be set without limit
- Cannot delete entity, key is null
- Table alias required
- This operation only works with cached lazy lists
- Could not move to cursor location
AI-assisted analysis of greenrobot/greenDAO@0bbb338e17 (2026-09-08).
Data as JSON: /api/errors/48e61bf4dec82f16.
Report an issue: GitHub.
Appendix: source
Thrown at DaoCore/src/main/java/org/greenrobot/greendao/query/QueryBuilder.java:348
int offsetPosition = -1;
if (offset != null) {
if (limit == null) {
throw new IllegalStateException("Offset cannot be set without limit");
}
builder.append(" OFFSET ?");
values.add(offset);
offsetPosition = values.size() - 1;
}
return offsetPosition;
}
/**
* Builds a reusable query object for deletion (Query objects can be executed more efficiently than creating a
* QueryBuilder for each execution.
*/
public DeleteQuery<T> buildDelete() {
if (!joins.isEmpty()) {
throw new DaoException("JOINs are not supported for DELETE queries");
}
String tablename = dao.getTablename();
String baseSql = SqlUtils.createSqlDelete(tablename, null);
StringBuilder builder = new StringBuilder(baseSql);
// tablePrefix gets replaced by table name below. Don't use tableName here because it causes trouble when
// table name ends with tablePrefix.
appendJoinsAndWheres(builder, tablePrefix);
String sql = builder.toString();
// Remove table aliases, not supported for DELETE queries.
// TODO(?): don't create table aliases in the first place.
sql = sql.replace(tablePrefix + ".\"", '"' + tablename + "\".\"");
checkLog(sql);
return DeleteQuery.create(dao, sql, values.toArray());
}
View on GitHub (pinned to 0bbb338e17)