I have a query in DB2 that I need intersection on.
SELECT * FROM records where id = 1
intersect
SELECT * FROM records where id = 2
Now this size of ids can grow dynamically, do we have anything in DB2 that can take the list of ids as the parameter? something like
intersect in (1,2,3,4,5) so that it may consider intersection on of result set on these ids using a single query?
You can create a TABLE with the ID LIST, and then create a procedure to create dinamically the SQL Statement to Execute.
Something like this:
CREATE PROCEDURE
YOUR_PROCEDURE
(
)
LANGUAGE SQL
SPECIFIC YOUR_PROCEDURE
NOT DETERMINISTIC
MODIFIES SQL DATA
CALLED ON NULL INPUT
INHERIT SPECIAL REGISTERS
SET OPTION
ALWBLK = *ALLREAD ,
ALWCPYDTA = *YES ,
COMMIT = *NONE ,
CLOSQLCSR = *ENDMOD ,
DECRESULT = (31, 31, 00) ,
DFTRDBCOL = *NONE ,
DLYPRP = *NO ,
DYNDFTCOL = *NO ,
DYNUSRPRF = *USER ,
SRTSEQ = *HEX ,
OUTPUT = *PRINT,
DBGVIEW = *SOURCE
BEGIN
DECLARE LAST_ELEMENT SMALLINT DEFAULT 0 ;
DECLARE FIRST_ELEMENT SMALLINT DEFAULT 1 ;
DECLARE STMT VARCHAR ( 5000 ) ;
DECLARE ID_CODE_TO_USE NUMERIC (9 , 0) ;
DECLARE ID_LIST CURSOR FOR
SELECT
ID_CODE
FROM
YOUR_TEMPORARY_TABLE ;
DECLARE CONTINUE HANDLER FOR NOT FOUND SET LAST_ELEMENT = -1 ;
OPEN ID_LIST ;
SET STMT = '' ;
SET FIRST_ELEMENT = 1 ;
FETCH_ID_LIST:
LOOP
FETCH ID_LIST
INTO
ID_CODE_TO_USE ;
-------
IF LAST_ELEMENT = -1 THEN
LEAVE FETCH_ID_LIST ;
END IF ;
-------
IF FIRST_ELEMENT= 1 THEN
SET STMT = 'SELECT * FROM RECORD WHERE ID = ' CONCAT CHAR(ID_CODE_TO_USE)
SET FIRST_ELEMENT = 0 ;
END IF ;
-------
SET STMT = 'INTERSECT SELECT * FROM RECORD WHERE ID = ' CONCAT CHAR(ID_CODE_TO_USE)
END LOOP FETCH_ID_LIST;
CLOSE ID_LIST ;
IF STMT <> '' THEN
EXECUTE IMMEDIATE STMT ;
END IF ;
END ;
Run this as is:
with ids (id) as
(
select id
from xmltable
(
'for $id in tokenize($s, ",") return <i>{string($id)}</i>'
-- the following string of IDs may be passed as a parameter
passing '1,2,3' as "s"
columns id int path 'if (. castable as xs:integer) then xs:integer(.) else ()'
)
)
, tab (id, c1, c2) as
(
values
(1, 1, 1)
, (1, 1, 1)
, (2, 1, 1)
, (2, 1, 1)
, (3, 1, 1)
, (1, 2, 2)
, (2, 2, 2)
)
select t.c1, t.c2
from tab t
join ids i on i.id=t.id
group by t.c1, t.c2
having count(distinct t.id) = (select count(1) from ids);
C1 C2
-- --
1 1
If this is not you want to have, then provide some example with source data and the exact result desired.
Related
DECLARE #source as NVARCHAR(MAX) = (SELECT md.[source] FROM sports_service.meet.meet_detail md WHERE md.meet_id = #{meetId})
WHERE reg.is_waitlist = 0 AND reg.cancelled_dt IS NULL
AND NOT EXISTS (
SELECT 1 FROM sports_service.meet.meet_invitations i
WHERE i.meet_id = pmm.meet_id AND i.sports_person_id = reg.sports_person_id)
IF #source != 'MANUAL'
AND EXISTS (
SELECT 1
FROM sports_service.meet.session session
INNER JOIN sports_service.meet.event event ON event.session_id = session.id
WHERE pmm.meet_id = session.meet_id
AND sports_service.dbo.fnGetMeetAge(p.birth_dt, detail.age_up_date, detail.id_format) <![CDATA[>=]]> event.low_age
AND sports_service.dbo.fnGetMeetAge(p.birth_dt, detail.age_up_date, detail.id_format) <![CDATA[<=]]> event.high_age
AND (event.eligible_gender IS NULL OR event.eligible_gender = p.gender))
If you want to check some condition (AND EXISTS ... clause) only if some other condition holds (source is MANUAL) and you want to do that in one query instead of:
WHERE (...some condition ...) AND (.. some other condition ... )
IF #source != 'MANUAL'
AND EXISTS (
SELECT 1 ... )
which is not supported as it is not a correct sql syntax you can do:
WHERE (...some condition ...) AND (.. some other condition ... )
AND (
(
(SELECT md.[source]
FROM sports_service.meet.meet_detail md
WHERE md.meet_id = #{meetId}
) = 'MANUAL'
)
OR EXISTS (
SELECT 1
FROM ...
)
)
You need to check if the performance of the resulting query would be satisfactory.
It would be probably better to execute the query that gets the source first and then execute either query with the additional condition or without it depending on the source value. To do that you can generate the query in mybatis using dynamic SQL (including or excluding this additional condition).
As you can't use If inside expression, you can write your sql like this:
DECLARE #source as NVARCHAR(MAX) = (SELECT md.[source] FROM sports_service.meet.meet_detail md WHERE md.meet_id = #{meetId})
WHERE reg.is_waitlist = 0 AND reg.cancelled_dt IS NULL
AND NOT EXISTS (
SELECT
1
FROM
sports_service.meet.meet_invitations i
WHERE
i.meet_id = pmm.meet_id
AND i.sports_person_id = reg.sports_person_id)
AND ( #source <> 'MANUAL'
AND
EXISTS ( SELECT 1 FROM
sports_service.meet.session session
INNER JOIN sports_service.meet.event event ON
event.session_id = session.id
WHERE
pmm.meet_id = session.meet_id
AND sports_service.dbo.fnGetMeetAge(p.birth_dt,
detail.age_up_date,
detail.id_format) >= event.low_age
AND sports_service.dbo.fnGetMeetAge(p.birth_dt,
detail.age_up_date,
detail.id_format) <= event.high_age
AND (event.eligible_gender IS NULL
OR event.eligible_gender = p.gender)))
You can't use IF inside expression.
I am developing a web application (java-maven project) and there is a problem with the query I use. The UI of my web app has 2 search fields: PTT and ID.
The user needs to fill at least one of the fields to make a search. So both fields are nullable but not at the same time.
Before, I had only one field: PTR and it was showing a result array of size 52. (also getting the same number if
I execute select * from users where ptr='smthing' ). After that I added ID field and updated my query as below:
I execute this query in my webservice:
String query= "SELECT t.ptr, t.id ";
query+= "FROM users t ";
query+= "WHERE t.ptr = COALESCE(?, t.ptr) AND " ;
query+= "t.id = COALESCE(?, t.id) ";
and set the fields with the help of Prepared Statement.
Now if the ptr field is filled, but id field is left blank (this can be null or empty string) on the UI and user makes a search, result array size becomes 30. I compared with database
and it does not fetch the rows where ID is null. So coalesce is not what I need when both of its parameters (?, t.ptr) is null.
How can I fix this problem, any suggestions?
I think the logic you want is:
WHERE (t.ptr = ? OR ? IS NULL) AND
(t.id = ? OR ? IS NULL)
I would recommend using named parameters, so you don't have to pass them in twice.
Check this statement:
String query= "SELECT t.ptr, t.id ";
query+= "FROM users t ";
query+= "WHERE (t.ptr = ? OR 1 = ?)"
query+= " AND " ;
query+= "(t.id = ? OR 1 = ?)";
You see that for each of t.id and t.ptr there is a counterpart parameter. In total there will be 4 parameters.
You say that at least 1 of t.id or t.ptr has a valid value, so there are 2 cases:
[1] t.id and t.ptr both have valid values.
For both the counterpart parameters you pass 0 and the query becomes:
"SELECT t.ptr, t.id FROM users t WHERE (t.ptr = valueptr OR 1 = 0) AND (t.id = valueid OR 1 = 0)"
In the WHERE part:
t.ptr = valueptr OR 1 = 0 is equivalent to t.ptr = valueptr, and
t.id = valueid OR 1 = 0 is equivalent to t.id = valueid,
and the query finally becomes:
"SELECT t.ptr, t.id FROM users t WHERE t.ptr = valueptr AND t.id = valueid"
[2] from t.id or t.ptr only one has a valid value, let's say this is t.ptr.
For the counterpart of t.ptr you pass 0, for t.id you pass -1 (or any other non existing value) and for the counterpart of t.id you pass 1 and the query becomes:
"SELECT t.ptr, t.id FROM users t WHERE (t.ptr = valueptr OR 1 = 0) AND (t.id = -1 OR 1 = 1)"
In the WHERE part:
t.ptr = valueptr OR 1 = 0 is equivalent to t.ptr = valueptr, and
t.id = -1 OR 1 = 1 is equivalent to true because 1 = 1 is always true,
and the query finally becomes:
"SELECT t.ptr, t.id FROM users t WHERE (t.ptr = valueptr OR 1 = 0)"
equivalent to:
"SELECT t.ptr, t.id FROM users t WHERE (t.ptr = valueptr)"
(In the case where only t.id has a valid value then you pass an invalid value for t.ptr and 1 for its counterpart and for the counterpart if t.id you pass 0.)
Maybe it seems complicated but it's working and it can be extended for more than 2 columns.
Oracle has build function for this. But it's hard to understand how to use its.
lnnvl(a = b) = true if (a != b ) or ( a = null ) or (b = null)
in your case
WHERE lnnvl(t.ptr != ? ) AND lnnvl( t.id != ?)
LNNVL
This is my code where METHODARGDATATYPE_VARRAY is a varray at 5th column in my table and first column has auto generated sequence concatenated with 't'.
String arrayElements[] = { "Test3", "Test4" };
ArrayDescriptor desc = ArrayDescriptor.createDescriptor
("METHODARGDATATYPE_VARRAY", conn);
ARRAY newArray = new ARRAY(desc, conn, arrayElements);
String sql="insert into TestCaseIDDetails values (concat('t',TestCaseID_sequence.nextval),?,?,?,?)";
PreparedStatement ps =
conn.prepareStatement (sql);
ps.setString(2,testCaseIDandDetailsBean.getClass_name()) ;
ps.setString(3,testCaseIDandDetailsBean.getMethod_name()) ;
ps.setString(4,testCaseIDandDetailsBean.getMethodReplacement()) ;
((OraclePreparedStatement)ps).setARRAY (5, newArray);
ps.execute ();
Iam trying to execute this code but again Iam getting errors as follows:
java.sql.SQLException: Invalid column index
at oracle.jdbc.driver.DatabaseError.throwSqlException(DatabaseError.java:112)
at oracle.jdbc.driver.DatabaseError.throwSqlException(DatabaseError.java:146)
at oracle.jdbc.driver.DatabaseError.throwSqlException(DatabaseError.java:208)
at oracle.jdbc.driver.OraclePreparedStatement.setARRAYInternal(OraclePreparedStatement.java:5906)
at oracle.jdbc.driver.OraclePreparedStatement.setARRAY(OraclePreparedStatement.java:5898)
at implementation.TestCaseIDandDetailsDAOImpl.addTestCaseIDandDetails(TestCaseIDandDetailsDAOImpl.java:54)
at implementation.TestCaseIDandDetailsDAOImpl.main(TestCaseIDandDetailsDAOImpl.java:134)
my tablescripts are:
CREATE or replace TYPE METHODARGDATATYPE_VARRAY AS VARRAY(20) OF varchar2(30);
create table TestCaseIDDetails(
testcaseID varchar2(20) primary key,
classname varchar2(20) not null,
methodname varchar2(20) not null,
MethodReplacement char(2) check(MethodReplacement in ('y','n')),
MethodArgDataType METHODARGDATATYPE_VARRAY);
Create sequence TestCaseID_sequence minvalue 1 start with 1 increment by 1 ;
The indexes should correspond to the indexes of the question mark placeholders in the prepared statement, not to the column numbers in the table. The index of the first question mark is 1, the second is 2, and so on. Your indexes are all off by one, should be 1 2 3 4 instead of 2 3 4 5.
Change the following
ps.setString(2,testCaseIDandDetailsBean.getClass_name()) ;
ps.setString(3,testCaseIDandDetailsBean.getMethod_name()) ;
ps.setString(4,testCaseIDandDetailsBean.getMethodReplacement()) ;
((OraclePreparedStatement)ps).setARRAY (5, newArray);
to
ps.setString(1,testCaseIDandDetailsBean.getClass_name()) ;
ps.setString(2,testCaseIDandDetailsBean.getMethod_name()) ;
ps.setString(3,testCaseIDandDetailsBean.getMethodReplacement()) ;
((OraclePreparedStatement)ps).setARRAY (4, newArray);
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 ...
Given my code below:
Query q = em.createNativeQuery(sql.toString(), SearchDTO.class);
for (String k : parameters.keySet()) {
q.setParameter(k, parameters.get(k));
}
q.setFirstResult((criteria.getPage()-1) * criteria.getLimit());
q.setMaxResults(criteria.getLimit());
return q.getResultList();
Where page is > 1, the sql generated is incorrect:
WITH query AS (SELECT inner_query.*, ROW_NUMBER() OVER (ORDER BY CURRENT_TIMESTAMP) as hibernate_row_nr FROM ( select TOP(?) cc.company_id as page0_, cc.long_name as page1_, cc.reuters_org_id as page2_, ccdom.country_name as country_of_domicile, ccinc.country_name as country_of_incorporation, ccr.region_name as region, ci.industry_name as industry from zz_prp_common_company cc left join zz_prp_common_country ccdom on cc.country_of_domicile = ccdom.country_id left join zz_prp_common_region ccr on ccr.region_id = ccdom.region_id left join zz_prp_common_country ccinc on cc.country_of_domicile = ccinc.country_id left join zz_prp_common_industry ci on cc.industry_id = ci.industry_id where 1=1 order by cc.long_name ) inner_query ) SELECT page0_, page1_, page2_, country_of_domicile, country_of_incorporation, region, industry FROM query WHERE hibernate_row_nr >= ? AND hibernate_row_nr < ?
I cannot understand why it is replacing the alias to my columns to page0_, page1_ and page2_. Due to this the where page0_ replaced the company_id column alias, I am getting this error:
Caused by: com.microsoft.sqlserver.jdbc.SQLServerException: The column name company_id is not valid.
I am using MS SQL Server 2008 R2, and I have setup my hibernate dialect to use org.hibernate.dialect.SQLServer2008Dialect.
I have found the answer to this issue. The sql native query I was using did NOT have any alias on the first 3 columns (company_id, long_name and reuters_org_id). When I debugged the SQLServer2008Dialect, it lead to the method getProcessedSql():
public String getProcessedSql() {
StringBuilder sb = new StringBuilder( sql );
if ( sb.charAt( sb.length() - 1 ) == ';' ) {
sb.setLength( sb.length() - 1 );
}
if ( LimitHelper.hasFirstRow( selection ) ) {
final String selectClause = fillAliasInSelectClause( sb );
int orderByIndex = shallowIndexOfWord( sb, ORDER_BY, 0 );
if ( orderByIndex > 0 ) {
// ORDER BY requires using TOP.
addTopExpression( sb );
}
encloseWithOuterQuery( sb );
// Wrap the query within a with statement:
sb.insert( 0, "WITH query AS (" ).append( ") SELECT " ).append( selectClause ).append( " FROM query " );
sb.append( "WHERE __hibernate_row_nr__ >= ? AND __hibernate_row_nr__ < ?" );
}
else {
hasOffset = false;
addTopExpression( sb );
}
return sb.toString();
}
The private method that sets the page0_, page1_, and page2, is done by fillAliasInSelectClause, an excerpt that does this is:
// Inserting alias. It is unlikely that we would have to add alias, but just in case.
alias = StringHelper.generateAlias( "page", unique );
The solution that worked for me is to provide the column alias on the 3 columns that initially did not have any. So basically, you will need to put alias on all your columns.