Delete a row where its primary key is closest to X - java

I am attempting to delete a row, whose primary key(column name=version) for example is 4.002000000000001. So I give say the statement:
DELETE FROM tbl WHERE version=4.002;
Now right now this wont work, because theres no row that has a version equal to 4.002, only a row with a version equal to 4.002000000000001. Please dont ask why I dont store the version as 4.002 in the DB because I am trying to do so by making the column type DECIMAL(5,3) but it still stores a double from Java as 4.002000000000001. And please dont ask why I dont say "...WHERE version=4.002000000000001;" because right now I have no control over how java formats it doubles(even using DecimalFormat class) when I create a statement using JDBC.
So is there a way to round down(floor function) in SQLite3? So then I can say "delete the row whose FLOOR(version) is equal to 4.002?
I am a C++ programmer (using Java to query an SQL database, please dont ask why :P) who is not proficient with complex SQL queries, so would this be correct for what I want to do:
DELETE FROM tbl WHERE ROUND(version, 0.5)=4.002;

And please dont ask why I dont say "...WHERE version=4.002000000000001;" because right now I have no control over how java formats it doubles(even using DecimalFormat class)
I won't.
But I will ask why you are using double to store a version number.
Machine floating point types (base 2) do not hold exact decimal numbers. But version number strings are exact. You would be better off treating version "numbers" as one of the following, depending on your version numbering scheme:
Scaled integers; e.g. 4.002 is 4002 divided by 1000. You probably don't need to store the scaling factor explicitly.
Tuples or arrays of integers; e.g. 4.002 is {4,2}. This allows version numbers like 4.003.005 to be represented as {4,3,5} etcetera.
Character strings constrained by a pattern; e.g. 4.002 is a string that matches the regex "\d+\.\d\d\d". This allows version strings with non-numeric parts.
Choose one of those and you won't have to worry about the fundamental inexact nature of floating point types, in memory or in the database.
Oh, and by the way the DecimalFormat class does let you control the number of digits after the decimal place when you format a number. You can specify this using a pattern or by calling setMaximumFractionDigits

Can you do?
DELETE FROM tbl WHERE version > 4.002 - 1e-4 AND version < 4.002 + 1e-4
or
DELETE FROM tbl WHERE version > 4.0015 AND version < 4.0025

DELETE FROM tbl WHERE abs(version-4.002)<.0001
if you want to remove all versions in an epsilon boundary of .0001

Related

Rounding issue when dividing a COUNT value by another value using jooq

When using SQL, I can run a simple query such as the query below with no issue, it returns an answer to 4 decimal places:
SELECT(COUNT(ID)/7) FROM myTable;
If the count above returns a value of 12, the returned select value is 1.7143 in workbench.
My issue occurs when I use jooq to do this calculation:
dsl.select(count(MYTABLE.ID).divide(7).from(MYTABLE).fetch();
The above code returns me a value of 1, whereas I want a value of 1.7143.
I have similar lines of jooq code which use SUM as opposed to COUNT and they return a value to 4 decimal places but I cannot find a way to get the above code to return the value to 4 decimal places.
I have tried using .round but had no success.
Has anyone else had a similar problem and knows of a solution?
There are two issues here, depending on what RDBMS you're using:
1. The type of the whole projected expression
The type of the whole division expression depends on the type of the left hand side (dividend), which is SQLDataType.INTEGER. So, irrespective of whether your RDBMS returns a decimal or floating point number, jOOQ will use JDBC's ResultSet.getInt() method to fetch the value, where you will be losing precision. So, the first step is to make sure jOOQ will fetch the desired data type. There are several ways to do this:
Use a cast on the COUNT(*) expression: count(MYTABLE.ID).cast(SQLDataType.DOUBLE).divide(7)
Use a cast on the entire expression:
count(MYTABLE.ID).divide(7).cast(SQLDataType.DOUBLE)
Use data type coercion on either expression: expr.coerce(SQLDataType.DOUBLE)
Casts have an effect on the generated SQL. Data type coercions do not.
2. How your RDBMS handles data types
In most RDBMS, count(*) expressions produce an integer type, and your division's right hand side (divisor) is also an integer, so the best resulting data type is, in fact, an integer type. I suspect you should pass a double or BigDecimal type as your divisor instead.
Solution
The ideal solution would then be to combine the above two:
dsl.select(count(MYTABLE.ID).cast(SQLDataType.DOUBLE).divide(7.0))
.from(MYTABLE)
.fetch();

Java ResultSet adding zeros

Weird behavior here.
On the SQL Server database the columns type are Numeric(20,6) but the resultset retrieves the values added by a bunch of zeros:
Table: 952.346,44 - 890.00
Resultset: 952346.440000 -> 890000.000000
The application is running on WAS 8.5, Java 7 with JDBC Driver sqljdbc4-4.1.jar
As you have mentioned Numeric(20,6) which means the scale of the number is 6
And thats the reason the same precision is considered in Java.
Now if you want to reduce it 2 decimal places, you can simply update column type or if it is already in production you can simply reduce the number in JAVA with various possible ways, on of which is as follows:
Double.parseDouble(String.format("%.2f", 952346.440000));
Your column is a NUMERIC(20,6), the 6 there is the scale, and that specifies the number of decimals. In other words, the values already were 952346.440000 and 890000.000000, and JDBC just returned them according to specification.
If you need only 2 decimals, then use NUMERIC(20,2). Or rescale the number in your code using one of the BigDecimal.setScale methods.

How to keep sequence of 1s and 0s in a column on MySQL?

I need to store a list of 1s and 0s in a single column on MySQL. For that column max length of the sequence for a cell will be 100.
On java side, after I fetch that data from MySQL, I need to convert it to something like EvictingQueue or any other appropriate data type. Because I need to add new values while the earliest entered one (1 or 0) will be popped from the list. Then I will update MySQL with new sequence.
What data type do you suggest me to use on MySQL for that column? Also do you have any other suggestion for Java side? So far, EvictingQueue looks logical to me.
Solution: I preferred using EvictingQueue on Java side and VARBINARY(100) on MySQL side. Works stable.
For MySQL, varchar type makes more sense as any numeric type may trim out leading zeros.
For java, I think we can have the value extracted as String and do bitwise shifting as explained here.
EvictingQueue is perfectly fine. However, I thought of providing another approach.

How to force hibernate to search for values with different patterns?

First of all, question is not about ignoring white spaces at the beginning or end of the strings so it is not a duplicate.
I have a mobile field in database that its values are in different formats such as xxx xxx xxx, xxxxxxxxx, x xxx xxx xx etc, how can I make hibernate criteria to ignore the patterns of strings?
For example, lets say the number in database is 344 555 666
344555666 is failed
344 555 666 is failed
344 is true (first three digits that do not have space in database!)
However, there is no doubt that all numbers are provided and all aforementioned values should return 344 555 666 as their results.
Another example would be as following:
Lets say a user searches for all phone numbers that includes 12345; then DB returns following results 12345678, 12345987 and 12345768 now I need to format these three numbers that are returned by DB before showing to the user.
Code
...
private String mobile;
....
Hibernate
.add(Restrictions.ilike("user.mobile", number);
PVR's answer is useful,but how about if in future I needed to add a new format like XXX-XXX-XXX or X-XXXX-XXXX-XXXX ? Please also note there is only one field that user uses to enter the search value.
Try using following..
criteria.add(Restrictions.ilike(
user.mobile, number, MatchMode.ANYWHERE));
Edit :
I meant that if the format of the no. in the database can only be one amongst XXX XXX XXXX / XXXXXXXXXX then we need to write a specific logic which checks both of the formats availability in database.
number1 : in format of XXX XXX XXXX
number2 : in format of XXXXXXXXXX
criteria.add(Restrictions.or(Restrictions.ilike(
user.mobile, number1, MatchMode.ANYWHERE),(Restrictions.ilike(
user.mobile, number2, MatchMode.ANYWHERE)));
Facing such problem, I usually reverse it. Currently, you have in a single column of your database (mobile) values in different formats (xxx xxx xxx, xxxxxxxxx, x xxx xxx xx etc.) and it is hard to make search on that column.
You should still allow input of mobile numbers in all those formats, but carefully rewrite them in one single format say 12345679 before writing them in database. This way that reformatting occurs only when inserting new records or on updates, and I assume you will have much more read accesses than write ones.
If you allready have records in your database, you should considere using a batch to transform them in one single operation .
Once you have only one format, you can put an index on the column as it could speed select queries by orders of magnitude as soon as you have thousands of records.
When you want to do a search, allow any format for user input of what they want, and apply same transformation that you apply on insert. For example if a user presents 123 456 789 or 123-456-789 or any of your accepted format, in your code for search transform it in 123456789 and do you query with that value (using the index ...)
From user point of view, you still allow he to present input as he wants, and simply the responses may come faster. The only drawback is that you will display not the value he entered but a standardized version of it.
From your point of view (as the programmer) you get something simpler to write and to maintain with less stress on database.
did you try Projections.sqlProjection
You can use replace REPLACE(mobile, ' ') inside
I know this is an answer that could eat up your db resources, you can
test it and check if it matches your need.
I've done phone number formatting before, but, the solution you are looking for could be difficult, if you have to search using regex I'll construct a regex in the code and search in the db. (Oracle has regex_like function, you may want to use that instead of ilike of hibernate)
eg phone number from client +333 555 9999, phone number in db: +3 33 555 9999
Construct the following regex based on what client sends:
/+(\s-.)*3(\s-.)*3(\s-.)*3(\s-.)*5(\s-.)*5(\s-.)*5(\s-.)*9(\s-.)*9(\s-.)*9(\s-.)*9(\s-.\d\w)*/
What you are saying is there could be many (.) dots may (\s) spaces many (-) hiphens in a phone number trailing with many(.\s-\d\w) (eg: x234 or ext2342)
As per your conversation with PVR, it seems like the format of the phone number can be anything.
Hibernate framework is based on patterns. It cannot handle any format on its own.
Its advisable to not include the phone number based criteria in Hibernate. You must execute your entire criteria query without the phone number and thereafter you must have java logic for filtering rest of the results.
However, the best solution is make your design more solid. Adding constraint on the format of the phone is the best practice. You can consider adding a validation on format of phone.
You can write your own Criterion by implementing the Criterion interface.
In your toSqlString method just use the replace function of your database. AFAIK replace(str, needle, replacement) is a SQL99 standard function so it should work in todays dbms.

How to perform a Restrictions.like on an integer field

I have an integer field in the DB (Postgresql) and my hibernate mapping file that I want to use in a like operation (e.g. Restrictions.like(Bean.fieldname,'123')).
The database does not support like for integer without explicit type casting select * from table where text(myint) like '1%'. Ideally, I'd like to keep the DB field type and Hibernate property type as integers and not have to load all the fields from the DB to iterate through in the Java code.
cheers :)
If the value really is a number, I'd just restrict it to a range - e.g. greater than or equal to 100 and less than 200. I wouldn't have thought you'd really want "all numbers starting with 1" - that suggests that 1 and 10000 are similar, whereas 1 and 2 are totally different. The information in a number should almost always relate to its magnitude, not the digits from its decimal representation.
Why do you need a LIKE? It's a very strange comparison, that's also why it's not an integer operator.
You could cast the value in the database to text/varchar, but you will kill performance unless you create a special index as well.
Restrictions.sqlRestriction("CAST({alias}.myint AS CHAR) like ?", "%1%", Hibernate.STRING));

Categories