DataAccessException when implementing WITH RECURSIVE query using SQLITE and jOOQ - java

I am currently using jOOQ 3.13.4 and SQLite in order to perform a recursive query. I am also restricted to only using Java 8 compatible versions of jOOQ. My general table structure is as follows:
CREATE TABLE group (
ID GUID(32) UNIQUE NOT NULL PRIMARY KEY,
ParentID GUID(32) group (ID) ON DELETE CASCADE
);
CREATE TABLE map (
GroupID GUID(32) NOT NULL REFERENCES group(ID) ON DELETE CASCADE,
Path TEXT NOT NULL
);
My goal is to select one group by its group.ID and retrieve a list of all map.Path values that are associated with the nested groups.
I am currently using the WITH RECURSIVE tutorial provided by jOOQ and have code very similar to the one described in the tutorial. The {some_value} is an id I can pass in as a parameter to determine the group that I want all nested paths for.
Table<?> table = table(select(GROUP.ID.as(field(name("id"), UUID)),
GROUP.PARENTID.as(field(name("parentId"), UUID)),
MAP.PATH.as(field(name("path"), VARCHAR)))
.from(GROUP)
.join(MAP)
.on(MAP.GROUPID.eq(GROUP.ID))).as("table");
Field<UUID> id = table.field(name("id"), UUID);
Field<UUID> parentId = table.field(name("parentId"), UUID);
Field<String> path = table.field(name("path"), VARCHAR);
CommonTableExpression<?> cte = name("tree").fields("id", "path")
.as(select(id,
path).from(table)
.where(id.eq({some_value}))
.union(select(id,
path).from(table)
.join(table(name("tree")))
.on(parentId.eq(field(name("tree",
"id"),
UUID)))));
return ctx.withRecursive(cte)
.selectFrom(cte)
.fetch(field(name("tree", "path"), VARCHAR), String.class)
However when running the method that contains the above code I receive the following exception:
org.jooq.exception.DataAccessException: SQL [with recursive tree(id, path) as (select * from (select "table".id, "table".path from (select group.ID as id, group.ParentID as parentId, map.Path as path from group join map on map.GroupID = group.ID) as "table" where "table".id = ?) x union select * from (select "table".id, "table".path from (select group.ID as id, group.ParentID as parentId, map.Path as path from group join map on map.GroupID = group.ID) as "table" join tree on "table".parentId = tree.id) x) select tree.id, tree.path from tree]; [SQLITE_ERROR] SQL error or missing database (recursive reference in a subquery: tree)
org.sqlite.SQLiteException: [SQLITE_ERROR] SQL error or missing database (recursive reference in a subquery: tree)
This exception makes me think that you cannot reference table "tree" within the declaration of "tree" for SQLite, but it seems to be able to do so in the tutorial.
Is there a caveat with jOOQ and SQLite for WITH RECURSIVE? Or is my aliased table a source of the problem?

Related

Java Spring JPA: How to find the sum of only non null values in a column?

I currently have a query like this:
SELECT DISTINCT t.column1, SUM(t2.column2 IS NOT NULL)
FROM table t
LEFT OUTER JOIN table t2 on table.id = t2.id
GROUP BY column1, column2;
I am trying to implement the query using Spring JPA CriteriaBuilder. I see the CriteriaBuilder.sum() method, but I don't see a way to apply the IS NOT NULL part to the selection. Column2's data type is string.
Sample of my code
criteriaBuilder.multiselect(root.get("column1"), cb.sum(root.get("column2")));
Only in MySQL would such a query run, due MySQL’s relaxed syntax rules.
In particular, in mysql sum(column2 is not null) is a count, not a sum. The expression column2 is not null is boolean and in mysql false is 0 and true is 1, so summing this expression is a mysql hack to count the number of times column2 is not null.
To convert it to standard sql:
select
t.column1,
count(t2.column2)
from table t
left join table t2 on t.id = t2.id
group by t.column1
This works because count() (and all aggregate functions) ignore nulls.
This version also corrects the errant column in the group by clause - in any other database, your query would have produced a “grouping by aggregate expression” error.
This query will produce the same result in MySQL as your current query.
I was able to find a solution to my problem. Thanks to #bohemian for helping me write a correct sum expression.
final CriteriaBuilder cb = em.getCriteriaBuilder();
final CriteriaQuery<Model1> cq = cb.createQuery(Model1.class);
final Root<Model1> root = cq.from(Model1.class);
final Join<Model1, Model1> selfJoin =
root.join("tableJoinColumn", JoinType.LEFT);
selfJoin.on(...);
cq.multiselect(root.get("column1"), cb.sum(cb.selectCase()
.when(cb.isNull(selfJoin.get("column2")), 0).otherwise(1).as(Long.class)));
...
The self join required me to create an additional property on my model.
Model1.java
/**
* Property for LEFT INNER JOIN.
*/
#OneToOne(fetch = FetchType.LAZY)
#JoinColumn(name="id")
private Model1 tableJoinColumn;
How to use JPA CriteriaBuilder selectCase() so that it can have Predicate as result?
Self join in criteria query

How can I access a value when inserting into a table?

I'm trying to write a java sql query, the simplified table would be table(name,version) with a unique constraint on (name, version).
I'm trying to insert a row into my database with a conditional statement. Meaning that when a entry with the same name exists, it should insert the row with same name and its version increased by 1.
I have tried with the following:
INSERT INTO table(name,version)
VALUES(?, CASE WHEN EXISTS(SELECT name from table where name=?)
THEN (SELECT MAX(version) FROM table WHERE name = ?) +1
ELSE 1 END)
values are sent by user.
My question is, how can I access the 'name' inside the values so I could compare them?
If you want to write this as a single query:
INSERT INTO table (name, version)
SELECT ?, COLAESCE(MAX(t2.version) + 1, 1)
FROM table t2
WHERE t2.name = ?;
That said, this is dangerous. Two threads could execute this query "at the same time" and possibly create the same version number. You can prevent this from happening by adding a unique index/constraint on (name, version).
With the unique index/constraint, one of the updates will fail if there is a conflict.
I see at least two approaches:
1. For each pair of name and version you first query the max version:
SELECT MAX(VERSION) as MAX FROM <table> WHERE NAME = <name>
And then you insert the result + 1 with a corresponding insert query:
INSERT INTO <table>(NAME,VERSION) VALUES (<name>,result+1)
This approach is very straight-forward, easy-to-read and implement, however, not really performant because of so many queries necessary.
You can achieve that with sql alone with sql analytics and window functions, e.g.:
SELECT NAME, ROW_NUMBER() over (partition BY NAME ORDER BY NAME) as VERSION FROM<table>
You can then save the result of this query as a table using CREATE TABLE as SELECT...
(The assumption here is that the first version is 1, if it is not the case, then one could slightly rework the query). This solution would be very performant even for large datasets.
You should get the name before insertion. In your case, if something went wrong then how would you know about it so you get the name before insert query.
Not sure but you try this:
declare int version;
if exists(SELECT name from table where name=?)
then
version = SELECT MAX(version) FROM table WHERE name = ?
version += 1
else
version = 1
end
Regards.
This is actually a bad plan, you might be changing what the user's specified data. That is likely to not be what is desired, maybe they're not trying to create a new version but just unaware that the one wanted already exists. But, you can create a function, which your java calls, not only inserts the requested version or max+1 if the requested version already exists. Moreover it returns the actual values inserted.
-- create table
create table nv( name text
, version integer
, constraint nv_uk unique (name, version)
);
-- function to create version or 1+max if requested exists
create or replace function new_version
( name_in text
, version_in integer
)
returns record
language plpgsql strict
as $$
declare
violated_constraint text;
return_name_version record;
begin
insert into nv(name,version)
values (name_in,version_in)
returning (name, version) into return_name_version;
return return_name_version;
exception
when unique_violation
then
GET STACKED DIAGNOSTICS violated_constraint = CONSTRAINT_NAME;
if violated_constraint like '%nv\_uk%'
then
insert into nv(name,version)
select name_in, 1+max(version)
from nv
where name = name_in
group by name_in
returning (name, version) into return_name_version;
return return_name_version;
end if;
end;
$$;
-- create some data
insert into nv(name,version)
select 'n1', gn
from generate_series( 1,3) gn ;
-- test insert existing
select new_version('n2',1);
select new_version('n1',1);
select *
from nv
order by name, version;

How to delete multiple rows from multiple tables using Where clause?

Using an Oracle DB, I need to select all the IDs from a table where a condition exists, then delete the rows from multiple tables where that ID exists. The pseudocode would be something like:
SELECT ID FROM TABLE1 WHERE AGE > ?
DELETE FROM TABLE1 WHERE ID = <all IDs received from SELECT>
DELETE FROM TABLE2 WHERE ID = <all IDs received from SELECT>
DELETE FROM TABLE3 WHERE ID = <all IDs received from SELECT>
What is the best and most efficient way to do this?
I was thinking something like the following, but wanted to know if there was a better way.
PreparedStatement selectStmt = conn.prepareStatment("SELECT ID FROM TABLE1 WHERE AGE > ?");
selectStmt.setInt(1, age);
ResultSet rs = selectStmt.executeQuery():
PreparedStatement delStmt1 = conn.prepareStatment("DELETE FROM TABLE1 WHERE ID = ?");
PreparedStatement delStmt2 = conn.prepareStatment("DELETE FROM TABLE2 WHERE ID = ?");
PreparedStatement delStmt3 = conn.prepareStatment("DELETE FROM TABLE3 WHERE ID = ?");
while(rs.next())
{
String id = rs.getString("ID");
delStmt1.setString(1, id);
delStmt1.addBatch();
delStmt2.setString(1, id);
delStmt2.addBatch();
delStmt3.setString(1, id);
delStmt3.addBatch();
}
delStmt1.executeBatch();
delStmt2.executeBatch();
delStmt3.executeBatch();
Is there a better/more efficient way?
You could do it with one DELETE statement if two of your 3 tables (for example "table2" and "table3") are child tables of the parent table (for example "table1") that have a "ON DELETE CASCADE" option.
This means that the two child tables have a column (example column "id" of "table2" and "table3") that has a foreign key constraint with "ON DELETE CASCADE" option that references the primary key column of the parent table (example column "id" of "table1"). This way only deleting from the parent table would automatically delete associated rows in the child tables.
Check out this in more detail : http://www.techonthenet.com/oracle/foreign_keys/foreign_delete.php
If you delete only few records of a large tables ensure that an index on the
column ID is defined.
To delete the records from the table TABLE2 and 3 the best strategy is to use the CASCADE DELETE as proposed by
#ivanzg - if this is not possible, see below.
To delete from TABLE1 a far superior option that a batch delete on a row basis, use signle delete using the age based predicate:
PreparedStatement stmt = con.prepareStatement("DELETE FROM TABLE1 WHERE age > ?")
stmt.setInt(1,60)
Integer rowCount = stmt.executeUpdate()
If you can't cascade delete, use for the table2 and 3 the same concept as above but with the following statment:
DELETE FROM TABLE2/*or 3*/ WHERE ID in (SELECT ID FROM TABLE1 WHERE age > ?)
General best practice - minimum logic in client, whole logic in the database server. The database should be able to do reasonable execution plan
- see the index note above.
DELETE statement operates a table per statement. However the main implementations support triggers or other mechanisms that perform subordinate modifications. For example Oracle's CREATE TRIGGER.
However developers might end up figuring out what is the database doing behind their backs. (When/Why to use Cascading in SQL Server?)
Alternatively, if you need to use an intermediate result in your delete statements. You might use a temporal table in your batch (as proposed here).
As a side note, I see not transaction control (setAutoCommit(false) ... commit() in your example code. I guess that might be for the sake of simplicity.
Also you are executing 3 different delete batches (one for each table) instead of one. That might negate the benefit of using PreparedStatement.

SQL for return all the id which does not exist in db and passing in sql

I need some help writing an SQL statement for the below requirement.
I have list of employee_id which I need to check whether they are exist in the DB or not from the java layer I want to use one query for this.
Sample query:
SELECT *
FROM employee
WHERE employee_id IN (1001,1002,1002,10000004).
In this query 10000004 does not exist in DB.
One approach in my mind is to use the below query:
SELECT Count(employee_id)
FROM employee
WHERE employee_id IN (1001,1002,1002,10000004).
Then check the list size and the result from the query in java layer. But I don’t want this because I need all those employee_id which does not exist in DB.
declare #employeeids varchar(1000) --to store ids as comma seperated string
declare #tmpEmployee table (employee_id varchar(50)) --temp employee table to store ids from string
declare #pointer int
select #employeeids = '1001,1002,1002,10000004' --list of ids to check against database
while (charindex(',', #employeeids, 0) > 0)
begin
set #pointer = charindex(',', #employeeids, 0)
insert into #tmpEmployee (employee_id)
--remove white spaces if exists
select ltrim(rtrim(substring(#employeeids, 0, #pointer)))
set #employeeids = stuff(#employeeids, 1, #pointer, '')
end
insert into #tmpEmployee (employee_id)
select ltrim(rtrim(#employeeids))
select r.employee_id -- required ids which does not exists in database
,e.employee_id
from #tmpEmployee r
left join employee e on r.employee_id=e.employee_id
where e.employee_id is null
If you are using Oracle, then this link may help you. It's all about usage of built in SYS.DBMS_DEBUG_VC2COLL function
Exist a very bad way to do that is this:
SELECT * FROM (SELECT 5930 id UNION SELECT 8109 id
UNION SELECT 8110 id UNION SELECT 8115 id UNION SELECT 8112 id
UNION SELECT 8113 id UNION SELECT -1 id) b
WHERE b.id NOT IN (SELECT f.id FROM employee f)
I recommed you do that in other way.

HQL/SQL/Criteria to join-match all records in a given list while selecting all fields

I'm trying to write a HQL/Criteria/Native SQL query that will return all Employees that are assigned to a list of Projects. They must be assigned to all Projects in order to be selected.
An acceptable way of achieving this with native SQL can be found in the answer to this question: T-SQL - How to write query to get records that match ALL records in a many to many join:
SELECT e.id
FROM employee e
INNER JOIN proj_assignment a
ON e.id = a.emp_id and a.proj_id IN ([list of project ids])
GROUP BY e.id
HAVING COUNT(*) = [size of list of project ids]
However, I want to select all fields of Employee (e.*). It's not possible to define SQL grouping by all the columns(GROUP BY e.*), DISTINCT should be used instead. Is there a way to use DISTINCT altogether with COUNT(*) to achieve what I want?
I've also tried using HQL to perform this query. The Employee and ProjectAssignment classes don't have an association, so it's not possible to use Criteria to join them. I use a cross join because it's the way to perform a Join without association in HQL. So, my HQL looks like
select emp from Employee emp, ProjectAssignment pa
where emp.id = pa.empId and pa.paId IN :list
group by emp having count(*) = :listSize
However, due to a bug in Hibernate, GROUP BY entity does not work. The SQL it outputs is something like group by (emptable.id).
Subquerying the assignment table for each project (dynamically adding and exists (select 1 from proj_assignment pa where pa.emp_id=e.id and pa.proj_id = [anId]) for each project in the list) is not an acceptable option.
Is there a way to write this query properly, preferrably in HQL (in the end I want a List<Employee>), without modifying mappings and without explicitly selecting all columns in the native SQL ?
EDIT: I'm using Oracle 10g and hibernate-annotations-3.3.1.GA
How about:
select * from employee x where x.id in(
SELECT e.id
FROM employee e
INNER JOIN proj_assignment a
ON e.id = a.emp_id and a.proj_id IN ([list of project ids])
GROUP BY e.id
HAVING COUNT(*) = [size of list of project ids]
)
I've found an alternative way to achieve this in HQL, it's far more inefficient than what I'd like, (and than what is really possible without that nasty bug) but at least it works. It's better than repeating subselects for each project like
and exists (select 1 from project_assignment pa where pa.id = someId and pa.emp_id = e.id)
It consists of performing a self-join subquery in order to find out, for each of the Employees, how many of the projects in the list they are assigned to, and restrict results to only those that are in all of them.
select e
from Employee
where :listSize =
(select distinct count(*)
from Employee e2, ProjectAssignment pa
where
e2.id = pa.id_emp and
e.id = e2.id
and pa.proj_id IN :projectIdList
)

Categories