Loop through Resultset depending on what columns are getting generated - java

I am working on a multithreaded project in which I need to randomly find columns that I will be using in my SELECT sql and then I will be executing that SELECT sql query.
After finding those columns-
I need to see whether the id is between the valid range, if it in between the Valid Range, then loop around the resultset using those columns from the columnsList and get the data back and store in a variable.
else if id is not in the valid range, I need to check I am not getting any data back from the resultset. But somehow if I am getting the data back and flag is true to stop the program, then exit the program. Else if I am getting the data back but flag is false to stop the program, then count how many of those happening.
Below is my code-
private volatile int count;
#Override
public void run() {
.....
final String columnsList = getColumns(table.getColumns());
....
rs = preparedStatement.executeQuery();
....
if (Read.endValidRange < id && id < Read.startValidRange) {
while (rs.next()) {
for(String column: columnsList.split(",")) {
System.out.println(column + ": " + rs.getString(column));
}
}
} else {
if(rs.next() && Read.flagTerminate) {
System.exit(1);
} else {
count++;
}
}
....
}
/**
* A simple method to get the column names in the order in which it was
* inserted
*
* #param columns
* #return
*/
private String getColumns(final List<String> columns) {
List<String> copy = new ArrayList<String>(columns);
Collections.shuffle(copy);
int rNumber = random.nextInt(columns.size());
List<String> subList = copy.subList(0, rNumber);
Collections.sort(subList, new Comparator<String>() {
#Override
public int compare(String o1, String o2) {
return columns.indexOf(o1) < columns.indexOf(o2) ? -1 : 1;
}
});
return StringUtils.join(subList, ",");
}
Problem Statement-
I am not able to understand how should I iterate through the resultset as every time I will be having different columns in my SELECT sql depending on what is getting generated. And apart from that how can I make it more cleaner in the if else loop.

OK..
You can have the Randomly generated columns saved in a String array..and then while iterating via resultset you use that array...as follows:
UPDATE
You should not include those columns in select statement..Instead you should select all columns in select statement using *...and then get the value of that column in rs.next() loop...
rs = con.executeQuery("Select * from tablename");
while(rs.next())
{
for (String col : columnsList )
{
System.out.print(rs.get(col));
}
System.out.print("\n");
}
UPDATE1
You need your critical code section written within run() to be wrapped in synchronized block so as to avoid Race Condition as follows:
private volatile int count;
#Override
public void run() {
.....
while (!Read.flagTerminate)
{
synchronized(this)
{
if (!Read.flagTerminate)
{
String columnsList = getColumns(table.getColumns());
....
rs = preparedStatement.executeQuery();
....
if (Read.endValidRange < id && id < Read.startValidRange)
{
while (rs.next())
{
for(String column: columnsList.split(","))
{
System.out.println(column + ": " + rs.getString(column));
}
}
}
else
{
while (rs.next())
{
count++;
}
}
}
}
}
System.exit(0);//it implies that flag = true;
....
}
/**
* A simple method to get the column names in the order in which it was
* inserted
*
* #param columns
* #return
*/
private String getColumns(final List<String> columns) {
List<String> copy = new ArrayList<String>(columns);
Collections.shuffle(copy);
int rNumber = random.nextInt(columns.size());
List<String> subList = copy.subList(0, rNumber);
Collections.sort(subList, new Comparator<String>() {
#Override
public int compare(String o1, String o2) {
return columns.indexOf(o1) < columns.indexOf(o2) ? -1 : 1;
}
});
return StringUtils.join(subList, ",");
}

What about this:
if (Read.endValidRange < id && id < Read.startValidRange) {
while (rs.next()) {
for (String column : columnsList.split(",")) {
System.out.println(column + ": " + rs.getString(column));
}
}
} else if (rs.next()) {
if (Read.flagTerminate) {
System.exit(1);
} else {
count++;
}
}
I have also updated the if-else conditions as per my understanding of the question.

Related

Get smallest available key (>= 1) in a HashMap

I am using a Map in which I save all tracks that the user registers. Every new track should be assigned a new ID which starts at 1. However, if tracks 1, 2, 3, 4 exist and the user deletes track with ID 1 the next added track gets the smallest available ID >=1 which in this case would be 1.
How is it possible to do this efficiently? Or is there a better datatype available?
private Map<Integer, Track> tracks;
public Register() {
this.trains = new HashMap<>();
}
public void addTrack(Track track) {
int id = <get Smallest Value Available >= 1>;
this.tracks.put(id, track);
}
public void removeTrack(int id) {
if (tracks.containsKey(id)) {
this.tracks.remove(id);
} else {
Terminal.printError("track with ID " + id + " doesn't exist.");
}
}
Approach 1: You can use TreeMap and iterate through its keys, and if there is a gap between two keys, you can insert your element in this gap. Addition will work in O(currentKeysCount) worst-case, deletion will work in O(log(currentKeysCount)).
private TreeMap<Integer, Track> tracks;
public Register() {
this.trains = new TreeMap<>();
}
public void addTrack(Track track) {
int id = 1;
for (int key : this.trains.keySet) {
if (key > id) break;
id = key + 1;
}
this.tracks.put(id, track);
}
public void removeTrack(int id) {
if (tracks.containsKey(id)) {
this.tracks.remove(id);
} else {
Terminal.printError("track with ID " + track.getId() + " doesn't exist.");
}
}
Approach 2: You can create a PriorityQueue that will store deleted keys. Addition and deletion will work in O(log(currentKeysCount) + log(deletedKeysCount)).
private Map<Integer, Track> tracks;
private PriorityQueue<Integer> deletedKeys;
private int nextKey;
public Register() {
this.trains = new HashMap<>();
this.deletedKeys = new PriorityQueue<>();
this.nextKey = 0;
}
public void addTrack(Track track) {
int id = nextKey;
if (!deletedKeys.isEmpty()) id = deletedKeys.poll();
this.tracks.put(id, track);
}
public void removeTrack(int id) {
if (tracks.containsKey(id)) {
this.tracks.remove(id);
this.deletedKeys.add(id);
} else {
Terminal.printError("track with ID " + track.getId() + " doesn't exist.");
}
}
Approach 3: It may be much easier to ignore missing keys and just increase nextKey counter on every addition (you can even use long instead of int). Unless you add a new key more often than once per millisecond, your program won't fail earlier than all code that uses System.currentTimeMillis() (and it will fail in more than 292 million years). Addition and deletion will work in O(log(currentKeysCount))
I would do it with a loop and see if the which value is not yet included in the map
public Integer getId(Map<Integer, Track> tracks) {
// Set on max-value
Integer id = Collections.max(tracks.keySet()) + 1;
for (int i = 1; i <= tracks.keySet().size(); i++) {
if (!tracks.keySet().contains(i)) {
// lower value available
id = i;
break;
}
}
return id;
}
Say if from 100 trains number 40 and number 60 are free, you want to get 40 from {40, 60}.
private final Map<Integer, Track> tracks = new HashMap<>();;
private final SortedSet<Integer> freeIds = new TreeSet<>();
public synchronized void addTrack(Track track) {
int id;
if (freeIds.isEmpty()) {
id = 1 + tracks.size(); // Numbering from 1
} else {
id = freeIds.first();
freeIds.remove(id);
}
track.setId(id);
tracks.put(id, track);
}
public synchronized void removeTrack(int id) {
Track track = tracks.remove(id);
if (track != null) {
track.setId(-1);
freeIds.add(id);
} else {
Terminal.printError("track with ID " + track.getId() + " doesn't exist.");
}
}

Use values from previous row to aggregate values when row missing

I have a whole bunch of rows that contain tax payments.
Each row contains PaymentDueDate.
If row is missing in between PaymentDueDates, I have to use same values from previous row to aggregate for all totals.
In below example, between Row="2" and Row="3", data is missing for months 2015/09, 2015/10, 2015/11, 2015/12, 2016/01, 2016/02.
So, I have to use Row="2" values to use to account for missing rows.
<PaymentChangeMaintenance>
<PaymentChangeMaintenanceTransaction Row="1">
<BuydownSubsidyAmount>0.00</BuydownSubsidyAmount>
<AnnualInterestRate>4.75000</AnnualInterestRate>
<PIAmount>689.79</PIAmount>
<PaymentDueDate>2015-07-01</PaymentDueDate>
<CityTaxAmount>23.22</CityTaxAmount>
<CountyTaxAmount>32.25</CountyTaxAmount>
</PaymentChangeMaintenanceTransaction>
<PaymentChangeMaintenanceTransaction Row="2">
<BuydownSubsidyAmount>0.00</BuydownSubsidyAmount>
<AnnualInterestRate>4.75000</AnnualInterestRate>
<PIAmount>689.79</PIAmount>
<PaymentDueDate>2015-08-01</PaymentDueDate>
<CityTaxAmount>125.25</CityTaxAmount>
<CountyTaxAmount>666.22</CountyTaxAmount>
</PaymentChangeMaintenanceTransaction>
<PaymentChangeMaintenanceTransaction Row="3">
<BuydownSubsidyAmount>0.00</BuydownSubsidyAmount>
<AnnualInterestRate>4.75000</AnnualInterestRate>
<PIAmount>689.79</PIAmount>
<PaymentDueDate>2016-03-01</PaymentDueDate>
<CityTaxAmount>125.25</CityTaxAmount>
<CountyTaxAmount>666.22</CountyTaxAmount>
</PaymentChangeMaintenanceTransaction>
</PaymentChangeMaintenance>
Here is code someone wrote, but it is not clean-looking. I would like to use for-each :/
private void aggregateEscrowPaymountAmounts(List<PaymentChangeMaintenanceFieldsV214Type> fieldsType,
PaymentChangeMaintenance paymentChangeMaintenance, final int numberOfTrialPayments) {
AtomicInteger cnt = new AtomicInteger(1);
Iterator<PaymentChangeMaintenanceFieldsV214Type> fieldsTypeIterator = fieldsType.iterator();
PaymentChangeMaintenanceFieldsV214Type fieldType = fieldsTypeIterator.next();
PaymentChangeMaintenanceFieldsV214Type nextFieldType = null;
if (fieldsTypeIterator.hasNext()) {
nextFieldType = fieldsTypeIterator.next();
}
LocalDate monthDate = fieldType.getNextPaymentDueDate();
while (cnt.getAndIncrement() <= numberOfTrialPayments) {
PaymentChangeMaintenance tempPaymentChangeMaintenance = createPaymentChangeMaintenanceEscrow(fieldType);
paymentChangeMaintenance.aggregate(tempPaymentChangeMaintenance);
monthDate = monthDate.plusMonths(1);
if (nextFieldType != null) {
LocalDate nextFieldTypeDate = nextFieldType.getNextPaymentDueDate();
if (nextFieldTypeDate.getMonthValue() == monthDate.getMonthValue()) {
fieldType = nextFieldType;
if (fieldsTypeIterator.hasNext()) {
nextFieldType = fieldsTypeIterator.next();
} else {
nextFieldType = null;
}
}
}
}
}
For this certain case you can use a following approach: determine a step - for you it's a month. Then initialize a default value for a case of absence of the value on the next step. Then use some method that will take a next value and default value and depends on a step presence will return one of them
Here is a pseudocode:
List<Item> items;
Item nextItem = items.get(0);
Value step = month;
for (int i = 1; i < items.size(); i++) {
nextItem = getNextItem(items.get(i), nextItem, step);
****
}
Item getNextItem(Item nextItem, Item defaultItem, Value step) {
if (!nextItem.getStepValue().equals(calcNext(step))) {
return defaultItem;
} else {
return nextItem;
}
}
StepValue calcNext(Value step) {
/*some calculations. In your case month increment*/
}

How can I check that a SQL query has no result?

As written in the title, I check my SQL database with following method:
public String[] getRecord(String category){
String[] record = new String[3];
Cursor crsRecord = sqliteDatabase.rawQuery(QUERY_GET_RECORD + category, null);
int i=0;
while(crsRecord.moveToNext()){
record[i] = crsRecord.getString(0);
i++;
}
return record;
}
Now it could be that the line:
Cursor crsRecord = sqliteDatabase.rawQuery(QUERY_GET_RECORD + category, null);
has no result, because I have no appropriate data in my database. How can I check that I have no result?
cursor.moveToFirst();
if (cursor.isAfterLast()){
// You have no results
}
Or, you could just change your code to this:
while(!crsRecord.isAfterLast()){
// Instead of using an int literal to get the colum index,
// use the getColumnIndex method
int index = crsRecord.getColumnIndex(COLUMN_NAME);
if (index == -1) {
// You don't have the column-- do whatever you need to do.
}
else {
record[i] = crsRecord.getString(index);
i++;
}
crsRecord.moveToNext();
}
If there are no records, the while loop never starts.

Large list returned from a SimpleJdbcTemplate query

here is my problem : at some point in my Java program, I get a (very) big List of Events from a database using the SimpleJdbcTemplate class from Spring.
List<Event> events =
this.simpleJdbcTemplate.query(myQuery,
myMapper(),
new Object[] {
filter.getFirst(),
filter.getSecond(),
filter.getThird()}
);
The problem is that the list may contain something like 600,000 Events ... Therefore using a lot of memory (and also taking time to be processed).
However I don't really need to retrieve all the Events at once. Actually I would like to be able to iterate over the list, read only a few events (linked to a specific KEY_ID - the sql query myQuery is ordered by KEY_ID), process them and finally get back iterating, letting the garbage collector get rid of the previous and already processed Events so that I never exceed a certain amount of memory.
Is there a nice way to do so using the Spring library (or any library)?
Cheers,
Vakimshaar.
I think part of your problem is that your query is executing all at once and you're getting the result set in a big lump that hogs memory and network bandwidth. In addition to needing a way to iterate through the result set you need a way to get the results back from the database a bit at a time.
Take a look at this answer about lazy-loading resultsets. It looks like you could set the fetch size in combination with using a ResultSetExtractor and possibly get the desired behavior (depending on the database).
You should construct your SQL query to return limited set of items starting with particular number. It is database specific operation (in Oracle and MySql you will manipulate rownum in some form) . Then you repeat the call increasing start number until all elements are processed.
Oracle example
SELECT * FROM ([your query]) WHERE rownum>=[start number]
AND rownum<[start number + chunk size];
If I understand correctly, you would like to iterate over the result set, but are not interested in building the full list of results.
Just use the query method with a ResultSetExtractor as argument. The ResultSetExtractor can use your mapper to transform the current row into an Event. Put every event into a list until you reach a different KEY_ID or the end of the result set, then proceed with your list of events and clear the list.
Maybe the following code might be useful for you?
protected <T> List<T> queryPagingList(final String query, final PagingQueryContext context, final ParameterizedRowMapper<T> mapper, final SqlParameter... parameters) throws DataAccessException {
final Integer count = context.getCount();
final Integer beginIndex = context.getBeginIndex();
final List<SqlParameter> parameterList = Arrays.asList(parameters);
final PreparedStatementCreatorFactory preparedStatementCreatorFactory = new PreparedStatementCreatorFactory(query, parameterList);
preparedStatementCreatorFactory.setResultSetType(ResultSet.TYPE_SCROLL_INSENSITIVE);
preparedStatementCreatorFactory.setNativeJdbcExtractor(new NativeJdbcExtractorAdapter() {
#Override
public PreparedStatement getNativePreparedStatement(final PreparedStatement ps) throws SQLException {
ps.setFetchSize(count + 1);
ps.setMaxRows((beginIndex * count) + 1);
return ps;
}
#Override
public Statement getNativeStatement(final Statement stmt) throws SQLException {
stmt.setFetchSize(count + 1);
stmt.setMaxRows((beginIndex * count) + 1);
return stmt;
}
});
final PreparedStatementCreator psc = preparedStatementCreatorFactory.newPreparedStatementCreator(parameterList);
final ResultSetExtractor<List<T>> rse = new ResultSetExtractor<List<T>>() {
public List<T> extractData(final ResultSet rs) throws SQLException {
if (count > 0) {
rs.setFetchSize(count + 1);
if (beginIndex > 0) {
rs.absolute((beginIndex - 1) * count);
}
}
rs.setFetchDirection(ResultSet.FETCH_FORWARD);
final List<T> results = new ArrayList<T>(count + 1);
for (int rowNumber = 0; rs.next(); ++rowNumber) {
if (count > 0 && rowNumber > count) {
break;
}
results.add(mapper.mapRow(rs, rowNumber));
rs.last();
context.setTotalResults(rs.getRow());
}
return results;
}
};
return this.simpleJbcTemplate.query(psc, null, rse);
}
Here is PagingQueryContext:
public class PagingQueryContext implements Serializable {
private static final long serialVersionUID = -1887527330745224117L;
private Integer beginIndex = 0;
private Integer count = -1;
private Integer totalResults = -1;
public PagingQueryContext() {
}
public Integer getBeginIndex() {
return beginIndex;
}
public void setBeginIndex(final Integer beginIndex) {
this.beginIndex = beginIndex;
}
public Integer getCount() {
return count;
}
public void setCount(final Integer count) {
this.count = count;
}
public Integer getTotalResults() {
return totalResults;
}
public void setTotalResults(final Integer totalResults) {
this.totalResults = totalResults;
}
#Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((beginIndex == null) ? 0 : beginIndex.hashCode());
result = prime * result + ((count == null) ? 0 : count.hashCode());
return result;
}
#Override
public boolean equals(final Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (!(obj instanceof PagingQueryContext)) {
return false;
}
final PagingQueryContext other = (PagingQueryContext) obj;
if (beginIndex == null) {
if (other.beginIndex != null) {
return false;
}
} else if (!beginIndex.equals(other.beginIndex)) {
return false;
}
if (count == null) {
if (other.count != null) {
return false;
}
} else if (!count.equals(other.count)) {
return false;
}
return true;
}
}
It adds one to the fetch size so that you can peek to see if there will be more results. Also, depending on how the JDBC driver you are using implements rs.last(), you might not want to use that call in the ResultSetExtractor and forgo using totalRows. Some drivers might load all of the data when last() is invoked.

Using JDBC, how can I substitute multiple IDs into "DELETE FROM T WHERE id IN (?)"

I have some code that produces a set of primary key values that I want to delete from a database table.
long[] keysToDelete = { 0, 1, 2, 3 };
and I'd like to use a PreparedStatement to execute the equivalent of
DELETE FROM MyTable WHERE myPrimaryKey IN (0, 1, 2, 3);
Any idea how?
Two steps:
Build up the PreparedStatement SQL String with the appropriate # of parameters.
Loop over the array of values and bind each one to its parameter.
Unfortunately, there's no good way to bind an array all at once.
I've written a class to dynamically generate such a multi-parameter query. It currently has some limitations (for quickness of writing) and has not been thoroughly tested, but may be a good way to get you started. Limitations:
Only handles one multi-argument parameter (??)
Falsely recognizes question marks in quotes as parameters
API is not pretty but the alternative was writing a full-on PreparedStatement decorator with lots of state management and that was more work than I was willing to put into it.
Source:
/**
* A PreparedStatement decorator that can bind a set of arguments
*
* A specialized ?? placeholder in a string can be bound to a set of
* values instead of just single values. Currently, only one such
* specialized placeholder is supported, and you must bind it before
* obtaining the prepared statement.
*
* If you want to bind additional values to the PreparedStatement after
* producing it, you must run the parameter index through the param()
* method.
*
* Example use:
*
*
* MultiValueBinder binder = new MultiValueBinder(
* "UPDATE table SET value = ? WHERE id IN (??)", conn);
* binder.setInts(myIds);
*
* PreparedStatement stmt = binder.statement();
* stmt.setString(binder.param(1), "myValue");
*
* ResultSet rs = stmt.executeQuery();
*
* Note: this class is not robust against using question marks in literal
* strings. Don't use them :).
*/
public class MultiValueBinder {
private Connection connection;
private PreparedStatement statement;
private String sql;
private int argumentsBefore = 0;
private int setSize = 0;
public MultiValueBinder(String sql, Connection connection) {
this.sql = sql;
this.connection = connection;
}
/**
* Bind a collection of integers to the multi-valued argument
*/
public void setInts(Collection<Integer> ints) throws SQLException {
explodeQuery(ints.size());
buildStatement();
try {
int i = 0;
for (Integer integer: ints)
statement.setInt(1 + argumentsBefore + i++, integer);
} catch (Exception ex) {
cleanStatement();
throw (ex instanceof SQLException) ? (SQLException) ex : new SQLException(ex);
}
}
/**
* Bind a collection of strings to the multi-valued argument
*/
public void setStrings(Collection<String> strings) throws SQLException {
explodeQuery(strings.size());
buildStatement();
try {
int i = 0;
for (String str: strings)
statement.setString(1 + argumentsBefore + i++, str);
} catch (Exception ex) {
cleanStatement();
throw (ex instanceof SQLException) ? (SQLException) ex : new SQLException(ex);
}
}
/**
* Explode the multi-value parameter into a sequence of comma-separated
* question marks.
*/
private void explodeQuery(int size) throws SQLException {
int mix = sql.indexOf("??");
if (mix == -1) throw new SQLException("Query does not contain a multi-valued argument.");
if (size == 0) throw new SQLException("Can't bind an empty collection; generated SQL won't parse.");
// Count the number of arguments before the multi-marker
argumentsBefore = 0;
for (int i = 0; i < mix; i++) {
if (sql.charAt(i) == '?') argumentsBefore++;
}
setSize = size;
// Generate the exploded SQL query
StringBuilder sb = new StringBuilder(sql.substring(0, mix)); // Start
for (int i = 0; i < setSize; i++) { // ?, ?, ...
if (i > 0) sb.append(", ");
sb.append('?');
}
sb.append(sql.substring(mix + 2)); // Remainder
sql = sb.toString();
}
/**
* Create the statement if it hasn't been created yet
*/
private void buildStatement() throws SQLException {
if (statement != null) return;
if (sql.contains("??"))
throw new SQLException("Multi-valued argument not bound yet.");
statement = connection.prepareStatement(sql);
}
private void cleanStatement() {
if (statement != null) {
try {
statement.close();
} catch (Exception ex) {
/* Ignore */
}
statement = null;
}
}
public PreparedStatement statement() throws SQLException {
buildStatement();
return statement;
}
/**
* Transform the 1-based-index of the given argument before query expansion
* into the index after expansion.
*
* The ?? placeholder takes up one index slot.
*/
public int param(int ix) {
if (ix <= argumentsBefore) return ix;
if (ix == argumentsBefore + 1)
throw new RuntimeException(ix + " is the index of the multi-valued parameter.");
return argumentsBefore + 1 + setSize;
}
}
Not totally sure but this might help:
PreparedStatement pstmt = Connection.prepareStatement("DELETE FROM MyTable WHERE myPrimaryKey IN (?)");
pstmt.setArray(1, idArray);

Categories

Resources