Compare date if not null - java

I need to find all the record with create date > X. X is a sql.Timestamp and might be null, in which case I want to just return all the records. So I tried: (createdAfter is Timestamp)
SELECT *
FROM sample AS s
WHERE s.isActive
AND (:createdAfter ISNULL OR s.insert_time > :createdAfter)
But all I'm getting is
org.postgresql.util.PSQLException: ERROR: could not determine data type of parameter $1
However, if I'll do the same query where I'm checking for an arbitrary int to be null:
SELECT *
FROM trades
WHERE (:sInt ISNULL OR trades.insert_time > :createdAfter )
Then it works. What's wrong?

There is no simple solution if you want to stick with native queries like that. The null value is converted to a bytea value. See for example this and this.
That value is quite hard to be casted or compared to a timestamp value.
The problem is not so much with the first comparison it would be handled by using coalesce, like:
COALESCE(:createdAfter) ISNULL
because there is no comparison beteween actual values and the data type does not matter. But the comparison
sometimestamp::timestamp > null::bytea (casts just to show the actual types so not working)
would need more logic behind maybe procedure & exception handling or so, not sure.
So if JPQL or CriteriaQueries are not possible for you have only bad options:
contruct the query by setting params by string concatenation or so (NOT! and not sure if realy works)
use PreparedStatement queries, more code & effort
also if using Hibernate, using session api like in this answer

You can try using the pg_typeof function, which returns a text string, and using a CASE statement to force which comparisons are made (otherwise there's no guarantee that postgres will short-circuit the OR in the correct order). You can then force the correct conversion by converting to text and then back to timestamp, which is inelegant but should be effective.
SELECT *
FROM sample AS s
WHERE s.isActive
AND
CASE WHEN pg_typeof( :createdAfter ) = 'bytea' THEN TRUE
WHEN s.insert_time > ( ( :createdAfter )::text)::timestamp THEN TRUE
ELSE FALSE
END

Related

How to query a boolean column using an integer with Postgres?

I post a similar question previously, but have opened another question to be more specific as the previous one gave me a solution but I have now encountered another problem.
We have an existing Oracle database that had boolean columns defined like so
CREATE TABLE MY_TABLE
(
SOME_TABLE_COLUMN NUMBER(1) DEFAULT 0 NOT NULL,
etc..
And with the corresponding java field defined like private boolean someTableColumn; I've come to understand this is because Oracle does not have a Boolean datatype, but under the hood does the conversion from boolean to integer when inserting data, and the reverse when retriving data.
This has caused an issue when I have been working on migrating our database from Oracle to Postgres. Thanks to answers on my previous question, I have migrated the column type from NUMBER(1) to BOOLEAN. This has solved the issue with inserting data. However, our codebase uses JDBCTemplate and we unfortunately have hundereds of hardcoded queries in the code that make queries like SELECT * FROM MY_TABLE WHERE TABLE_COLUMN=1.
When these run against the Postgres DB, I get the following error ERROR: operator does not exist: boolean = integer. We have a requirement to have backwards compatability with Oracle, so I can't simply update these queries to replace 1 and 0 with TRUE and FALSE respectively.
Is there a way I can configure Postgres so it can do a conversion behind the scenes to resolve this? I have looked at casts but I don't really understand the documentation and the examples given don't seem to match my use case. Any help is appreciated.
Can you try to use '0' and '1' instead of 0 and 1 in your requests ?
I used to work on apps compliant with both Oracle and Postgresql using this syntax. Apps were using JPA but can say with certainty that we were also using this syntax with nativeQuery = true.
Note: I would have posted this as a comment but I don't have the required reputation to do so, hence the post as an answer
Update:
Duh! Brain Freeze. On later thought there is a way to get this conversion in both directions. Process via a View.
Steps:
Rename your table.
Create a view having the same name as the old table. In this view
translate the boolean column to the appropriate integer.
Create an trigger function and an instead of trigger on the view for insert/update dml. In the trigger function translate the column value to boolean as appropriate.
See revised demo.
alter table testb rename to testb_tab;
create or replace view testb (id, name, is_ok)
as
select i,
, name
, is_ok::int
from testb_tab;
create or replace
function testb_act_dml()
returns trigger
language plpgsql
as $$
begin
if tg_op = 'INSERT' then
insert into testb_tab(name,is_ok)
values (new.name, new.is_ok::boolean) ;
else
update testb_tab
set name = new.name
, is_ok = new.is_ok::boolean
where id = old.id;
end if;
return new;
end;
$$;
create trigger testb_biuri -- before insert update row instead of
instead of insert or update on testb
for each row execute function testb_act_dml();
Finally, there is another option which probably has the lease work. Do not change the column description to Boolean. Either leave it as an integer or define it as a smallint. Either way a check constraint may come in useful. So something along the line of:
create table tests( id int generated always as identity primary key
, name text
, is_ok smallint
, constraint is_ok_ck
check ( is_ok in (0,1) or is_ok is null)
);
This is one of the issues I have with the concept of "database independence". It simply does not exist. Vendors implementation often simply vary too much. In this case Postures to the rescue, perhaps but also perhaps extreme: create you own create your own operators. Proceed with caution:
-- function to compare boolean = integer
create or replace
function"__bool=int"( b boolean, i int)
returns boolean
language sql
as $$
select (b=i::boolean);
$$;
-- create the Operator for boolean = integer
create operator = (
leftarg = boolean
, rightarg = int
, function = "__bool=int"
, commutator = =
);
The above will not allow your code: SELECT * FROM MY_TABLE WHERE TABLE_COLUMN=1 (see demo here).
However, this road may lead to unexpected twists, and lots of function/operator pairs. For example the above does not support SELECT * FROM MY_TABLE WHERE TABLE_COLUMN<>1. That requires another function/operator combination. Further I do not see a retrieval function that converts a boolean back to an integer. If you follow this path be sure to massively test your boolean-to-integer (integer-to-boolean) operations. It may just be better to just byte the bullet and updated those few queries (you did say hundreds not thousands) as #mlogario suggests.

Select count(*) returns a row even when I dont expect it

So I am querying a MySql database from my java application and I am trying to use a query,
Select count(*) from table where `NUMERIC`='1'
to count the rows from a database. When I run this query it works fine, and I get a 1 returned (I am using a test db with 12 records, Numeric has values 1-12 so this makes sense). However I wanted to try to break this and do some error handling. I changed my query to
Select count(*) from table where `Numeric`='1adjfa'
I expected this to return 0, however it still returns 1. In fact, as long as I have 1 at the beginning of the value it will work, if I change the value to just 'adjfa' than it returns 0. I have confirmed this through both my Java App and the MySQL workbench. Any ideas as to why this returns 1, even with the junk at the end of it?
Two different data types can not be compared. Instead one of the two needs to be cast/coerced to the same data type as the other.
In your case you're not doing the coercion, so the DB Engine is doing an implicit coercion.
Based on data-type-order-of-precedence, the database engine chooses the string to be coerced to a numeric.
The value '1adjfa' therefore becomes a 1, and then your comparison is being made.
This results is your query effectively being:
Select count(*) from table where `Numeric` = 1
You should either not be comparing numerics and strings, or do the coercion yourself, for example...
Select count(*) from table where CAST(`Numeric` AS VARCHAR(32)) = '1adjfa'
In terms of breaking the query, I'm hoping that in your application you're actually using parameterised queries. This will allow you to define the data-type of the parameter, and your application should throw the error if the wrong data-type is supplied.
Numeric has a number data type. To make the comparision to 1adjfa the DB engine tries to convert it also to a number which results in 1 and the rest gets cut off.

How to process 0000-00-00 date in jdbc MySQL query

I'm getting this exception:
java.sql.SQLException: Value '0000-00-00' can not be represented as java.sql.Date
Originating from this code:
Date internalDate = rs.getDate(idx++);
Where rs is a ResultSet.
So this is fine to me - I know there are zero'ed dates in the database and I need to be able to read these and convert them into an appropriate (probably null) data in my downstream data structures. The problem is I don't know how to retrieve it and get a "soft" error. I thought about wrapping this line in a try/catch for SQLException but understand this will break validity of the ResultSet.
Is it possible to read this value in another way without throwing a SQLException?
I like #a_horse_with_no_name's answer, however if you don't have control over the connection, you could change the query to return a null instead:
select
...
case when my_date_col = '0000-00-00' then null else my_date_col end as my_date_col,
...
or the slightly more terse, but mysql-only, option:
if(my_date_col = '0000-00-00', null, my_date_col) as my_date_col
Also, caution is advised changing the entire application's JDBC behaviour as you may break code that relies on such dates being returned - perhaps they use rs.getString(i) instead. You would have to regression test all other queries to be sure.
You need to tell the JDBC driver to convert them to NULL. This is done by passing a connection property name zeroDateTimeBehavior with the value convertToNull
For more details see the manual: http://dev.mysql.com/doc/refman/4.1/en/connector-j-installing-upgrading.html
I suggest using rs.getString() and parsing the String yourself. If it is all 0s, then use a null Date reference. If not, create an appropriate Date object.
Set the column Allow Null, and Set Default Value at Column : 1900-01-01 was my best solution...
Convert this String result to a date by using "simpleDateFormat.parse(yourValue)"
Get day or year or month value from that date ( whichever you want)
This will return int value
Check if it is 0
If it is zero set date equal to null.

How to create a mutliple search SQL statement where all the parameters are optional?

I would like to know if there is any smart way of making a SQL statement for a search engine where there are 5 optional parameters. All parameters can be used or only one of them, or a mix of any of them.. This makes up to 3000+ different combinations.
The statement needs to be prepared to avoid SQL injections.
I've looked at this post, but it dosent quite cut.
What I'm looking for is something like,
String sql =SELECT * FROM table WHERE (optional1)=? AND (optional2)=? AND (optional3)=? AND (optional4)=? AND (optional5)=?
prepared.setString(1, optional1)
and so on...
Use your java code to add the options to the where clause based on the presence of your arguments (their length or existence, whichever). That way if the optional parameter is not needed, it won't even be part of your SQL expression. Simple.
#a1ex07 has given the answer for doing this as a single query. Using NULLs and checking for them in each condition.
WHERE
table.x = CASE WHEN #x IS NULL THEN table.x ELSE #x END
or...
WHERE
(#x IS NULL OR table.x = #x)
or...
WHERE
table.x = COALESCE(#x, table.x)
etc, etc.
There is one warning, however; As convenient as it is to make one query to do all of this, All of these answers are sub-optimal. Often they're horednous.
When you write ONE query, only ONE execution plan is created. And that ONE execution plan must be suitable for ALL possible combinations of values. But that fixes which indexes are searched, what order they're searched, etc. It yields the least worst plan for a one-size-fits-all query.
Instead, you're better adding the conditions as necessary. You still parameterise them, but you don't include a condition if you know the parameter is NULL.
This is a good link explaining it further, it's for MS SQL Server specifically but it's generally applicatble to any RDBMS that caches the plans after it compiles the SQL.
http://www.sommarskog.se/dyn-search.html
I believe it should work (haven't tested though)
SELECT * FROM table
WHERE
field1 = CASE
WHEN ? IS NULL THEN field1
ELSE ?
END AND
field2 = CASE
WHEN ? IS NULL THEN field2
ELSE ?
END AND .... etc
//java code
if ([optional1 is required])
{
prepared.setString(1, optional1) ;
prepared.setString(2, optional1) ;
}
else
{
prepared.setNull(1, java.sql.Types.VARCHAR) ;
prepared.setNull(2, java.sql.Types.VARCHAR) ;
}
etc.

How do I change this query to make it return records that meet the `minPrice` condition even if the `keywords` value is null?

I don't know if this is a problem that is specific to Google App Engine for Java, but if the value set as the keywords parameter is a null String, then nothing is returned from the query, even if a minPrice is set.
How do I change this query to make it return records that meet the minPrice condition even if the keywords value is null? Ideally I would somehow use the same query for both conditions without creating separate queries based on a null String condition.
Query qry = entityManager.createQuery("SELECT p FROM Test p
WHERE keywords = :keywords and price >= :minPrice");
qry.setParameter("keywords", keywords);
qry.setParameter("minPrice", Integer.parseInt(minPrice));
It's the way the GAE datastore works (most relational databases work that way too, btw!): nulls are not equal to anything, so the keywords = :keywords part of your query is false on records with null keywords -- since that part is false, so is the and, of course.
You'll need two queries, one for keywords = :keywords and one for the "is null" check, and use their two disjoint result sets (Python GAE simulates an "IN" operator in app-level code, which I believe Java GAE doesn't, but since the sets are disjoint in this case there's really no mystery or difficulty to it anyway;-).
Edit: it's a simulated IN (which would be usable here) in Python, not OR; the Java equivalent of that app-level-simulated IN is actually contains.

Categories