SQL removing arithmatic from GROUP BY hibernate - java

I currently have a query that works fine in SQL, but I need it to run in Hibernate. It finds the number of records of 2 fields inside of a time window. However, a quick google search will tell you that you can't have an arithmetic expression in the GROUP BY clause in hibernate.
My table/SQL:
CREATE TABLE T (
Field_1 CHAR(4),
Field_2 CHAR(4),
Time_Stamp Date
);
--Populated
DECLARE #DATE1 DATE, #DATE2 DATE, #INTERVAL INT;
#DATE1 = TO_DATE('2013-01-01', 'yyyy-MM-dd');
#DATE2 = TO_DATE('2013-01-02', 'yyyy-MM-dd');
#INTERVAL = 15;
SELECT * FROM (
SELECT COUNT(Field_1) as field1,
COUNT(Field_2) as field2,
FLOOR( TO_NUMBER( TO_CHAR( Time_Stamp, 'hhmi' ) ) / #INTERVAL ) as timeInterval
FROM T
WHERE Time_Stamp BETWEEN #DATE1 AND #DATE2
GROUP BY FLOOR( TO_NUMBER( TO_CHAR( Time_Stamp, 'hhmi' ) ) / #INTERVAL )
) ORDER BY timeInterval ASC;
As you can see I can remove the ORDER BY so that there is no arithmetic in it using a subquery. Is it possible to do something similar in this instance with the GROUP BY clause or some other work around?
I have the query already written in hibernate, and can translate between the two fine, I only need the structure of the query so the arithmetic is outside of the group by funciton, SQL syntax is fine.

Perform your calculation in inner query then group by the result of it.
SELECT
COUNT(Field_1) as field1,
COUNT(Field_2) as field2,
timeInterval
FROM (
SELECT
Field_1,
Field_2,
FLOOR( TO_NUMBER( TO_CHAR( Time_Stamp, 'hhmi' ) ) / #INTERVAL ) as timeInterval
FROM T
WHERE Time_Stamp BETWEEN #DATE1 AND #DATE2
)
GROUP BY timeInterval
ORDER BY timeInterval ASC;

Related

Oracle nested select with defined rows

My query looks like this:
SELECT
nvl(dd,'TOTAL') "Subject",
SUM(cnt) "Count,
SUM(pct) AS "%"
FROM
(
SELECT
dd,
COUNT(1) cnt,
round(RATIO_TO_REPORT(COUNT(1) ) OVER() * 100,2) AS pct
FROM
student p,
student_subject a
WHERE
p.sId = a.sId
AND student_type IN (
'1',
'2'
)
AND dd IN (
'MATH',
'SCIENCE',
'HISTORY'
)
GROUP BY
dd
ORDER BY
1
)
GROUP BY
ROLLUP(dd)
ORDER BY
1;
My Output should look like this:
Subject Count %
MATH 33 23.2%
SCIENCE 24 11.46%
HISTORY 56 44.778%
TOTAL 113 85.4.2%
If a particular subject doesnt have data it should still provide the row with 0 values like below
Subject Count %
MATH 33 23.20%
SCIENCE 0 0.00%
HISTORY 56 44.77%
TOTAL 113 85.42%
What I am getting rightnow is below with no SCIENCE row which is not desired ,
Subject Count %
MATH 33 23.20%
HISTORY 56 44.77%
TOTAL 113 85.42%
What I did is I removed the dd IN clause "AND dd IN (
'MATH',
'SCIENCE',
'HISTORY'
)"
However I am not able to get to the another inner select to select the 3 subjects.
If i understand the datamodel correctly when a student is not enrolled to a subject an entry for the subject wouldn't exist in student_subject table, which means the missing subject is not present in the deficit table as well. Hence technically it is not possible to join these two tables and report for a column value that doesn't exist in either of them.
Now to solve this,i use WITH clause to create another table to hold all the desired subjects and perform an outer join with the result set retrieved.
I have tested this and it works perfectly. Complete solution(Oracle 18c) with table and Query can be found in DBFIDDLE URL https://dbfiddle.uk/?rdbms=oracle_18&fiddle=df73453d7fa4e0478e74fa509b20a411.
WITH some_data AS (
SELECT 'MATH' AS subj
FROM dual
UNION ALL
SELECT 'SCIENCE' AS subj
FROM dual
UNION ALL
SELECT 'HISTORY' AS subj
FROM dual
)
SELECT
nvl(subj,'TOTAL') "Subject",
nvl(SUM(cnt),0) "Count",
nvl(SUM(pct),0) AS "%"
FROM
(SELECT
dd,
COUNT(1) cnt,
round(RATIO_TO_REPORT(COUNT(1) ) OVER() * 100,2) AS pct
FROM
student p,
student_subject a
WHERE
p.sId = a.sId
AND student_type IN (
'1',
'2'
)
AND dd IN (
'MATH',
'SCIENCE',
'HISTORY'
)
GROUP BY
dd
ORDER BY
1
) tab, some_data
where tab.dd(+) = some_data.subj
GROUP BY
ROLLUP(subj)
ORDER BY
1;
You need to use the list of tables as the inner view and use left join as follows:
SELECT NVL(DD, 'TOTAL') "Subject",
SUM(CNT) "Count",
SUM(PCT) AS "%"
FROM (
SELECT DD,
COUNT(1) CNT,
ROUND(RATIO_TO_REPORT(COUNT(1)) OVER() * 100, 2) AS PCT
FROM (
SELECT 'MATH' AS SUB FROM DUAL UNION ALL
SELECT 'SCIENCE' AS SUB FROM DUAL UNION ALL
SELECT 'HISTORY' AS SUB FROM DUAL
) SUBJECTS
LEFT JOIN STUDENT_SUBJECT A
ON SUBJECTS.SUB = A.DD
LEFT JOIN STUDENT P
ON P.SID = A.SID
WHERE STUDENT_TYPE IN (
'1','2'
)
GROUP BY DD
ORDER BY 1
)
GROUP BY ROLLUP(DD)
ORDER BY 1;
You have to use case statement to make it 0, If any of the subject is null then default it to 0. Let me know if you require a query. Am suggesting the logic so that you can try yourself.

Optimizing SQL query with cte and variable inputs

I am trying to run the following SQL script using Java and am getting issues with no resultset from JDBCTemplate. I thought about reducing it using functions/stored procedures and would like some help with it:
SQL - first part:
SET NOCOUNT ON
IF OBJECT_ID('tempdb.dbo.#tempSearch', 'U') IS NOT NULL
DROP TABLE #tempSearch;
CREATE TABLE #tempSearch
(
ID INT,
Value VARCHAR(255)
)
INSERT INTO #tempSearch
VALUES (1, 'Variable1'), (2, 'Variabl2');
Second part:
WITH cte AS
(
SELECT
RoleID,
',' + REPLACE(REPLACE(GroupNames, ',', ',,'), ' ', '') + ',' GroupNames
FROM
UserGroup_Role_Mapping
), cte2 AS
(
SELECT
cte.RoleID,
REPLACE(cte.GroupNames, ',' + Value + ',', '') AS GroupNames,
s.ID, s.Value
FROM
cte
JOIN
#tempSearch s ON ID = 1
UNION ALL
SELECT
cte2.RoleID,
REPLACE(cte2.GroupNames, ',' + s.Value + ',', '') AS l,
s.ID, s.Value
FROM
cte2
JOIN
#tempSearch s ON s.ID = cte2.ID + 1
)
SELECT
a.Role, a.Sort_Order,
a.Parent, a.Parent_ID, a.Parent_URL,
a.Child, a.Child_ID,a.Child_URL
FROM
Config_View a
WHERE
a.Role IN (SELECT Name
FROM
(SELECT DISTINCT RoleID FROM cte2 WHERE LEN(GroupNames) = 0) tempRoles
JOIN
User_Role ON tempRoles.RoleID = User_Role.ID
)
DROP TABLE #tempSearch
I was thinking first part can be done in a stored procedure. I did read here (stored procedure with variable number of parameters) about making a table from a list of variables but am not sure how to do set those variables in a loop like i am doing from above (1,Variable1 etc.).
I think the second part can be by itself?
So my updated query might be:
Call stored procedure (variable1, ..., variablex);
SQL part 2?
If anyone can help that would be great!
It's possible to do this in two seperate batches, but only if you can ensure that the first batch runs in session scope, and not in a nested batch ( eg via sp_executesql ). Temp tables created in nested batches, like stored procedures or prepared statements are automatically destroyed at the end of the nested batch. So it depends on how you call it. My guess is that a PreparedStatement won't work.
The right way to do this is probably to use a stored procedure with a table-valued parameter, or a JSON (for SQL 2016+), or XML parameter and parses it in the stored procedure body. See https://learn.microsoft.com/en-us/sql/connect/jdbc/using-table-valued-parameters?view=sql-server-2017
You can also use a TSQL batch instead of a stored procedure and bind a Table-Valued Parameter, or a NVarchar(max) parameter containing JSON.
With a TVP you could simply use a batch like:
with s as (
select * from ? --bind a table-valued parameter here
), cte as (
select RoleID,','+replace(replace(GroupNames,',',',,'),' ','')+',' GroupNames from UserGroup_Role_Mapping
)
,cte2 as(
select cte.RoleID, replace(cte.GroupNames,','+Value+',','') as GroupNames, s.ID, s.Value
from cte
join s on ID=1
union all
select cte2.RoleID, replace(cte2.GroupNames,','+s.Value+',','') as l, s.ID ,s.Value
from cte2
join s on s.ID=cte2.ID+1
)
SELECT a.Role, a.Sort_Order, a.Parent, a.Parent_ID, a.Parent_URL, a.Child, a.Child_ID,a.Child_URL
FROM Config_View a
WHERE a.Role IN (
Select Name from (
Select distinct RoleID from cte2 where len(GroupNames)=0
) tempRoles
join User_Role
on tempRoles.RoleID = User_Role.ID
)
That would be the value of the string variable sql, and then call it something like:
SQLServerPreparedStatement pStmt = (SQLServerPreparedStatement) connection.prepareStatement(sql);
pStmt.setStructured(1, "dbo.CategoryTableType", sourceTVPObject);
ResultSet rs = stmt.executeQuery();

using JPA how to set parameter in native query when I have PIVOT

q = createQuery( " select * from ( ( SELECT * FROM ( SELECT dt.route_id , dt.amount , dc.card_type_id FROM DDRC_TRANS\n" +
"dt , DDRC_CARD dc WHERE ( dt.card_id = dc.id AND dt.insert_time >= ('02-JAN-2000 04:00:00 AM')\n" +
"AND dt.insert_time <= ('02-FEB-2050 04:00:00 AM') AND dt.route_id IN ( '1', '3' ) ) ) PIVOT\n" +
"( SUM(amount ) FOR card_type_id IN ( 1,2,3 ) ) ) )"
, clazz);
Ok Everything looks nice! I can run this query and it returns the result too!
But I have found one problem. What about If I need to set parameter? for instance like this
q = createQuery( " select * from ( ( SELECT * FROM ( SELECT dt.route_id , dt.amount , dc.card_type_id FROM DDRC_TRANS\n" +
"dt , DDRC_CARD dc WHERE ( dt.card_id = dc.id AND dt.insert_time >= ('02-JAN-2000 04:00:00 AM')\n" +
"AND dt.insert_time <= ('02-FEB-2050 04:00:00 AM') AND dt.route_id IN ( '1', '3' ) ) ) PIVOT\n" +
"( SUM(amount ) FOR card_type_id IN ( :param ) ) ) )"
, clazz);
List<String> valueList = Arrays.asList("1,2,3");
q.setParameter("param", valueList);
now here I have that type of error:
java.sql.SQLException: ORA-56900: bind variable is not supported inside pivot|unpivot operation
why? how can I fix this? Is this hibernate BUG?
The problems looks like ojdbc does not allow to bind variables within PIVOT clause, because it is not supported.
The first query you posted set the values explicit way. In the second, you are indirectly using preparedStatement through the JPA provider.
This is why you finally get a query statement that looks like follows which is not supported (note the parametrized values of IN clause)
select * .... PIVOT ( SUM(amount ) FOR card_type_id IN ( ? ) ) ) )
Although it won't work anyway, you must be aware that you are not setting a list of values. In the query above you just set one parameter.
For you native query (assuming that you are creating with EntityManager.createNativeQuery inside your createQuery method),
q = createQuery( "select * .... card_type_id IN ( :param ) ");
List<String> valueList = Arrays.asList("1,2,3");
q.setParameter("param", valueList);
what you are finally generating depends on how the JPA provider replace the :param by the valueList but it will be just one parameter replacement not a list. For example, Hibernate will generate a preparedStatement like this
select * .... card_type_id IN ( ? )
that finally takes next form after set set the valueList parameter,
select * .... card_type_id IN ( [1,2,3] )
What you really should achieve is a preparedStatement like this (note many parameters ?)
select * .... card_type_id IN ( ?,?,? )
For do that the valueList should be as follows:
List<String> valueList = Arrays.asList("1","2","3");
Then the statement will take above prepared from and finally:
select * .... card_type_id IN ( '1','2','3' )
You CANNOT pass a List in to JDBC as a parameter.
You have to pass in the individual values and have multiple "?" in the statement. Beyond that, you are down to the rules of the specific JDBC driver you are using and where it accepts parameters to be used. All JDBC drivers restrict where parameters can be applied in one way or another ...

ORA-00936, Java and SQL

I am aware that I have a missing expression, but I am unaware where it is?
this is the String I send to the parser:
#macro(intervals $startDate $endDate) SELECT #bind($startDate 'TIMESTAMP') - 1 + LEVEL interv, 1+EXTRACT(DAY FROM #bind($endDate 'TIMESTAMP')-#bind($startDate 'TIMESTAMP')) days_in_period FROM dual CONNECT BY LEVEL <= 1+EXTRACT(DAY FROM #bind($endDate 'TIMESTAMP')- #bind($startDate 'TIMESTAMP'))#end
#macro(tagSeries $dso $includeSetInfo) SELECT DISTINCT t.tag_group #if($includeSetInfo) , t.set_id tag_set_id, t.name tag_set_name #end , tsi.tag_id FROM TRANSPONDER_SET t, TRANSPONDER_SET_ID TSI WHERE t.set_id IN ( #if($TAG_SET_ID) #bind($TAG_SET_ID 'VARCHAR') #else #foreach($transponderSet in $dso.getTransponderSet() ) #if($velocityCount > 1) , #end #bind($transponderSet.getSetId() 'VARCHAR') #end #end ) AND t.set_id = tsi.set_id#end
#set( $data_opt = $data_selection)
WITH
intervals AS (#intervals($PERIOD_START_DATE $PERIOD_END_DATE)),
tags AS (#tagSeries($data_opt false)),
ag_regions AS ( SELECT /* materialize */
node_id, CONNECT_BY_ROOT ag2.name region_name
FROM area_group ag2
START WITH ag2.parent_node_id = 1
CONNECT BY NOCYCLE PRIOR ag2.node_id = ag2.parent_node_id),
stations AS (SELECT distinct ag.abbreviation,
ag.node_id,
rp.station_key,
ag.sorting
, CASE s.equipment_type_key WHEN 2 THEN s.station_name ELSE NULL END pk_number
,(SELECT region_name
FROM ag_regions
WHERE node_id = ag.node_id) region
FROM reg_point_view rp, area_group ag, station s
WHERE rp.area_group_node_id = ag.node_id
#if($station_region_options.isStations())
AND rp.station_key IN (#bind($station_region_options.getEntries() 'NUMERIC'))
#elseif($station_region_options.isPartitionNodes())
AND ag.node_id IN (#bind($station_region_options.getEntries() 'NUMERIC'))
#elseif($station_region_options.isTreeNodes())
AND ag.node_id IN (SELECT node_id
FROM area_group
START WITH NODE_ID IN (#bind($station_region_options.getEntries() 'NUMERIC'))
CONNECT BY PRIOR NODE_ID = PARENT_NODE_ID
AND NODE_ID <> PARENT_NODE_ID)
#end
AND (
rp.ant_ikraft_dato <= #bind($PERIOD_END_DATE 'TIMESTAMP') -- period end date
and (rp.ant_udlobs_dato is null
or rp.ant_udlobs_dato >= #bind($PERIOD_START_DATE 'TIMESTAMP') -- period start date
)
)
AND rp.station_key = s.station_key
),
pre_aggregation AS
(SELECT TRUNC(vd.reg_time) day, s.abbreviation, s.node_id, s.pk_number, s.region, count(*) regs, s.sorting
FROM stations s, validated_data vd, tags t
WHERE vd.station_key = s.station_key
AND vd.tag_id = t.tag_id
AND vd.tag_group = t.tag_group
AND vd.reg_time BETWEEN #bind($PERIOD_START_DATE 'TIMESTAMP') AND #bind($PERIOD_END_DATE 'TIMESTAMP')
GROUP BY TRUNC(vd.reg_time), s.abbreviation, s.node_id,s.pk_number, s.region, s.sorting)
SELECT abbreviation, node_id, pk_number, region, 100*sum(days_read)/(select count(*) from intervals) utilization from (
SELECT abbreviation, node_id, pk_number, region, sorting, sum(decode(regs,0,0,1)) days_read FROM pre_aggregation
GROUP BY abbreviation, node_id, pk_number, region, sorting, regs
UNION ALL SELECT DISTINCT abbreviation, node_id, pk_number, region, sorting,0 days_read FROM stations)
GROUP BY abbreviation, pk_number, region, node_id, sorting
-- Just to test
ORDER BY utilization desc, sorting
can you find it ? my cmd writes the following
ORDER BY utilization desc sorting]; nested exception is java.sql.SQLSyntaxErrorException: ORA-00936: missing expression
from my cmd I get the following:
Class: class org.springframework.jdbc.BadSqlGrammarException
Message: StatementCallback; bad SQL grammar [WITH
intervals AS ( SELECT to_timestamp('01-01-2013 00:00:00','dd-mm-yyyy hh24:mi:ss') - 1 + LEVEL interv, 1+EXTRACT(DAY FROM to_timestamp('30-01-2013 23:59:59','dd-mm-yyyy hh24:mi:ss')-to_timestamp('01-01-2013 00:00:00','dd-mm-yyyy hh24:mi:ss')) days_in_period FROM dual CONNECT BY LEVEL <= 1+EXTRACT(DAY FROM to_timestamp('30-01-2013 23:59:59','dd-mm-yyyy hh24:mi:ss')- to_timestamp('01-01-2013 00:00:00','dd-mm-yyyy hh24:mi:ss'))),
tags AS ( SELECT DISTINCT t.tag_group , tsi.tag_id FROM TRANSPONDER_SET t, TRANSPONDER_SET_ID TSI WHERE t.set_id IN ( ) AND t.set_id = tsi.set_id),
ag_regions AS ( SELECT /* materialize */
node_id, CONNECT_BY_ROOT ag2.name region_name
FROM area_group ag2
START WITH ag2.parent_node_id = 1
CONNECT BY NOCYCLE PRIOR ag2.node_id = ag2.parent_node_id),
stations AS (SELECT distinct ag.abbreviation,
ag.node_id,
rp.station_key,
ag.sorting
, CASE s.equipment_type_key WHEN 2 THEN s.station_name ELSE NULL END pk_number
,(SELECT region_name
FROM ag_regions
WHERE node_id = ag.node_id) region
FROM reg_point_view rp, area_group ag, station s
WHERE rp.area_group_node_id = ag.node_id
AND rp.station_key IN (1549)
AND (
rp.ant_ikraft_dato <= to_timestamp('30-01-2013 23:59:59','dd-mm-yyyy hh24:mi:ss') -- period end date
and (rp.ant_udlobs_dato is null
or rp.ant_udlobs_dato >= to_timestamp('01-01-2013 00:00:00','dd-mm-yyyy hh24:mi:ss') -- period start date
)
)
AND rp.station_key = s.station_key
),
pre_aggregation AS
(SELECT TRUNC(vd.reg_time) day, s.abbreviation, s.node_id, s.pk_number, s.region, count(*) regs, s.sorting
FROM stations s, validated_data vd, tags t
WHERE vd.station_key = s.station_key
AND vd.tag_id = t.tag_id
AND vd.tag_group = t.tag_group
AND vd.reg_time BETWEEN to_timestamp('01-01-2013 00:00:00','dd-mm-yyyy hh24:mi:ss') AND to_timestamp('30-01-2013 23:59:59','dd-mm-yyyy hh24:mi:ss') GROUP BY TRUNC(vd.reg_time), s.abbreviation, s.node_id,s.pk_number, s.region, s.sorting)
SELECT abbreviation, node_id, pk_number, region, 100*sum(days_read)/(select count(*) from intervals) utilization from (
SELECT abbreviation, node_id, pk_number, region, sorting, sum(decode(regs,0,0,1)) days_read FROM pre_aggregation
GROUP BY abbreviation, node_id, pk_number, region, sorting, regs
UNION ALL SELECT DISTINCT abbreviation, node_id, pk_number, region, sorting,0 days_read FROM stations)
GROUP BY abbreviation, pk_number, region, node_id, sorting
--Dette er lige lidt test kode!
ORDER BY utilization desc sorting]; nested exception is java.sql.SQLSyntaxErrorException: ORA-00936: missing expression
I have had a look at : java.sql.SQLException: ORA-00936: missing expression & ORA-00936: missing expression oracle
But I can't seem to figurer out if there is a comma to much or there is a spelling gone wrong.
This part:
SELECT DISTINCT t.tag_group, tsi.tag_id
FROM TRANSPONDER_SET t, TRANSPONDER_SET_ID TSI
WHERE t.set_id IN ( ) AND t.set_id = tsi.set_id
you have no values for t.set_id specified, IN () is not valid.

Postgresql and Java passing one value and using it in multiple places without cross join

I have a query I am writing in postgresql. I am calling this from a java jdbc library using a prepared statement. I have a date field that I want to pass in to see if any of the relating tables have updated since I last checked. However I don't know how to have the date fill multiple spots. Here is what works.
WHERE p.last_updated_time > '2015-02-06 11:21:32.064'
OR p.p_id IN (
SELECT DISTINCT con.p_id
FROM enriched.p_con con
WHERE con.last_updated_time > '2015-02-06 11:21:32.064'
)
OR p.p_id IN (
SELECT DISTINCT pi.p_id
FROM enriched.p_ident pi
WHERE pi.last_updated_time > '2015-02-06 11:21:32.064'
)
OR p.p_id IN (
SELECT DISTINCT pp.p_id
FROM enriched.p_p pp
WHERE pp.last_updated_time > '2015-02-06 11:21:32.064'
)
OR p.p_id IN (
SELECT DISTINCT prov.p_id
FROM enriched.p_prov prov
WHERE prov.last_updated_time > '2015-02-06 11:21:32.064'
)
However when I change it to use the prepared statement to the following I get an error.
WHERE p.last_updated_time > ?
OR p.p_id IN (
SELECT DISTINCT con.p_id
FROM enriched.p_con con
WHERE con.last_updated_time > ?
)
OR p.p_id IN (
SELECT DISTINCT pi.p_id
FROM enriched.p_ident pi
WHERE pi.last_updated_time > ?
)
OR p.p_id IN (
SELECT DISTINCT pp.p_id
FROM enriched.p_p pp
WHERE pp.last_updated_time > ?
)
OR p.p_id IN (
SELECT DISTINCT prov.p_id
FROM enriched.p_prov prov
WHERE prov.last_updated_time > ?
)
I tried using a cross join which works but takes my query time from 45 seconds to several days.
CROSS JOIN (SELECT TO_DATE(?, 'YYYY-MM-DD HH:MI:SS.MS') last_crawl_date) ld
Any thoughts? This seems like a simple problem to fix but I just can't seem to see it.
You have 5 ? in your prepared statement SQL, so you'll need to set the date five times.
statement.setDate(1, yourDate);
statement.setDate(2, yourDate);
statement.setDate(3, yourDate);
statement.setDate(4, yourDate);
statement.setDate(5, yourDate);

Categories