I have a table with four columns, this is how it looks like. I would call it T_BPR_KPI_MONTHLY_VALUES
KPI_NAME_SHORT_S | MONTH_N | YEAR_N | VALUE_N
-----------------------------------------------
MY_KPI_1 | 1 | 2015 | 99.87
MY_KPI_2 | 1 | 2015 | 97.62
... | 1 | 2015 | ...
MY_KPI_1 | 2 | 2015 | ...
... | ... | 2015 | ...
Each kpi represents a measurement and each of them has daily values which are saved in another table called T_BPR_KPI_DY. My goal is to calculate and save monthly values of each KPI.
It is possible that on the certain day, daily values for some kpis are still missing and in order to precisely calculate monthly values I must be able to replace exisisting values in the database as well as insert new etries for the future months and years.
I tought that oracle sql merge operation would be good choice for this task. The idea is to check whether an entry already exists and if so than to update its value and if not to insert new one.
This is how the query looks like
MERGE INTO T_BPR_KPI_MONTHLY_VALUE A
USING( SELECT 'MY_KPI_1' AS KPI_NAME_SHORT_S, 1 AS MONTH_N, 2014 AS YEAR_N FROM DUAL ) B
ON ( A.KPI_NAME_SHORT_S = B.KPI_NAME_SHORT_S )
WHEN MATCHED THEN
UPDATE SET VALUE_N = ( select AVG(MY_KPI_1) from T_BPR_KPI_DY where DAY_D between '01.01.2014' AND '31.01.2014')
WHEN NOT MATCHED THEN
INSERT (KPI_NAME_SHORT_S, MONTH_N, YEAR_N, VALUE_N) VALUES ('MY_KPI_1', 1, 2014, ( select AVG(MY_KPI_1) from T_BPR_KPI_DY where DAY_D between '01.01.2014' AND '31.01.2014') )
I tought that calculating avg monthly values on the fly is not a bad idea, so as you can see I have another select query which only calculates avg monthy value for the specific kpi. I am not sure if this is a best practic solution but it works fine when I execute this query in oracle sql developer tool. however when I try to execute it from the app it does not work.
This is how the method looks like
public static void storeValuesToDb(ArrayList<String> kpiNames) throws SQLException {
Connection conn = getOracleJDBCConnection_DASH();
int currentYear = cal.get(Calendar.YEAR);
int startYear = cal.get(Calendar.YEAR) - 1;
for (String kpiName : kpiNames) {
for (int i = startYear; i <= currentYear; i++) {
for (int j = 0; j < 12; j++) {
try {
String myMergeSQL = ""
+ "MERGE INTO T_BPR_KPI_MONTHLY_VALUE A "
+ "USING( SELECT '" + kpiName + "' AS KPI_NAME_SHORT_S, " + (j + 1) + " AS MONTH_N, " + i + " AS YEAR_N FROM DUAL ) B ON ( A.KPI_NAME_SHORT_S = B.KPI_NAME_SHORT_S ) "
+ "WHEN MATCHED THEN "
+ "UPDATE SET VALUE_N = ( select AVG(" + kpiName + ") from T_BPR_KPI_DY where DAY_D between '" + getFirstDateOfMonth(j, i) + "' AND '" + getLastDateOfMonth(j, i) + "') "
+ "WHEN NOT MATCHED THEN "
+ "INSERT (KPI_NAME_SHORT_S, MONTH_N, YEAR_N, VALUE_N) VALUES ('" + kpiName + "', " + (j + 1) + ", " + i + ", ( select AVG(" + kpiName + ") from T_BPR_KPI_DY where DAY_D between '" + getFirstDateOfMonth(j, i) + "' AND '" + getLastDateOfMonth(j, i) + "') )";
System.out.println(myMergeSQL);
Statement stmt_dash = conn.createStatement();
stmt_dash.executeUpdate(myMergeSQL);
conn.commit();
stmt_dash.close();
} catch (SQLException ex) {
conn.close();
}
}
}
}
conn.close();
}
In terminal it prints out only the first merge sql. It neither finishs the operation nor throws an exception. It blocks somehow and in the db happens also nothing. It could be possible that my merge query is not correct or that it is not possible to execute this kind of operation with statement object. If someone is able to see what cases this issue, please help.
Thx in advance
I would start by reformulating your merge query and solve some issues:
the USING part of a MERGE actually means your "source of raw data". You are using a select from dual with hardcoded values. Here you should select all KPIs and also calculate the Average by KPI. Compose your query that selects all KPIs with their coresponding VALUE_N and use it in the USING part
when matched then UPDATE SET use the values from "source of raw data" which is alias B in your code, not compute on inside the UPDATE clause.
when not matched then INSERT VALUES - again, use values from "source of raw data" which is alias B in your code, do not try to compute the VALUE_N inside the insert - well at least not in that manner I think this is your querys main issue.
MERGE INTO xxx A using () B you gave 2 aliases to your tables but down the line inside the WHEN MATCHED or NOT you are not using the alias. This can raise problems if A and B have similar named columns.
An example of how I use merge in production:
Merge into Destination, using a select from a table Source (inside the select from source you can also add other computations obviously, in your case the average)
T_REPORT_DAILY_SNAPSHOT_2G should be in your code the select kpis name, value and average or whatever you need on INSERT and UPDATE
MERGE INTO T_CELLS_2G dest
USING (SELECT DISTINCT *
FROM T_REPORT_DAILY_SNAPSHOT_2G) src
ON (dest.lac = src.lac and dest.cell_id = src.cell_id)
WHEN MATCHED THEN
UPDATE SET
dest.cell_name = src.cell_name,
dest.loc_code = src.loc_code,
dest.site_code = src.site_code,
dest.rac = src.rac
WHEN NOT MATCHED THEN
INSERT (dest.cell_name,
dest.loc_code,
dest.site_code,
dest.lac,
dest.cell_id,
dest.rac)
VALUES (src.cell_name,
src.loc_code,
src.site_code,
src.lac,
src.cell_id,
src.rac);
Hope this helps in some way.
Related
I'm doing a Java program that has to interact with a MySQL database, and I'm trying to delete a row of a table from a query in Java.
The problem is that when I try to convert a String in Java, "2021/2022" to char(9) I get an error that says that data is too long for column. Can anyone help?
This is the method that should delete the row:
public boolean borrarMatricula(Connection conn, int alumno, int profesor, int asignatura,
String c){
boolean borrado = false;
String drop = "DROP PROCEDURE IF EXISTS DELETE_ENROLLMENT";
String rutina = "CREATE PROCEDURE DELETE_ENROLLMENT(IN alumno double(4,0), "
+ "IN profesor double(2,0), IN asignatura double(3,0), IN c char(9))"
+ "BEGIN "
+ "DELETE FROM MATRICULAS WHERE codigoAlumno=alumno and "
+ "codigoProfesor=profesor and codigoAsignatura=asignatura and curso=c;"
+ "END";
try{
Statement s = conn.createStatement();
s.execute(drop);
s.execute(rutina);
CallableStatement cs=conn.prepareCall("{call DELETE_ENROLLMENT(" +
alumno + "," + profesor + "," + asignatura + "," + c + ")}");
cs.execute();
borrado = true;
}
catch(SQLException e){
Vista.muestraErrorSQL(e);
}
catch(Exception e){
e.printStackTrace(System.err);
}
return(borrado);
}
"Curso" is defined as a char(9), and the String I'm using is 2021/2022
What happened is that you are concatenating Java variables into your CALL statement:
CallableStatement cs=conn.prepareCall("{call DELETE_ENROLLMENT(" +
alumno + "," + profesor + "," + asignatura + "," + c + ")}");
So it runs a CALL statement as though you formatted it like this:
mysql> call delete_enrollment(1, 1, 1, 2021/2022);
Query OK, 0 rows affected, 1 warning (0.00 sec)
mysql> show warnings;
+---------+------+----------------------------------------+
| Level | Code | Message |
+---------+------+----------------------------------------+
| Warning | 1265 | Data truncated for column 'c' at row 1 |
+---------+------+----------------------------------------+
In Java, warnings are promoted to errors. The value is not the string '2021/2022', but a floating-point value which is the quotient of 2021/2022, or approximately 0.9995. It results in a warning when you try to use a floating-point value as a string.
If you used an actual quoted string, there is no warning:
mysql> call delete_enrollment(1, 1, 1, '2021/2022');
Query OK, 0 rows affected (0.00 sec)
But you should just avoid using string concatenation. You're already using a prepared statement, so you should use bound parameters:
CallableStatement cs=conn.prepareCall("{call DELETE_ENROLLMENT(?, ?, ?, ?)}");
cs.setDouble(1, alumno);
cs.setDouble(2, profesor);
cs.setDouble(3, asignatura);
cs.setString(4, c);
cs.execute();
Using parameters instead of string-concatenation is one of the most important reasons to use prepared statements. It makes your code more secure and less error-prone, and it's easier to read and write the code.
See also: https://dev.mysql.com/doc/connector-j/8.0/en/connector-j-usagenotes-statements-callable.html
I have a problem with a SQL statement. I have a java app with a button to add a column into a database. For every new date, I have a new column created, which is done using the following query
final String queryCreate = "alter table Currency add '"+ newColumn + "' decimal ";
When I try to populate the column with data using the following query:
final String queryAdd = "insert into Currency( '" + newColumn + "' ) values(1.95583)";
The data is added below the last row of the previous column.
like this:
https://postimg.org/image/579gjmyzj/
My question is why the insert statement does what it does in my situation, what am I doing wrong?
INSERT creates new records, if you want to modify existing records you need to use UPDATE.
For example, to modify the first record:
"UPDATE Currency SET " + newColumn + " = 1.95583 WHERE Currency_ID = 1"
use update query
assuming strCurrencyID="1";
strCurrencyName="EUR";
final String queryAddUpdate =
if exists(Select Top 1 1 from Currency where Currency_ID=" + strCurrencyID +")
Begin update Currency set " + newColumn + "=1.95583 where Currency_ID=" +strCurrencyID + "
End
Else
Begin
insert into Currency(Currency_id, Currency_name, '" + newColumn + "' ) values("+ strCurrencyID +",'" + strCurrencyName + ", 1.95583)
End"
This will update the value in in column if currency id exists if not this will insert new row.
But I think database design should be change can you explain your business requirement.
I have this select that works every time running in workbench but fails sometimes for the same arguments over jdbc. The problem is that sometimes, over JDBC, 'pos' value returns null. I Think that, for some reason, the #p as not started, but dont know how to fix.
SELECT t1.wId, t1.twId, t1.name, t1.timeout, t1.pos
FROM (
SELECT w.id AS wId, tw2.id AS twId, w.name AS name, tw2.timeout AS timeout, #p:=#p+1 AS pos
FROM timeout_workqueue tw1
INNER JOIN timeout_workqueue tw2
ON tw1.workqueue_id = tw2.workqueue_id
INNER JOIN workqueue w
ON tw1.workqueue_id = w.id
WHERE tw1.id = ?
ORDER BY tw2.id) t1, (SELECT #p:=1) c
WHERE t1.twId = ?;
The whole Java Code Are:
public TimeoutWorkqueueView getTimeoutWorkqueueView(Integer id) {
String sql = "SELECT t1.wId, t1.twId, t1.name, t1.timeout, t1.pos"
+ " FROM ("
+ " SELECT w.id AS wId, tw2.id AS twId, w.name AS name, tw2.timeout AS timeout, #p:=#p+1 AS pos"
+ " FROM timeout_workqueue tw1"
+ " INNER JOIN timeout_workqueue tw2"
+ " ON tw1.workqueue_id = tw2.workqueue_id"
+ " INNER JOIN workqueue w"
+ " ON tw1.workqueue_id = w.id"
+ " WHERE tw1.id = ?"
+ " ORDER BY tw2.id) t1, (SELECT #p:=1) c"
+ " WHERE t1.twId = ?";
return (TimeoutWorkqueueView) getJdbcTemplate().queryForObject(sql, new BeanPropertyRowMapper(TimeoutWorkqueueView.class), id, id);
}
Ok so the problem i can see here (please verify) is that you think you are running the query with parameter set to 1 to begin with.
However if you set #p = 1 -> this #p:=#p+1 will not evaluate to 1 any more.
Also assuming you have 20 rows, you run this query 20 times but on the last run it will return null because pos will be 21 and this does not exists.
I'm new to JPA (JPQL), so I got stacked with no good solution to maintain WHERE clause with a flexible comparison plan.
I would like to implement a JPQL statement of the form like:
SELECT i FROM Item i
WHERE i.weight (comparison_operator_placeholder) :weight
AND i.height (comparison_operator_placeholder) :height;
(comparison_operator_placeholder):{ = | < | > | <= | >= }
The (comparison_operator_placeholder) shall be selected during runtime, based on user's input.
Intuitively I realize that no such grammar exists, however, there shall be other way than writing queries for each combination. I will appreciate any workaround.
You don't have to write multiple queries. Just use a variable for the operator(s), like so:
String query = "SELECT i FROM Item i \n" +
"WHERE i.weight " + operator + " :weight \n" +
"AND i.height " + operator2 + " :height";
I wasnt quite sure how to word the title so sorry about that. I am trying to load some start and stop schedule times into a database but sometimes a start and stop time for the same system will overlap such as one time may be 5:30 to 12:30 and then another time for the same system may be 8:30 to 10:30 I want to avoid inserting that data into the table.
I am using a jdbc and odbc bridge to do this all in a java program, when the user clicks generate schedules it reads all the schedule info from a text file, and then inserts it into the database. When the programs reads the times that are in between already existing times I want to skip doing the insert.
My Ideas so have to been to some how compared the end time i get from the text file with the MAX value of the times in the database and if its less than that value then skip this insert but I dont know how to tie the MAX value into an if statement. Another idea was after im done all the inserts then just delete the rows where the SCHEDULE_TIME is greater than the min value in the SCHEDULE_TIME column and the SCHEDULE_TIME is less than the max value in the SCHEDULE_TIME column.
here is an example of what the data in my table looks like:
SITE_ID ------- DEV_ID ------- SCHEDULE_TIME ------- VALUE_ENUM
---------------------------------------------------------------
1 3000 09:30:00 1
1 3000 15:30:00 0
1 3000 12:30:00 1
1 3000 13:30:00 0
1 3000 16:30:00 1
1 3000 18:30:00 0
the rows alternate from start top to stop time all the rows where VALUE_ENUM are 1 are start times and all the rows where VALUE_ENUM are 0 are stop times. Im trying to delete the time that falls between a other start and stop times in this case delete rows 3 and 4. Keep in mind this tables actually creates hundreds of rows from the text file so I can not just delete it manually, it would be best if I could find a way to just avoid inserting it.
Here is a copy of my current insert method, ignore all the extra columns I use, they are unrelated to the problem, they are just using so I add and delete for the correct systems.
private void Insert() throws SQLException
{
stmt = conn.createStatement();
String sqlStm = "update ARRAY_BAC_SCH_Schedule set SCHEDULE_TIME = {t '" + finalEnd + "'} WHERE SCHEDULE_TIME >= {t '" + finalStart + "'} AND" +
" SCHEDULE_TIME <= {t '" + finalEnd + "'} AND VALUE_ENUM = 0 AND DEV_ID = " + devID + " and INSTANCE = " + instanceNum;
int updateSuccess = stmt.executeUpdate(sqlStm);
if (updateSuccess < 1)
{
sqlStm = "insert into ARRAY_BAC_SCH_Schedule (SITE_ID, DEV_ID, INSTANCE, DAY, SCHEDULE_TIME, VALUE_ENUM, Value_Type) " +
" values (1, " + devID + ", " + instanceNum + ", " + day + ", {t '" + finalStart + "'}, 1, 'Unsupported')";
stmt.executeUpdate(sqlStm);
sqlStm = "insert into ARRAY_BAC_SCH_Schedule (SITE_ID, DEV_ID, INSTANCE, DAY, SCHEDULE_TIME, VALUE_ENUM, Value_Type) " +
" values (1," + devID + ", " + instanceNum + ", " + day + ", {t '" + finalEnd + "'}, 0, 'Unsupported')";
stmt.executeUpdate(sqlStm);
}
if(stmt!=null)
stmt.close();
}
I hope I explained this enough, sorry if the question is unclear.
I have also posted this question under sql tags to see if someone knows a way to do it with just sql.
Update: I had it working before by taking the last start and end time entering and then when I go to enter 2 new times I checked to see that the new start time was >= the previous one and that the new end time <= the previous end time and if they were I would skip the insert.
However I have had to make some changes to the program which I can no longer get the previous start/end time, my idea now is if I can select the last 2 times in the table and saved them to a variable on the java side and then do the same comparison I did before
I think the problem is with your design. If your text file is relying in the order you shouldn't be saving as is in the database.
I would have store that information in just one record. For example instead of having VALUE_ENUM and two records in the database you will just need one record with the following fields START_SCHEDULE_TIME and END_SCHEDULE_TIME. Now with that design you could just do a select like this:
select count(*) from ARRAY_BAC_SCH_Schedule where finalStart >=
START_SCHEDULE_TIME and finalEnd <= END_SCHEDULE_TIME
if the select returns 1 then you just skip and don't insert.
Is it fair to say that the problem area is characterised/identified by 2 or more consecutive start times? I suspect it is.
If that is the case then perhaps just load everything you have into the table, then parse the table in time order. Each time you encounter 2 consecutive start times you know you must delete 'this' start/end pair.
Of course if you encounter a THIRD start time in a row you know you have to delete 2 end times.
so in pseudocode - The dataset is ordered by Dev_Id, schedule_time, value_enum desc
int NestCount = 0;
int CurrDevId = 0;
for each record
{
// Optional DevId Logic Starts Here
if ThisRecord.DevId <> CurrDevId then
{
if NestCount <> 0
{
CurrDevId = 0;
RollBack;
Shriek("Cannot Proceed Because The Data Are Fubar");
}
else
{
CurrDevId = ThisRecord.DevId;
}
} // And Ends Here
if
CurrDevId <> 0 && // Optional DevId Logic
IsAStartRecord()
{
If NestCount > 0
{
DeleteThisRecord();
}
NestCount += 1;
}
else // Is an end record
{
NestCount -= 1;
If NestCount > 0
{
DeleteThisRecord();
}
}
}
If it is possible to implement this as a stored procedure I probably would.
Does that help ?
thanks for the help, but I ended up just doing a string replace on the : in the time and made it into a . then converted it to a double and saved that time into a temp variable and then the next time I had times for that system I checked if the start time was greater than the last start time and if the end time was less than the last one so if they fell between I could avoid adding, not 100% tested but it worked for the first system I tried