Issue in Converting postgres stored procedure to java query - java

I am migrating a DB. In old DB we use some stored procedures - SP which we want to get rid off in new DB. Simply we want to use plain java query instead of a SP. We will call the query from our java spring boot app.
Here is the SP :
CREATE OR REPLACE PROCEDURE public.spfetchowner(
owner integer,
optype integer,
INOUT p_refcur refcursor)
LANGUAGE 'plpgsql'
AS $BODY$
BEGIN
OPEN p_refcur FOR
SELECT
z.owner_num,
COALESCE(op_type_num, optype) AS op_type_num,
ad.sunday, ad.monday, ad.tuesday, ad.wednesday, ad.thursday, ad.friday, ad.saturday
FROM (SELECT owner AS owner_num) AS z
LEFT OUTER JOIN owner_details AS ad
ON z.owner_num = ad.owner_num AND op_type_num = optype;
END;
$BODY$;
ALTER PROCEDURE public.spfetchowner(integer, integer, refcursor)
OWNER TO postgres;
My DB table details :
owner_num integer NOT NULL,
op_type_num integer NOT NULL,
sunday numeric(5,3),
monday numeric(5,3),
tuesday numeric(5,3),
wednesday numeric(5,3),
thursday numeric(5,3),
friday numeric(5,3),
saturday numeric(5,3),
CONSTRAINT pk_owner_details PRIMARY KEY (owner_num, op_type_num)
This is my java repository method to fetch details from owner_details
#Query(nativeQuery = true,
value = "I need the proper equivalent query from the SP here")
OwnerDetails fetchOwnerDetailsByOwnerNumAndOpType(
#Param("ownerNum") Integer owner,
#Param("typeNum") Integer type );
I am unable to form the query from the SP that I need to use in repo method. Can someone help me out here on the query formation part. Simple select is working but I would need the select query as framed in the SP with the joins and then how to use the same in my repo method.

When specifying the parameters to your query (#Query) you basically have 2 options
Option 1 (using named parameters)
#Query(nativeQuery = true,
value = "SELECT
z.owner_num,
COALESCE(op_type_num, optype) AS op_type_num,
ad.sunday, ad.monday, ad.tuesday, ad.wednesday, ad.thursday, ad.friday, ad.saturday
FROM (SELECT :ownerNum AS owner_num) AS z
LEFT OUTER JOIN owner_details AS ad
ON z.owner_num = ad.owner_num AND op_type_num = :typeNum")
OwnerDetails fetchOwnerDetailsByOwnerNumAndOpType(
#Param("ownerNum") Integer owner,
#Param("typeNum") Integer type );
or option 2 (using ordinal numbers preceded by a ?)
#Query(nativeQuery = true,
value = "SELECT
z.owner_num,
COALESCE(op_type_num, optype) AS op_type_num,
ad.sunday, ad.monday, ad.tuesday, ad.wednesday, ad.thursday, ad.friday, ad.saturday
FROM (SELECT ?1 AS owner_num) AS z
LEFT OUTER JOIN owner_details AS ad
ON z.owner_num = ad.owner_num AND op_type_num = ?2")
OwnerDetails fetchOwnerDetailsByOwnerNumAndOpType(
Integer owner,
Integer type );

Related

How to convert a Store Proc in Postgresql to simple Java inline query

I have the below SP which I am trying to convert into simple Java inline query :
CREATE OR REPLACE PROCEDURE public.spdummytable(
par_zone_no integer,
par_fpsallocid integer,
INOUT p_refcur refcursor)
LANGUAGE 'plpgsql'
AS $BODY$
BEGIN
OPEN p_refcur FOR
SELECT
z.zone_no,
m AS monthnumber,
COALESCE(fpsallocid, par_FPSallocid) AS fpsallocid,
to_char((CAST ('2000-01-01' AS TIMESTAMP WITHOUT TIME ZONE))::TIMESTAMP + (COALESCE(aw.month, m) - 1::NUMERIC || ' MONTH')::INTERVAL, 'Month') AS monthname,
week1,
week2,
week3,
week4
FROM (SELECT par_Zone_No AS zone_no) AS z
CROSS JOIN (SELECT 1 AS m
UNION SELECT 2
UNION SELECT 3
UNION SELECT 4
UNION SELECT 5
UNION SELECT 6
UNION SELECT 7
UNION SELECT 8
UNION SELECT 9
UNION SELECT 10
UNION SELECT 11
UNION SELECT 12) AS moty
LEFT OUTER JOIN anotherTable AS aw
ON z.zone_no = aw.zone_no AND
aw.month = moty.m AND
COALESCE(fpsallocid, par_FPSallocid) = par_FPSallocid;
END;
$BODY$;
ALTER PROCEDURE public.spdummytable(integer, integer, refcursor)
OWNER TO postgres;
This will fetch some weekly values for every month from Jan to Dec.
What I am trying is below :
public List<MyResponse> result = null;
Connection conn = DatabaseConnection.connect();
PreparedStatement stmt = conn.prepareStatement("call public.spdummytable(?,?,?,,....,cast('p_refcur' as refcursor)); FETCH ALL IN \"p_refcur\";");
stmt.setString(1, "8006");
stmt.setString(2, "8049");
----
----
boolean isResultSet = stmt.execute();
if(isResultSet) {
ResultSet rs = stmt.getResultSet();
while(rs.next()) {
MyResponse myResponse = new MyResponse();
myResponse.setSomeVariable(rs.getString("columnname"));
-----
-----
result.add(myResponse)
}
rs.close();
conn.close();
}
But I am confused on the query formation part from the above SP. This seems to be a complex conversion. Can someone please help form the inline query. Appreciate your help on this.
EDIT/UPDATE
If I am unable to explain myself, I just want to say that I need to form the postgresql SELECT query from the above SP. I know the PreparedStatement is wrong above, I am trying to form a basic sql query from the above SP . Changing/Modifying the SP is not an option for me. I am planning to cut the dependency from the database and control it over Java. Please help.
I don't think getResultSet works with a stored procedure like that but I'm unsure. You're operating on a cursor with your INOUT parameter. As suggested in the comments, this would be much easier with a set returning function.
Note: stored procedures didn't exist in Postgres before Postgres 11.
If you cannot convert this to a set returning function, you'll need to handle the cursor object in a different manner. Something like this:
CallableStatement stmt = conn.prepareCall("{? = call public.spdummytable(?,?) }");
stmt.registerOutParameter(1, Types.OTHER);
stmt.setString(2, "8006");
stmt.setString(3, "8049");
stmt.execute();
ResultSet results = (ResultSet) stmt.getObject(1);
while (results.next()) {
// do something with the results.
}
Set returning function:
CREATE OR REPLACE FUNCTION public.spdummytable
( par_zone_no INTEGER
, par_fpsallocid INTEGER
)
RETURNS TABLE ( zone_no INTEGER -- I don't know the data types of these fields
, monthnumber INTEGER
, fpsallocid INTEGER
, monthname TEXT
, week1 TEXT
, week2 TEXT
, week3 TEXT
, week4 TEXT
)
AS $$
BEGIN
RETURN QUERY
SELECT z.zone_no AS zn
, moty AS mo_num
, COALESCE(fpsallocid, par_FPSallocid) AS fpsid
, to_char((CAST ('2000-01-01' AS TIMESTAMP WITHOUT TIME ZONE))::TIMESTAMP + (COALESCE(aw.month, m) - 1::NUMERIC || ' MONTH')::INTERVAL, 'Month') AS mo_name
, week1 w1
, week2 w2
, week3 w3
, week4 w4
FROM (SELECT par_Zone_No AS zone_no) AS z
CROSS JOIN generate_series(1, 12) AS moty
LEFT OUTER JOIN anotherTable AS aw ON z.zone_no = aw.zone_no
AND aw.month = moty
AND COALESCE(fpsallocid, par_FPSallocid) = par_FPSallocid
;
END;
$$ LANGUAGE PLPGSQL;
You could also define your own return type and use RETURNS SETOF your_type. Use the same function body as above.
CREATE TYPE type_dummytype AS
( zone_no INTEGER -- I don't know the data types of these fields
, monthnumber INTEGER
, fpsallocid INTEGER
, monthname TEXT
, week1 TEXT
, week2 TEXT
, week3 TEXT
, week4 TEXT
);
CREATE OR REPLACE FUNCTION public.spdummytable
( par_zone_no INTEGER
, par_fpsallocid INTEGER
)
RETURNS SETOF type_dummytype
AS $$ ... $$
Then your prepared statement becomes something like this:
PreparedStatement stmt = conn.prepareStatement("SELECT * FROM public.spdummytable(?, ?);");
stmt.setString(1, "8006");
stmt.setString(2, "8049");
All of the other java should be good to go.

Hibernate not Injecting Parameters in Query Annotation

I've read the documentation on inserting method parameters into queries and other questions however they all suggest that this should work.
#Query(value = "SELECT * FROM period WHERE time LIKE \":day%\" AND id IN (SELECT id FROM booking)", nativeQuery = true)
List<Booking> findAllBookingsOnDay(#Param("day") String day);
Results in:
Hibernate: SELECT * FROM period WHERE time LIKE ":day%" AND id IN (SELECT id FROM booking)
I've tried removing quotations and percentage but that just results in.
Hibernate: SELECT * FROM period WHERE time LIKE ? AND id IN (SELECT id FROM booking)
The correct way to do this is to just use an unescaped placeholder in the query, and the bind the LIKE wildcard expression to it from the Java side.
#Query(value = "SELECT * FROM period WHERE time LIKE :day AND id IN (SELECT id FROM booking)", nativeQuery =true)
List<Booking> findAllBookingsOnDay(#Param("day") String day);
Usage:
String day = "%friday%"; // or whatever value would make sense here
findAllBookingsOnDay(day);

`SELECT COUNT(*) FROM (SELECT DISTINCT ...)` in Hibernate or JPA

This feels like a trivial use case for Hibernate or JPA, but I've been struggling for a couple of days to get this to work.
I have an position entity class that has latitude, longitude and updateTime fields (among others). I would like to count the number of distinct combinations of those three fields while ignoring the others. In SQL, this is trivial:
SELECT COUNT(*) FROM (SELECT DISTINCT LONGITUDE, LATITUDE, UPDATE_TIME FROM POSITION) AS TEMP;
It is important that I abstract myh database implementation from the rest of my application because different users may wish to use different database engines. (Heck I use h2 for testing and mariadb for local production...)
I have been trying to translate this SQL into Java code using either Hibernate or JPA syntax, but I cannot figure out how.
EDIT - Here is as close as I have been able to get using JPA (ref: https://en.wikibooks.org/wiki/Java_Persistence/Criteria)
public long getCountDistinctInFlightPositions() {
Session session = sessionFactory.openSession();
CriteriaBuilder criteriaBuilder = session.getCriteriaBuilder();
CriteriaQuery<Tuple> innerQuery = criteriaBuilder.createTupleQuery();
Root<Position> position = innerQuery.from(Position.class);
innerQuery.multiselect(
position.get("longitude"),
position.get("latitude"),
position.get("updateTime")
);
// The method countDistinct(Expression<?>) in the type CriteriaBuilder is not applicable for the arguments (CriteriaQuery<Tuple>)
criteriaBuilder.countDistinct(innerQuery);
return 1;
}
You can do it this way:
CriteriaQuery<Long> countQuery = cb.createQuery( Long.class );
Root<Position> root = countQuery.from( Position.class );
countQuery.select( cb.count( root.get( "id" ) ) );
Subquery<Integer> subQuery = countQuery.subquery( Integer.class );
Root<Position> subRoot = subQuery.from( Position.class );
subQuery.select( cb.min( subRoot.get( "id" ) ) );
subQuery.groupBy( subRoot.get( "longitude" ),
subRoot.get( "latitude" ),
subRoot.get( "updateTime" ) );
countQuery.where( root.get( "id" ).in( subQuery ) );
Long count = entityManager.createQuery( countQuery ).getSingleResult();
This effectively generates the following SQL:
SELECT COUNT( p0.id ) FROM Position p0
WHERE p0.id IN (
SELECT MIN( p1.id )
FROM Position p1
GROUP BY p1.longitude, p1.latitude, p1.updateTime )
In a scenario where I have 3 rows and 2 of them have the same tuple of longitude, latitude, and update time, the query will return a result of 2.
Make sure you maintain a good index on [Longtitude, Latitude, UpdateTime] here so that you can take advantage of faster GROUP BY execution. The PK is already b-tree indexed so the other operations wrt COUNT/MIN should be accounted for easily by that index already.

JOOQ: Type class org.jooq.impl.SelectImpl is not supported in dialect DEFAULT

My question about writing query in jooq dsl.
I have some list of client attributes stored in an Oracle database.
Table structure is following:
CLIENT_ATTRIBUTE_DICT (ID, CODE, DEFAULT_VALUE) - list of all possible attributes
CLIENT_ATTRIBUTE (ATTRIBUTE_ID, CLIENT_ID, VALUE) - attributes values for different clients
I'm trying to select values of all existing attributes (in dict) for given client:
If exist row in CLIENT_ATTRIBUTE with given clientId than attribute value = CLIENT_ATTRIBUTE.VALUE else CLIENT_ATTRIBUTE_DICT.DEFAULT_VALUE
My query in SQL (works fine):
SELECT d.code,
NVL
(
(
SELECT value
FROM CLIENT_ATTRIBUTE a
WHERE a.ATTRIBUTE_ID = d.id
AND a.CLIENT_ID = 1
),
(
SELECT DEFAULT_VALUE
FROM CLIENT_ATTRIBUTE_DICT dd
WHERE dd.id=d.id
)
) value
FROM CLIENT_ATTRIBUTE_DICT d;
My query in Jooq dsl:
ClientAttributeDictTable dict = CLIENT_ATTRIBUTE_DICT.as("d");
Map<String, String> attributes =
dsl.select(
dict.CODE,
DSL.nvl(
dsl.select(CLIENT_ATTRIBUTE.VALUE)
.from(CLIENT_ATTRIBUTE)
.where(
CLIENT_ATTRIBUTE.ATTRIBUTE_ID.eq(dict.ID),
CLIENT_ATTRIBUTE.CLIENT_ID.eq(clientId)
),
dsl.select(CLIENT_ATTRIBUTE_DICT.DEFAULT_VALUE)
.from(CLIENT_ATTRIBUTE_DICT)
.where(CLIENT_ATTRIBUTE_DICT.ID.eq(dict.ID))
).as("value")
).from(dict)
.fetchMap(String.class, String.class);
When jooq query runs it fails with error message:
Caused by: Type class org.jooq.impl.SelectImpl is not supported in dialect DEFAULT
at org.jooq.impl.DefaultDataType.getDataType(DefaultDataType.java:757) ~[na:na]
at org.jooq.impl.DefaultDataType.getDataType(DefaultDataType.java:704) ~[na:na]
at org.jooq.impl.DSL.getDataType(DSL.java:14378) ~[na:na]
at org.jooq.impl.DSL.val(DSL.java:12766) ~[na:na]
at org.jooq.impl.Utils.field(Utils.java:802) ~[na:na]
at org.jooq.impl.DSL.nvl(DSL.java:8403) ~[na:na]
What am I doing wrong?
UPD
JOOQ version 3.7.2
The error is two-fold.
Your API usage is currently not supported (jOOQ currently doesn't accept Select types in the nvl() function). Write this instead:
DSL.nvl(
DSL.field(dsl.select(CLIENT_ATTRIBUTE.VALUE)
// ^^^^^^^^^ explicitly wrap the Select in a Field
.from(CLIENT_ATTRIBUTE)
.where(
CLIENT_ATTRIBUTE.ATTRIBUTE_ID.eq(dict.ID),
CLIENT_ATTRIBUTE.CLIENT_ID.eq(clientId))),
DSL.field(dsl.select(CLIENT_ATTRIBUTE_DICT.DEFAULT_VALUE)
// ^^^^^^^^^ explicitly wrap the Select in a Field
.from(CLIENT_ATTRIBUTE_DICT)
.where(CLIENT_ATTRIBUTE_DICT.ID.eq(dict.ID)))
).as("value")
jOOQ (or rather, the Java compiler) cannot detect this API misusage, because the nvl() API is overloaded in a way that always compiles. This is issue https://github.com/jOOQ/jOOQ/issues/5340 and will be fixed in a future version (probably jOOQ 3.9)
I found workaround.
First I rewrited SQL query:
SELECT D.CODE, NVL(S.VALUE, D.DEFAULT_VALUE) ATTRIBUTE_VALUE
FROM CLIENT_ATTRIBUTE_DICT D
LEFT JOIN
(SELECT DD.ID,
A.VALUE
FROM CLIENT_ATTRIBUTE_DICT DD
JOIN CLIENT_ATTRIBUTE A
ON A.ATTRIBUTE_ID = DD.ID
WHERE A.CLIENT_ID = 1
) S ON S.ID = D.ID;
Than I express my query in JOOQ dsl:
String ID_FIELD_NAME = "ID";
String VALUE_FIELD_NAME = "VALUE"
ClientAttributeDictTable DICT_ATTRIBUTES = CLIENT_ATTRIBUTE_DICT.as("D");
Table<Record2<Long, String>> EXISTING_ATTRIBUTES =
dsl.select(CLIENT_ATTRIBUTE_DICT.ID, CLIENT_ATTRIBUTE.VALUE)
.from(CLIENT_ATTRIBUTE_DICT)
.join(CLIENT_ATTRIBUTE)
.on(CLIENT_ATTRIBUTE.ATTRIBUTE_ID.eq(CLIENT_ATTRIBUTE_DICT.ID))
.where(CLIENT_ATTRIBUTE.CLIENT_ID.eq(clientId))
.asTable("S", ID_FIELD_NAME, VALUE_FIELD_NAME);
Field<String> ATTRIBUTE_VALUE_FIELD =
nvl(
EXISTING_ATTRIBUTES.field(VALUE_FIELD_NAME, String.class),
DICT_ATTRIBUTES.DEFAULT_VALUE
).as("ATTRIBUTE_VALUE");
Map<String,String> attributes =
dsl.select(DICT_ATTRIBUTES.CODE, ATTRIBUTE_VALUE_FIELD)
.from(DICT_ATTRIBUTES)
.leftJoin(EXISTING_ATTRIBUTES)
.on(DICT_ATTRIBUTES.ID
.eq(EXISTING_ATTRIBUTES.field(ID_FIELD_NAME, Long.class))
)
.fetchMap(DICT_ATTRIBUTES.CODE, ATTRIBUTE_VALUE_FIELD);
In logs I found a little bit changed SQL query generated by JOOK:
SELECT D.CODE,
NVL(S.VALUE, D.DEFAULT_VALUE) ATTRIBUTE_VALUE
FROM CLIENT_ATTRIBUTE_DICT D
LEFT OUTER JOIN (
(SELECT NULL ID, NULL VALUE FROM dual WHERE 1 = 0
)
UNION ALL
(SELECT CLIENT_ATTRIBUTE_DICT.ID,
CLIENT_ATTRIBUTE.VALUE
FROM CLIENT_ATTRIBUTE_DICT
JOIN CLIENT_ATTRIBUTE
ON CLIENT_ATTRIBUTE.ATTRIBUTE_ID = CLIENT_ATTRIBUTE_DICT.ID
WHERE CLIENT_ATTRIBUTE.CLIENT_ID = 141
)
) S ON D.ID = S.ID;
Any ideas how to improve my workaround are appreciated.

Error: subquery Returns more than one row

Hi I have been trying to select more than one rows by calling the procedure through CallableStatement. While I am trying to populate the result set to the combo box the code returns the error as follows.
Java Error:
java.sql.SQLException: Subquery returns more than 1 row
Stored Procedure :
CREATE DEFINER=`user_name`#`%` PROCEDURE `GET_USER_PROFILE`(
IN p_user_id VARCHAR(150),
IN p_role VARCHAR(150),
OUT p_user_data VARCHAR(200),
OUT p_city VARCHAR(150),
OUT p_state VARCHAR(150),
OUT p_country VARCHAR(150),
OUT q_Msg VARCHAR(150))
BEGIN
DECLARE available INT DEFAULT 0;
SET p_city = (SELECT CITY FROM countries GROUP BY CITY);
SET p_state = (SELECT STATE FROM countries GROUP BY STATE);
SET p_country = (SELECT COUNTRY FROM countries GROUP BY COUNTRY);
SELECT COUNT(EMAIL) INTO available FROM STAFF_PROFILE WHERE EMAIL = p_user_id AND ROLE = p_role;
IF(available=1) THEN
SET p_user_data = (SELECT * FROM STAFF_PROFILE WHERE EMAIL = p_user_id AND ROLE = p_role );
else
SET q_Msg = 'USER_LOGGED_FIRST';
END IF;
END
#DaveHowes and #Ilya are correct, the issue is with your SQL statement.
Lets say in your Countries table consists of the following:
city state country
'New York' 'New York' 'USA'
'Los Angeles' 'California' 'USA'
'Chicago' 'Illinois' 'USA'
'Ottawa' '' 'Canada'
Now, if we take your sub queries from your example:
SELECT city FROM countries GROUP BY city
would return:
city
'New York'
'Los Angeles'
'Chicago'
'Ottawa'
You're trying to assign a multiple results to a varchar hence you get the exception "Subquery returns more than 1 row".

Categories

Resources