My tables
N
ID|T_ID
1|1
2|2
T
ID|NAME
1|T1
2|T2
Using the tables as follows
com.db.N N_TABLE = N.as("N_TABLE");
com.db.T T_TABLE = T.as("T_TABLE");
com.db.T T2_TABLE = T.as("T2_TABLE"); //Random alias, not used in query
SelectQuery selectQuery = create.selectQuery();
selectQuery.addFrom(N_TABLE);
selectQuery.addJoin(T_TABLE, JoinType.LEFT_OUTER_JOIN, T_TABLE.ID.eq(N_TABLE.T_ID));
Result<Record> result = selectQuery.fetch();
for (Record record : result) {
System.out.println(record.get(T2_TABLE.NAME));
}
It gives a ambiguity warning, but still gets the value even though alias is wrong. I would expect it to return "null", I guess it falls back to using only field name.
Any idea how should I use it to get "null" in case of a wrong alias?
EDIT
I'll try to provide a more concrete example
My table is as follows
CREATE TABLE user
(
id bigserial NOT NULL,
username character varying(200) NOT NULL,
last_name character varying(100),
created_user_id bigint NOT NULL,
modified_user_id bigint NOT NULL,
CONSTRAINT pk_user PRIMARY KEY (id),
CONSTRAINT user_username_key UNIQUE (username)
)
Data in tables
3;"admin";"admin";3;3
4;"test";"test";4;3
Code
//Input params
Long userId = 4L;
boolean includeModifiedUser = false;
User userTable = USER.as("userTable");
User modifiedUserTable = USER.as("modifiedUserTable");
SelectQuery selectQuery = create.selectQuery();
selectQuery.addFrom(userTable);
//In some cases I want to include the last modifier in the query
if (includeModifiedUser) {
selectQuery.addJoin(modifiedUserTable, JoinType.LEFT_OUTER_JOIN, modifiedUserTable.ID.eq(userTable.MODIFIED_USER_ID));
}
selectQuery.addConditions(userTable.ID.eq(userId));
Record record = selectQuery.fetchOne();
System.out.println(record.get(userTable.LAST_NAME)); //prints "test1"
System.out.println(record.get(modifiedUserTable.LAST_NAME)); //prints "test1", would expect null as modifiedUserTable is currently not joined
Tested on jooq 3.9.3 and 3.9.5
Works as designed
In SQL, there is no such thing as a qualified column name in a result set. Instead, a result set (like any other table) has a set of columns and each column has a name, which is described by jOOQ's Field.getName(). Now, "unfortunately", in top-level SELECT statements, you are allowed to have duplicate column names, in all SQL dialects, and also in jOOQ. This is useful when you join two tables and both tables have e.g. an ID column. That way, you don't have to rename each column just because an ambiguity arises.
If you do have duplicate column names in a table / result, jOOQ will apply the algorithm described in TableLike.field(Field)
This will return:
A field that is the same as the argument field (by identity comparison).
A field that is equal to the argument field (exact matching fully qualified name).
A field that is equal to the argument field (partially matching qualified name).
A field whose name is equal to the name of the argument field.
null otherwise.
If several fields have the same name, the first one is returned and a warning is logged.
As you can see, the rationale here is that if there is no full or partial identity or qualified name equality between a field in the result set and the field you're looking up in the result set, then the field name as in Field.getName() is used to look up the field.
Side note on column match ambiguity
At first, you've mentioned that there was an "ambiguous match" warning in the logs, which then disappeared. That warning is there to indicate that two columns go by the same Field.getName(), but neither of them is an "exact" match as described before. In that case, you will get the first column as a match (for historic reasons), and that warning, because that might not be what you wanted to do.
Related
I have a code that creates sql parameters using MapSqlParameterSource. Here is my code:
MapSqlParameterSource parameters = new MapSqlParameterSource()
.addValue(EVENT_ID, eventId)
.addValue(TYPE, type.toString())
.addValue(ACCOUNT_ID, null)
.addValue(USER_ID, null);
if (Type.SPOOFER_USER == type) {
parameters.addValue(USER_ID, account.getUser().getId());
}
else {
parameters.addValue(ACCOUNT_ID, account.getId());
}
Basically, if account type is spoofer, I have to have user id instead of account id. However, I don't like that I have to set account_id and user_id to null when I instantiate parameters. Is there way to set account_id and user_id as null so I don't have to write this two lines?:
MapSqlParameterSource parameters = new MapSqlParameterSource()
.addValue(EVENT_ID, eventId)
.addValue(TYPE, type.toString())
.addValue(ACCOUNT_ID, null) //////////////////////////THIS ONE
.addValue(USER_ID, null); //////////////////////////AND THIS ONE
Here is my sql query:
INSERT INTO database (id, event_id, type, account_id, user_id)
VALUES (database.nextval, :event_id, :type, :account_id, :user_id)
Update:
Maybe my question was not specific enough. What happens is that when I run
jdbcTemplate.update(insertEventExtra, parameters);
With the given parameters without making them "NULL", I get this exception in my unit test:
org.springframework.dao.InvalidDataAccessApiUsageException: No value supplied for the SQL parameter 'user_id': No value registered for key 'user_id'
I use hsql to test it. my .sql looks like this:
...
ID NUMBER(38,0) PRIMARY KEY,
EVENT_ID BIGINT NOT NULL,
TYPE VARCHAR2(20 BYTE) NOT NULL,
ACCOUNT_ID NUMBER(38,0),
GROUP_ID NUMBER(38,0),
USER_ID NUMBER(38,0),
...
So my specific question is that my test is giving me exception when I try to run test with parameters without setting them to null.
You must include the addValue(ACCOUNT_ID, null) and addValue(USER_ID, null) because your INSERT statement includes the two named parameters :account_id, :user_id.
The framework attempts to extract the values for the named parameters from the MapSqlParameterSource object and when it does not find one of them, it throws the exception. It does this to avoid user errors, because if you didn't intend to provide a value for a parameter, you wouldn't include the parameter in the INSERT statement.
Make your columns nullable and default to null in the database schema. Then, if you don't specify a value for a column when inserting, it should default to null
By default all not null columns have default value of NULL unless and until you provide value while inserting or updating the column.
I have tried using below query
#Query(value = "select E.SRC,E.TRGT,E.EVNT_STTS,E.ADDL_ATTR from EVNT.EVNT_LOG E where E.EVNT_ID=:eventId and E.ADDL_ATTR IS NULL" , nativeQuery = true)
List<AggregationLog> findByEventIdAndTargetAndAdditionalAttributeWithNULL(#Param("eventId") Long eventId);
in above query I am getting invalid column name.
Please guide me to select a row with eventID and addl_attr as null.
Well... invalid column means oracle cannot resolve the column name in the query. That usually means 1 of 2 things:
A Typo
The columns were created with case sensitivity. Whoever created the table surrounded the column names with double quotes and put column names in something other than all caps. Do a "DESCRIBE <table_name>" in sqlplus, sqldeveloper or any other client and check if the column names are all upper case. If they're not, you will need to enclose them in double quotes in your query with the name matching exactly what you see.
I'm trying to use GENERATED BY DEFAULT AS IDENTITY key for my record id's for my tables because a user needs to register themself and the user shouldn't be able to choose their own record id. So I decided to use GENERATED BY DEFAULT AS IDENTITY but I don't know how to write my INSERT statements.
This is my user table:
CREATE TABLE USER
(
ID_USER INT NOT NULL GENERATED BY DEFAULT AS IDENTITY,
USERNAME VARCHAR(20) UNIQUE NOT NULL,
FORENAME VARCHAR(20) NOT NULL,
SURNAME VARCHAR(20) NOT NULL,
PASSWORD VARCHAR(10) NOT NULL,
USER_TYPE INT NOT NULL,
PRIMARY KEY(ID_USER),
FOREIGN KEY (USER_TYPE) REFERENCES USER_TYPES(ID_TYPE)
);
and users will be allowed to register themselves.
This is what im using for my database
When a table has a column GENERATED BY DEFAULT AS IDENTITY it means that you can insert with a value in that column if you want to but you don't have to. So then in your insert, you could instead write
INSERT INTO ARTIST (ORIGIN,ARTIST_NAME) VALUES ('USA','Nirvana');
For reference: https://www.ibm.com/support/knowledgecenter/en/SSEPEK_11.0.0/apsg/src/tpc/db2z_identitycols.html
Edit after comment:
In the case where you need to retrieve the ID, #Mathias was correct that this is a duplicate question. A possible solution taken from this answer would be:
PreparedStatement result = cnx.prepareStatement(
"INSERT INTO ARTIST (ORIGIN,ARTIST_NAME) VALUES ('USA','Nirvana')",
RETURN_GENERATED_KEYS);
int updated = result.executeUpdate();
if (updated == 1) {
ResultSet generatedKeys = result.getGeneratedKeys();
if (generatedKeys.next()) {
int key = generatedKeys.getInt(1);
}
}
where key has the ID that you need for your next query.
The question is slightly different from the one already answered and needs a different answer.
The OP states: user shouldn't be able to choose their own record id. In that case the column definition should be ID_USER INT NOT NULL GENERATED ALWAYS AS IDENTITY to disallow any user-supplied value.
The table name shouldn't be USER as this is a reserved word. Try USERS instead.
The insert statement shouldn't insert into the ID_USER column. Similar to the example in the other answer, it should list the columns that are being inserted. An example below:
INSERT INTO USERS (USERNAME, FORENAME, SURNAME, PASSWORD, USER_TYPE)
VALUES ('JohnSmith','John', 'Smith', 'apasswrdx67', 3)
The OP wants to insert the generated value into another table using the GUI DatabaseManager. This is done by using the IDENTITY() function immediatly after inserting that row. For example,
INSERT INTO SOMETABLE (X, Y, Z) VALUES (IDENTITY(), 'some value', 'other value')
I'm having some issues with my ResultSet using JDBC.
Here's my relation:
create table person (
person_id number(5) generated always as identity
minvalue 1
maxvalue 99999
increment by 1 start with 1
cycle
cache 10,
firstname varchar(10) not null,
lastname varchar(10) not null,
);
I'm trying to insert a (firstname, lastname) into the tuple and then get the person_id that comes out of it. Here's my JDBC code:
//connection is taken care of beforehand and is named con
prep = con.prepareStatement("insert into person (firstname, lastname) values (?, ?)", Statement.RETURN_GENERATED_KEYS);
prep.setString(1, firstname);
prep.setString(2, lastname);
prep.execute();
ResultSet generated = prep.getGeneratedKeys();
if (generated.next()) {
String key = generated.getString("0");
System.out.println(key);
}
This works all fine. But my problem is that the key should be an integer, not a String. Every time I run this, I get a ResultSet that contains a string of "AAA3vaAAGAAAFwbAAG", or something along those lines. I want to get the person_id so I can use it later in my Java program.
Is there something I'm doing wrong in regards to searching through the ResultSet or the execution of the statement itself?
tl;dr
int id = generated.getInt( 1 ) ;
Details
Your Question seems confused.
There are two forms of each get… method on ResultSet.
Pass a column number (an int)
Pass a column name (a String)
You seem to have combined the two into this:
String key = generated.getString( "0" ) ;
I doubt that you have a column named with a single digit zero. Besides being a poor choice of name, standard SQL forbids starting an identifier with a digit.
So that line makes no sense. Perhaps you meant the first column by using a zero 0 and mistakenly wrapped it in quotes, thereby transforming your intended int into an actual String.
Even that intention would be wrong. The ResultSet::getString documentation incorrectly describes the int as an “columnIndex”. Usually “index” means a zero-based counting offset. But actually ResultSet::getString( int ) requires you pass an ordinal number with counting starting at one. So getString( 0 ) is never valid.
So if you want to retrieve the value of your result set’s first column as text, do this:
String key = generated.getString( 1 ) ; // Retrieve first column of result set as text.
Yet again, this would be wrong in the context of your code. You are apparently attempting to retrieve the primary key values being generated during the INSERT. Your primary key column person_id is defined as number(5) which is not a textual type. So retrieving as a String is not appropriate.
NUMBER(5) is not standard SQL. If you happen to be using Oracle database, the doc says that would be an integer type with a precision of five, meaning numbers with up to five digits. So retrieve that as a integer type in Java by calling ResultSet::getInt.
int id = generated.getInt( 1 ) ; // Retrieve the new row’s ID from the first column of the result set of generated key values returned by the `INSERT` prepared statement.
My comments above are for databases in general. But for Oracle specifically, see the Answer by Mark Rotteveel explaining that Oracle database does not return the generated sequence number when calling getGeneratedKeys. Instead it returns ROWID pseudo-column.
Your problem is that Oracle by default returns the ROWID of the inserted record, and not the generated identifier. From Oracle JDBC Developer's Guide: Retrieval of Auto-Generated Keys:
If key columns are not explicitly indicated, then Oracle JDBC drivers
cannot identify which columns need to be retrieved. When a column name
or column index array is used, Oracle JDBC drivers can identify which
columns contain auto-generated keys that you want to retrieve.
However, when the Statement.RETURN_GENERATED_KEYS integer flag is
used, Oracle JDBC drivers cannot identify these columns. When the
integer flag is used to indicate that auto-generated keys are to be
returned, the ROWID pseudo column is returned as key. The ROWID
can be then fetched from the ResultSet object and can be used to
retrieve other columns.
So, if you use Statement.RETURN_GENERATED_KEYS, you'll get the ROWID, and you can then use that ROWID to select the inserted row to obtain the other values (including the generated identifier).
If you want to specifically retrieve the generated id, for Oracle you'll need to explicitly ask for that column as follows:
String[] columns = { "PERSON_ID" }
prep = con.prepareStatement(
"insert into person (firstname, lastname) values (?, ?)", columns);
prep.setString(1, firstname);
prep.setString(2, lastname);
prep.executeUpdate();
ResultSet generated = prep.getGeneratedKeys();
if (generated.next()) {
int key = generated.getInt("PERSON_ID");
System.out.println(key);
}
I have added two columns in the sql to get the values through hibernate.My databse is oracle and those fields datatype i number. So i have created the beans with long and (tried Integer too) but when retrieving the values(executing the valuesquery).
Its giving me an error
org.hibernate.type.LongType - could not read column value from result set
java.sql.SQLException: Invalid column name
at oracle.jdbc.driver.OracleStatement.getColumnIndex(OracleStatement.java:3711)
at oracle.jdbc.driver.OracleResultSetImpl.findColumn(OracleResultSetImpl.java:2806)
at oracle.jdbc.driver.OracleResultSet.getLong(OracleResultSet.java:444)
at weblogic.jdbc.wrapper.ResultSet_oracle_jdbc_driver_OracleResultSetImpl.getLong(Unknown Source)
at org.hibernate.type.LongType.get(LongType.java:28)
at org.hibernate.type.NullableType.nullSafeGet(NullableType.java:163)
at org.hibernate.type.NullableType.nullSafeGet(NullableType.java:189)
tABLE DEFINITION :
CREATE TABLE "PRODUCTLIST"
(
PRICELIST_PUBLISH_KEY decimal(22) NOT NULL,
PRODUCT_NBR varchar2(54) NOT NULL,
PRODUCT_KEY decimal(22),
PRODUCT_DESCRIPTION varchar2(360),
PRODUCT_FAMILY_NBR varchar2(30),
PRODUCT_FAMILY_DESCR varchar2(180),
PRODUCT_GROUP_NBR varchar2(30),
PRODUCT_GROUP_DESCR varchar2(180),
PRODUCT_LINE_NBR varchar2(30),
PRODUCT_LINE_DESCR varchar2(180),
PRODUCT_CLASS_CODE varchar2(6),
LAST_PP_GENERATED_DATE_KEY decimal(22),
LAST_PP_GENERATED_DATE date,
PUBLISH_PERIOD_KEY decimal(22) NOT NULL,
PUBLISH_PERIOD_DATE date,
PL_KEY decimal(22),
PRODUCTLIST varchar2(750),
SALES_KEY decimal(22),
PRODUCT varchar2(60),
DM_EXTRACTED_BY_USER varchar2(90)
)
sql :
Query query = session.createSQLQuery(channelQuery)
.addScalar("PRODUCT",Hibernate.STRING)
.addScalar("PRODUCTLIST",Hibernate.STRING)
.addScalar("PRODUCTKEY",Hibernate.LONG)
.addScalar("SALESKEY",Hibernate.LONG)
.setResultTransformer(Transformers.aliasToBean(SearchResult.class));
return query.list();
}
});
Please help me to fix the issue ?
In your table definition, I can't see all the fields you're using in the addScalar() methods: there are no PRODUCTKEY nor SALESKEY fields. Instead I can see a PRODUCT_KEY and a SALES_KEY fields (underscores). I think you should use the correct name of the fields in the addScalar() methods.
But if your query is the one you put in your comments, you have to correct some details:
you should use p instead of pub as alias for the table name. As there is only one table in the query, you can suppress the alias.
In your SELECT clause, p.productprice is not an existing field in your table. Maybe you want to use p.pricelist instead.
In your WHERE clause, p.productnbr is not an existing field in your table. You should use p.product_nbr instead.
Then you should change the field names in the addScalar() methods to match those you are using in the query.
Modified query
SELECT distinct p.product, p.productlist, p.PL_KEY, p.SALES_KEY
FROM productlist p
WHERE p.product_nbr in ('1002102')
Your code should be:
Query query = session.createSQLQuery(channelQuery)
.addScalar("PRODUCT",Hibernate.STRING)
.addScalar("PRODUCTLIST",Hibernate.STRING)
.addScalar("PL_KEY",Hibernate.LONG)
.addScalar("SALES_KEY",Hibernate.LONG)
.setResultTransformer(Transformers.aliasToBean(SearchResult.class));
return query.list();
If you define aliases in your query, then you can use the alias names instead of the field names. For example, with this query:
SELECT distinct p.product, p.productlist, p.PL_KEY as PRODUCTKEY, p.SALES_KEY as SALESKEY
FROM productlist p
WHERE p.product_nbr in ('1002102')
you can use the following code (it's your original code):
Query query = session.createSQLQuery(channelQuery)
.addScalar("PRODUCT",Hibernate.STRING)
.addScalar("PRODUCTLIST",Hibernate.STRING)
.addScalar("PRODUCTKEY",Hibernate.LONG)
.addScalar("SALESKEY",Hibernate.LONG)
.setResultTransformer(Transformers.aliasToBean(SearchResult.class));
return query.list();