JPQL FROM section - java

I want to optimize the number of queries in the database. At the moment on REST the list of devices comes. Need to check if new devices have been added. Now it works like this: all devices for the current user are selected from the database and a check(with a list received from the request) for the presence of new devices. I want to translate all the work into a database and do something like this:
select p
from :firstParam p
where p.sdauId NOT IN (select t.id
from Equipment t
where t.owner.id = :secondParam)
Param ":firstParam" is a list of devices received from the request. ":secondParam" is a user id.
Can i use the section "from" like that? After reading the documentation and making many attempts to make a similar request, nothing came of it. I will be grateful for any tips on writing a request or approach to solving such a problem.

Database object names (e.g. database, table, column names) cannot be bound using a placeholder in a prepared statement. So, you'll have to hard code the name of the first table:
select p
from yourTable p -- cannot use a parameter for table names
where p.sdauId NOT IN (select t.id from Equipment t where t.owner.id = :secondParam)

Related

SQL/JPA want to select data for a list of ids filtering by a specific timestamp range for each id

I have a table with some data, there are record id, userIds, timestamp and data columns. Receive from the client a list of userIds, initially, I just had to fetch data by the same timestamp range for all useIds, just using userId IN (list). However, now I'm required to get data by different timestamp ranges for each userId, let's say userID 1 needs data from 1643580000000 to 1646431200000 and userId 2 from 1626418800000 to 1647500400000 (utcTimestamp in mills).
Most probably I'll receive a list of [userId, startTime, endTime], so, I was considering to loop the main query for each userId with its respective timestamp range (I've seen it's a bad idea due to performance, but if I have to, I have to), but I also found out about cursors (not much experience here).
I wanted to know if it's possible to get what I want without loops or cursors, and if not, best way with each one.
Thanks in advance!
Notes: using MariaDB. SQL query will be used as nativeQuery in a Java service repository.
You can run a query laike this.
Of course you have to add for every user his own time range
SELECT * FROM mytable
WHERE (userID = 1 AND `timestamp` BETWEEN 1643580000000 AND 1646431200000)
OR (userId = 2 AND `timestamp`BETWEEN 1626418800000 AND 1647500400000)
If you have a lot of rows and not that many id to process, you can do it with
SELECT * FROM mytable
WHERE (userID = 1 AND `timestamp` BETWEEN 1643580000000 AND 1646431200000)
UNION
SELECT * FROM mytable
WHERE (userId = 2 AND `timestamp`BETWEEN 1626418800000 AND 1647500400000)
Here also you need to add for every id another UNION

Insert values in many to many relationship tables with JOOQ

I have three tables in my database, SUBSCRIPTION, USER_ID, and an association table called SUBSCRIPTION_USER_ID.
My strategy is to use JOOQ batch with three queries, the first one to insert on row into SUBSCRIPTION, the second query to insert multiple rows into USER_ID, and finally, I need to insert the association IDs into SUBSCRIPTION_USER_ID, so I did the following:
InsertValuesStep2 insertUserIds = insertInto(
USER_ID, USER_ID.USER_ID_TYPE, USER_ID.USER_ID_VALUE);
for (String userId : subscriptionDTO.getUserId())
insertUserIds = insertUserIds.values(getValue(0, userId), getValue(1, userId));
InsertReturningStep insertReturningUserIds = insertUserIds.onConflictDoNothing();
InsertResultStep insertReturningSubscription = insertInto(SUBSCRIPTION)
.set(SUBSCRIPTION.CHANNEL_ID, subscriptionDTO.getChannel())
.set(SUBSCRIPTION.SENDER_ID, subscriptionDTO.getSenderId())
.set(SUBSCRIPTION.CATEGORY_ID, subscriptionDTO.getCategory())
.set(SUBSCRIPTION.TOKEN, subscriptionDTO.getToken())
.onConflictDoNothing()
.returningResult(SUBSCRIPTION.ID);
Unfortunately, to insert values into the association table, I tried many ways but nothing works for me, finally, I tried to insert values in SUBSCRIPTION_USER_IDusing with select but It doesn't work:
InsertValuesStep insertValuesSubscriptionUserIds = insertInto(
SUBSCRIPTION_USER_ID,
SUBSCRIPTION_USER_ID.SUBSCRIPTION_ID,
SUBSCRIPTION_USER_ID.USER_ID_ID)
.select(select(SUBSCRIPTION.ID, USER_ID.ID)
.from(SUBSCRIPTION)
.innerJoin(USER_ID)
.on(concat(USER_ID.USER_ID_TYPE,
val(CATEGORY_USER_ID_DELIMITER),
USER_ID.USER_ID_VALUE).in(subscriptionDTO.getUserId())
.and(SUBSCRIPTION.SENDER_ID.equal(subscriptionDTO.getSenderId()))
.and(SUBSCRIPTION.CHANNEL_ID.equal(subscriptionDTO.getChannel()))
.and(SUBSCRIPTION.CATEGORY.equal(subscriptionDTO.getCategory()))
.and(SUBSCRIPTION.TOKEN.equal(subscriptionDTO.getToken()))));
Am I missing something above? Is there a better way using JOOQ to insert many-to-many relationship values or to use queries results as parameters for other queries?
I'm assuming you posted your entire code. In case of which:
You don't call execute on your USER_ID insertion
Simply add
insertUserIds.onConflictDoNothing().execute();
Or alternatively, fetch the generated IDs using a call to returning().fetch()
Inner join
This might just be a stylistic question, but what you seem to be doing is a cross join. Your INNER JOIN filters aren't really join predicates. I'd put them in the WHERE clause. Clarity may help avoid further problems in such a query.
Specifically, that first "join predicate" is very confusing, containing a CONCAT call, which isn't something one would see in an INNER JOIN every day, and only touches one table, not both:
.on(concat(USER_ID.USER_ID_TYPE,
val(CATEGORY_USER_ID_DELIMITER),
USER_ID.USER_ID_VALUE).in(subscriptionDTO.getUserId())
Wrong predicate
That last predicate seems wrong. You're inserting:
.set(SUBSCRIPTION.TOKEN, subscriptionDTO.getToken())
But you're querying
.and(SUBSCRIPTION.TOKEN.equal(subscriptionDTO.getContactId()))));
That should probably be subscriptionDTO.getToken() again
As mentioned above, I have inserted values for SUBSCRIPTION and USER_ID tables. And get for the association table I need to get the IDs of the already inserted values from the above two tables, so to solve the issue I've used this query to insert in SUBSCRIPTION_USER_ID:
InsertReturningStep insertReturningSubscriptionUserId = insertInto(
SUBSCRIPTION_USER_ID,
SUBSCRIPTION_USER_ID.SUBSCRIPTION_ID,
SUBSCRIPTION_USER_ID.USER_ID_ID)
.select(select(SUBSCRIPTION.ID, USER_ID.ID).from(SUBSCRIPTION
.where(concat(USER_ID.USER_ID_TYPE, val(CATEGORY_USER_ID_DELIMITER), USER_ID.USER_ID_VALUE).in(subscriptionDTO.getUserId()))
.and(SUBSCRIPTION.SENDER_ID.equal(subscriptionDTO.getSenderId()))
.and(SUBSCRIPTION.CHANNEL_ID.equal(subscriptionDTO.getChannel()))
.and(SUBSCRIPTION.CATEGORY.equal(subscriptionDTO.getCategory()))
.and(SUBSCRIPTION.TOKEN.equal(subscriptionDTO.getToken()))).onConflictDoNothing();
Finally, I have executed all the queries using batch:
using(configuration).batch(insertReturningSubscription,
insertReturningUserIds,
insertReturningSubscriptionUserId).execute()

How can I prevent duplicated insert in oracle database with Java?

Let me give the example first. It is a log table.
User A subscribe the service A = OK
User A unsubscribe the service A = OK
User A subscribe the service A again = OK
User A subscribe the service A again = Not OK, because you can't subscribe same service at the same time.
Sometimes the client goes crazy and send 5 subscribe requests at the same time ( 4 tomcat servers behind), if I do nothing in this situation then 5 same records will be inserted.
As you can see, I can't use unique constraint here.
I guess perhaps I can use some single thread block in Oracle, but not sure..
I tried "merge" , but I guess it is used in specific records instead of last record.
begin single thread
select the last record
if the last record is the same then don't insert.
if the last record is not the same then insert.
end single thread
Is it possible and how to achieve ?
Perhaps you need to check for the user id, and service type. if same user trying to subscribe same service before the previous subcribed service is performed, then alert the user.
or maybe you want to limit the user to subscribe in only some given duration, say: user can only subscribe same service in each 1 day
You can update the record if the record already exist, for example:
Make a query to check if the record with particular user and service is exist:
SELECT * FROM table WHERE userid = userid AND serviceid=serviceid
If the query return any result, means its exist. then do update:
UPDATE table SET column1='value', column2='value2' ... WHERE userid = userid AND serviceid = serviceid
else, if no result returned, means the user haven't subscribe the service. then insert record:
INSERT INTO table(column1, column2, ...) values ('value1', 'value2', ...)
I think you could solve this problem with constraint. When user subscribes it inserts a row when it unsubscribes it deletes it. A row must be unique for same user and same service.
If you do not want to delete rows add ACTIVE column to this table and make constraint on USER + SERVICE + ACTIVE.
I do not fully understand your problem, but it seems you need to implement mutual exclusion somewhere. Have you tried with a SELECT ... FOR UPDATE?
http://www.techonthenet.com/oracle/cursors/for_update.php
I tried "MERGE" and subquery to solve this case.
By the way, this problem is only happened when subscribe. First, I get the status(subscribe or unsubscribe) from the last record of a user and the service. If the last status in table is 'subscribe', means this subscribed request might be the duplicated one.
MERGE INTO subscr_log M
USING
(SELECT status
FROM subscr_log
WHERE rid=
(SELECT MAX(rid)
FROM monthly_subscr_log
WHERE SCRID ='123456'
AND service_item='CHANNEL1'
)
) C
ON (C.status ='SUB' ) -- try to see the last record is subscribe or not
WHEN MATCHED THEN
UPDATE SET M.REASON='N/A' WHERE M.STATUS='XXXXXXX' --do impossible sql
WHEN NOT MATCHED THEN
INSERT VALUES (9999,8888,'x','x','x','x','x','x','x','x','x',sysdate,'x','x','x','x');

extract column names from sql query after where clause

I've a requirement where I need to pull out data from database.
The query is-
SELECT e.Data AS EntityBlob, f.Data AS FpmlBlob
FROM [Trades.InventoryRecord] ir, EntityBlob e, FpmlBlob f
WHERE %s AND uid = e.uid AND uid = f.uid
Here %s is the predicate after where clause which user will input from an html form.
User input will be in this form :
1. TradeDate = '2013-04-05' AND IsLatest = 'TRUE'
2. StreamId= 'IA0015'
3. The query may have IN clause also
Now when this query is rendered I get exception ambigous column streamId or ambigous column IsLatest, as these columns exists in more than one table with same name. So to remove this ambiguity I need to modify the query as - ir.IsLatest or ir.StreamId
To do so by java code, I need to first parse the predicate after where clause, extract column names and insert table name alias- 'ir' before each column name so that the query becomes -
SELECT e.Data AS EntityBlob, f.Data AS FpmlBlob
FROM [Trades.InventoryRecord] ir, EntityBlob e, FpmlBlob f
WHERE ir.TradeDate = '2013-04-05' AND ir.IsLatest = 'TRUE' AND uid = e.uid AND uid = f.uid
what is the best way to parse this predicate, or if there is any other way I can achieve the same result?
My answer to this question is to not parse the user input - there is far too much that can go wrong. It would be a lot better to have a UI with drop downs and buttons for selecting equality, inequality, ranges, in statements, etc. It may seem like more work, but protecting yourself from a SQL injection attack is even more. And even if you are not concerned about malicious SQL injection, then the user still has to get every thing exactly right, or the statement fails.

Does the HQL IN clause only accept a limited number of values (MySQL database)?

Here is my simplified database:
user_post
- id
- post_id (FK)
post
- id
- link_id (FK)
link
- id
I've a list of user_post and I wanna get for each of them the link they are linked to.
Currently, I'm doin it like this:
SELECT userPost.post.link FROM UserPost userPost WHERE userPost IN (:userPosts)
Works great, but maybe later, I'll get a huge number of user_post, so there'll be a lot of values within the IN clause (100 < x < 5000+).
Is IN limited? Is there any other way to do it? Before, I was doin it like this:
for (UserPost userPost : user.getUserPosts()) {
Link link = userPost.getPost().getLink();
//
}
But the solution above takes really a lot of time to get executed, so I'm lookin for a more optimized one.
Yes, the IN clause is limited:
The number of values in the IN list is only limited by the
max_allowed_packet value.
In Oracle, the limit is set to 1000 elements.
Instead of selecting user posts, and then iterating through them to get their link or issuing another query to get them, you could load the links in the same query as the user posts:
select userPost from UserPost userPost
left join fetch userPost.post post
left join fetch post.link link;
This query will load eveything at once, and iterating theough the user posts and getting their link using userPost.getPost().getLink() won't trigger any additional query.

Categories

Resources