how can I return multiple row counts - mysql

I have three entities, User, Rule, and Activity
with the following relationship, One rule has many activities (bidirectional)
Many users have many activities(bidirectional), the server is with spring using spring jpa
I want to show how many Activities has one user started but not finished from every rule, with SQL this query would return that from one rule
SELECT COUNT(*) FROM user_activity WHERE user_id=userId AND score<10 AND
activity_id IN (SELECT id FROM activity WHERE rule_id=ruleId);
I could do that with JPQL, but how could I return that count from every rule?
so I could have from every rule, how many activities has that User
if someone needs it I can provide java model code too.
tables are User, Activity, Rule and User_Activity

I think this query is probably what you want. It finds all the activities associated with a given rule, then joins the users participating in that activity who have not finished it (I presume that's what score<10 indicates?)
SELECT r.id, u.id, COUNT(a.id)
FROM rule r
JOIN activity a ON a.rule_id = r.id
JOIN user_activity ua ON ua.activity_id = a.id AND ua.score < 10
JOIN user u ON u.id = ua.user_id
GROUP BY r.id, u.id
Obviously you can expand the SELECT to include things like the rule description and the user name etc.
I couldn't see any indication of what "activity complete" means so I've presumed it's that the score is less than 10. If that's not the case, you would need to change the join to user_activity appropriately.

Related

Performing bidirectional join queries on tables with one to many relationship

I wanna have a bidirectional relationship between my two tables that are related by a one to many relationship.
So, i have a User table, with columns such as user_id, and transaction_id etc (and other user datails columns).
I have another table called transaction which has columns like transaction_id and user_id (to refer to the user this particular transaction belongs to).
Now, the issue is, a particular user can have multiple transactions attached to them, which means, this transaction table can have multiple rows for the same user. This makes it easy for me to get user details via join query. But I want to make this relationship bi-directional.
As in, if all i have is user details, i should be able to get the transaction details of that person. but since, a user can have multiple transactions at once, I'm not able to write a join query.
As such, I am yet to figure out how to store two transaction id's for a particular user, keeping in mind that i should be able to make a join query having the user details (I could always store transaction id's in the form of an array, but that won't let my join query happen).
Assuming this table structures :
transactions
id
user_id
tdate
amount
user
id
name
email
you seem to be already aware that you can list all transactions of a user, along with his information, with :
SELECT
u.id,
u.name,
u.email,
t.id,
t.tdate,
t.amount
FROM
users u
INNER JOIN transactions t ON t.user_id = u.id
WHERE
u.id = ?
ORDER BY
u.id,
t.tdate
The question mark represents the id of the user whose transactions you want to see. This returns one record for each transaction of the user.
If you are looking to, for a given transaction, list all other transactions of the same user, then one solution is to add another join to the query : basically, this would first retrieve the given transaction by id (t0), and then list all transactions by the same user (t).
SELECT
u.id,
u.name,
u.email,
t.id,
t.tdate,
t.amount
FROM
users u
INNER JOIN transactions t0 ON t0.user_id = u.id
INNER JOIN transactions t ON t.user_id = u.id
WHERE
t0.id = ?
ORDER BY
u.id,
t.tdate

Querying a large table using mysql

I manage a property website. I have a table with banned users (small table) and a table called advert_views which keeps track of each listing that each user views (currently 1.3m lines and growing). The advert_views table alsio takes note of the IP address for every advert viewed).
I want to get the IP addresses used by the banned users and check if any of these banned users have opened new accounts. I ran the following query:
SELECT adviews.user_id AS 'banned user_id',
adviews.client_ip AS 'IPs used by banned users',
adviews2.user_id AS 'banned users that opened a new account'
FROM banned_users
LEFT JOIN users on users.email_address = banned_users.email_address #since I don't store the user_id in banned_users
LEFT JOIN advert_views adviews ON adviews.user_id = users.id AND adviews.user_id IS NOT NULL # users may view listings when not logged in but they have restricted access to the information on the listing
LEFT JOIN (SELECT client_ip,
user_id
FROM advert_views
WHERE user_id IS NOT NULL
) adviews2
ON adviews2.client_ip = adviews.client_ip
WHERE banned_users.rec_status = 1 and adviews.user_id <> adviews2.user_id
GROUP BY adviews2.user_id
I applied an index on the advert_views table and the users table as per below:
enter image description here
My query takes half an hour to execute. Is there a way how to improve my query speed?
Thanks!
Chris
First of all: Why do you outer join the tables? Or better: Why do you try to outer join the tables? A left join is meant to get data from a table even when there is no match. But then your results could contain rows with all values null. (That doesn't happen though, because adviews.user_id <> adviews2.user_id in your where clause dismisses all outer-joined rows.) Don't give the DBMS more work to do than necessary. If you want inner joins, then don't outer join. (Though the difference in execution time won't be huge.)
Next: You select from banned_users, but you only use it to check existence. You shouldn't do this. Use an EXISTS or IN clause instead. (This is mainly for readability and in order not to produce duplicate results. This probably won't speed things up.)
SELECT av1.user_id AS 'banned user_id',
av2.client_ip AS 'IPs used by banned users',
av2.user_id AS 'banned users that opened a new account'
FROM adviews av1
JOIN adviews av2 ON av2.client_ip = av1.client_ip AND av2.user_id <> av1.user_id
WHERE av1.user_id IN
(
SELECT user_id
FROM users
WHERE email_address IN (select email_address from banned_users where rec_status = 1)
)
GROUP BY av2.user_id;
You may replace the inner IN clause with a join. It's mostly a matter of personal preference, but it is also that in the past MySQL sometimes didn't perform well on IN clauses, so many people made it a habit to join instead.
WHERE av1.user_id IN
(
SELECT u.user_id
FROM users u
JOIN banned_users bu ON bu.email_address = u.email_address
WHERE bu.rec_status = 1
)
At last consider removing the GROUP BY clause. It reduces your results to one row per reusing user_id, showing one of its related banned user_ids (arbitrarily chosen in case there is more than one). I don't know your tables. Are you getting many records per reusing user_id? If not, remove the clause.
As to indexes I suggest:
banned_users(rec_status, email_address)
users(email_address, user_id)
adviews(user_id, client_ip)
adviews(client_ip, user_id)

Join another table with multiple rows to another table's single result

I currently select a single row (a post):
SELECT s.id AS id,s.date,s.title,s.views,s.image,s.width,s.description,u.id AS userId,u.username,u.display_name,u.avatar,
(select count(*) from comments where item_id = s.id and type = 1) as numComments,
(select count(*) from likes where item_id = s.id and type = 1) as numLikes,
(select avg(value) from ratings where showcase_id = s.id) as average,
(select count(*) from ratings where showcase_id = s.id) as total
FROM showcase AS s
INNER JOIN users AS u ON s.user_id = u.id
WHERE s.id = :id
LIMIT 5
Then get comments for that post in a separate query:
SELECT c.id as c_id,c.text,c.date,u.id as u_id,u.username,u.display_name,u.avatar
FROM comments as c
INNER JOIN users as u ON c.user_id = u.id
WHERE item_id = :item_id AND type = :type
:id and :item_id are the same. However, the comments return multiple rows whereas the first query returns one row - is there a way to join the comments to the first query or is the current way fine?
It really depends on your application.
If we are talking about a few records returned from a small or medium table, and if the query is executed just a few times a day, then it wouldn't matter much if:
you work with two record sets (two different queries are executed
and then their results are put together);
you join the two queries, copying the post information for each record from the comments query;
you build a XML with the comments and join it to the record returned in the first query (the post record).
Another factor to take in consideration is whether the post and it's comments are displayed at the same time. If this is NOT the case and the comments are not visible at first and displayed only after some action like the click of a button, then you should chose the 1st option above, for performance reasons.
But if both the post information it's comments must be displayed at the same time, then you should chose one of the 3 options above. Which one is more of a personal favorite in modeling your application data structures and it's database access layer.
Now, if the volume of data may get huge, then you should dig a little deepen and run some simulations to find the query(ies) that give you the optimal performance.

Mysql order by left join date

I have two tables, users and sessions. Each user can have multiple sessions. I want to get user name and the date of his most recent session. But I need to do this in such a way as to be able to return the array sorted by date of last session. Also, some of the users may not have sessions so the left join will return nothing for date.
SELECT u.name, max(s.sdate) FROM users u
LEFT JOIN sessions s ON s.uid=u.id
ORDER BY s.sdate
LIMIT 25,25
Clearly this won't work but I'd like something simple. Something similar? Note that I also need to specify which set of rows to return because of pagination. This would be page 2. I did see similar posts but they were overly complex for my use.
I have kept this simplified to keep it easy to understand.
I believe in your case u need only to group your results by user.
try
SELECT u.name, max(s.sdate) AS dt FROM users u
LEFT JOIN sessions s ON s.uid=u.id
GROUP BY u.id
ORDER BY dt
LIMIT 25,25

Selecting data from multiple tables, where a specific table does not contain any identifiable columns?

I have a dilemma.
Let's assume(for simplicity's sake) I have four tables, with different numbers of columns and rows, they are: users, mail, events and service.
When I receive a request, I have an ID that links on three of those tables, but with different columns it matches to.
Let's say, users matches on user_id, mail matches on user_ref and events matches on user_ref as well.
That would've been a fine query for me to write up, even with single, multiple or even all IDs.
The problem arrives on the next step I have to take, and that's the *service table.
The service table doesn't conform to the same standards of the others, thus it does not have an user_id, or user_ref that can be pulled.
What it has instead, is a *mail_ref* column, and it has the potential to contain duplicates.
My current method is trying to use an IN() method, but it only works for selecting a single user/row.
Here's my current query:
SELECT
u.Name as Name,
COUNT(m.user_ref) AS Mail_total,
e.mail_id,
COUNT(e.user_ref) AS Event_total,
COUNT(s.mail_ref) AS service_total
FROM
users u
LEFT JOIN
mail m ON m.user_ref = u.user_id
LEFT JOIN
service s ON s.mail_ref IN(e.mail_id)
LEFT JOIN
events e ON e.user_ref = u.user_id
WHERE u.user_id IN(my,list,of,ids)
GROUP BY s.mail_ref
The problem I have with it currently, is that although it's selecting the correct data, it's not selecting unique data for every id I specify.
It works marginally fine when given a single id, but as mentioned above, not when it has to retrieve multiple rows.
If anyone could help me out it would be much appreciated.
Do a subquery in the left join for service. Instead of:
LEFT JOIN
service s ON s.mail_ref IN(e.mail_id)
Try
LEFT JOIN
(select TOP 1 mail_ref from server) as S on s.mail_ref = e.mail_id
See if that works.
SELECT
u.Name as Name,
(select count(*) from mail m where m.user_ref = u.user_id) AS Mail_total,
e.mail_id,
(select count(*) from events e where e.user_ref = u.user_id) AS Event_total,
(select count(*) from
events e
inner join services s on s.mail_ref = e.mail_id
where
e.user_ref = u.user_id) as service_total
FROM
users u
WHERE u.user_id IN(my,list,of,ids)