MySQL fetch rows if every columns equals the same thing - mysql

I have a table like this :
---------------------------------------
| Actions |
---------------------------------------
| action_id | user_id | action_active |
---------------------------------------
| 1 | 1 | 0 |
---------------------------------------
| 2 | 2 | 1 |
---------------------------------------
| 3 | 1 | 0 |
---------------------------------------
| 4 | 2 | 0 |
---------------------------------------
I want to retrieve all rows where a user has all of his rows as action_active = 0. If he has just one action_active as 1, don't retrieve it.
In this example, it should only retrieve the row 1 and 3, since the user 1 has all of his rows at action_active = 0.
I thought about something like this, but I'm not sure about how right it is :
SELECT *
FROM Actions AS a
WHERE action_active = ALL (SELECT action_active FROM actions as s WHERE action_active = 0 where a.idx_user = s.idx_user)
I'm not sure my query is right.
Thanks !

Calculate sum in a sub-query to find users with all zero values and join that with main select
SELECT a.*
FROM actions a
JOIN (SELECT user_id, SUM(action_active) AS sum
FROM actions
GROUP BY user_id) AS sum_a ON sum_a.user_id = a.user_id
WHERE sum = 0

Use NOT EXISTS:
SELECT a.*
FROM actions a
WHERE NOT EXISTS (SELECT 1
FROM actions a2
WHERE a2.user_id = a.user_id AND
a2.action_active <> 0
);
This should have better performance than a solution using group by -- and this makes direct use use of an index on actions(user_id, action_active).
You can also phrase this using a LEFT JOIN:
SELECT a.*
FROM actions a LEFT JOIN
actions a2
ON a2.user_id = a.user_id AND a2.action_active <> 0
WHERE a2.user_id IS NULL;

Related

Select all items and count in related table by criteria

I have tables Match and Reaction as following:
REACTION
+----------+----------+----------+----------+
| user_id | game_id | item_id | reaction |
+----------+----------+----------+----------+
| 1 | 1 | 1 | 1 |
| 1 | 1 | 2 | 1 |
| 2 | 1 | 1 | 1 |
| 2 | 1 | 2 | 0 |
+----------+----------+----------+----------+
MATCH:
+----------+----------+
| game_id | item_id |
+----------+----------+
| 1 | 1 |
| 1 | 2 |
+----------+----------+
Now I want (if possible without subqueries) to select ALL item_ids from MATCH table AND count of rows where field reaction in table Reaction is equal to 1 for user with id = 2. For example, for defined tables I want to get following results:
+----------+----------+
| item_id | count |
+----------+----------+
| 1 |  1 |
| 2 | 0 |
+----------+----------+
I've tried something like
SELECT match.item_id, COUNT(reaction.user_id) as c
FROM match
LEFT JOIN reaction ON reaction.item_id = match.item_id
WHERE reaction.reaction = 1 AND match.game_id = 2
GROUP BY match.item_id
HAVING c > 0
but it didn't work as expected. I cannot get count for particular user.
I think you are close. I think you just need to move conditions on the second table to the ON clause:
SELECT m.item_id, COUNT(r.user_id) as c
FROM match m LEFT JOIN
reaction r
ON r.item_id = m.item_id AND
r.reaction = 1 AND
r.user_id = 2
WHERE m.game_id = 2
GROUP BY m.item_id;
I'm not sure what the HAVING clause is for, because you seem to want counts of 0.
Note that this also introduces table aliases so the query is easier to write and to read.
SELECT match.item_id, COUNT(reaction.user_id) as c
FROM match JOIN reaction ON (reaction.item_id = match.item_id and reaction.reaction = 1 AND match.game_id = 2)
GROUP BY match.item_id
HAVING COUNT(reaction.user_id)
I think you need to filter 'before' join -> so use the 'on' clause.
Filters in where are applied after the join is made while filter applied on on clause are applied before the join is made
You have not game_id = 2 so this should return no value
and you should not use left joined table columns in where condition otherwise these wprk as inner join ... in these cases you shou move the related condition in ON clause
SELECT match.item_id, COUNT(reaction.user_id) as c
FROM match
LEFT JOIN reaction ON reaction.item_id = match.item_id
AND reaction.reaction = 1
WHERE match.game_id = 2
GROUP BY match.item_id
HAVING c > 0
but try also
SELECT match.item_id, COUNT(reaction.user_id) as c
FROM match
LEFT JOIN reaction ON reaction.item_id = match.item_id
AND reaction.reaction = 1
GROUP BY match.item_id

MySQL - Select N random rows of a different user where only value is contained less or equal toX

I want to select N random rows from a table, but in all of these rows a specific value may only occur X times.
Table "reviews":
*--------------------*
| ID | CODE_REVIEWER |
*--------------------*
| 1 | 2 |
| 1 | 3 |
| 1 | 4 |
*--------------------*
Table "users" (I left out a lot of unimportant stuff:
*----*
| ID |
*----*
| 1 |
| 2 |
| 3 |
| 4 |
| 5 |
*----*
Example output:
For X = 3:
*-----------*
| REVIEWER |
*-----------*
| 4 |
| 1 |
| 5 |
*-----------*
For X = 2:
*-----------*
| REVIEWER |
*-----------*
| 1 |
| 5 |
| 3 |
*-----------*
For X = 1 (empty):
*-----------*
| REVIEWER |
*-----------*
So, it must be a ResultSet containing a few IDs that are different from the ID X, but these IDs may only occur in "table 2" as a "code_reviewer" N times.
So everybody can be the "reviewer" FOR 3 people, and everbody can be reviewed BY 3 people
Thanks!
Edit:
This is what I got so far:
select newid from (select id, count(*) as num from (select * from users
where id != ?) as users group by id order by RAND() LIMIT ?) as sb
where num < 3 and newid not in (select code_reviewer from reviews where id = ?)
It works perfectly, apart from that it sometimes returns for example
*---*
| 2 |
| 1 |
| 2 |
*---*
(Contains the 2 twice, which shouldn't be so)
Unfortunately, I know MSSQL and not MySQL. I will try to answer using MSSQl, and hopefully that will lead you in the right direction.
I use variables to determine how many rows I should return, and then use a simple NEWID to act as a randomizer. (It is my understanding that you would order by RAND() in MySQL instead of NEWID())
declare #userId int
select #userId = 1
declare #existingReviewCount int
select #existingReviewCount = COUNT(*) from Reviews where Id = #userId
declare #requiredRowCount int
select #requiredRowCount = 3 - #existingReviewCount
select top (#requiredRowCount) Id from Users
where #userId != Id
order by NEWID()
Now replace #userId with 1 and it will return an empty set.
This seems to be essentially a top n per group problem. There are a few ways to solve that. Here is a quick and dirty way that will give you a comma separated list of id's that you need. If you want to just explode these in your code you are good to go.
select u.*,
-- r_counts.cnt as reviews_count,
substring_index(
group_concat(u_rev.id order by rand()),
',',
greatest(3-r_counts.cnt,0)) as reviewers
from users u
join users u_rev on u.id != u_rev.id
left join (
select u.id, count(r.id) as cnt
from users u
left join reviews r on u.id = r.id
group by u.id
) r_counts on r_counts.id = u.id
left join (
select u.id, count(r.id) as cnt
from users u
left join reviews r on u.id = r.reviewer
group by u.id, r.reviewer
) as did_review_counts
on did_review_counts.id = u_rev.id
where u.id = 11
and did_review_counts.cnt < 3
group by u.id;
If you need the results another way, google "top n per group mysql" and check out some of the solutions there.
Note: the 3 above would be your review number target. Edit: Now this would need to be run only 1 at a time. Then rerun after each review was done.

SELECT not working when using != in WHERE clause (using GROUP BY and HAVING COUNT)

This question is based on: Select row from left join table where multiple conditions are true
I am now trying to select rows from Table 1, which do not have a connection in Table 2 to a certain property ID.
These are the tables:
Table 1
| ID | Name |
| 1 | test |
| 2 | hello |
Table 2
| ID | PropertyID |
| 1 | 3 |
| 1 | 6 |
| 1 | 7 |
| 2 | 6 |
| 2 | 1 |
I am using the following query (which is working with '='):
SELECT tab1ID
FROM table2
WHERE propertyID != 3 OR propertyID = 6
GROUP BY tab1ID
HAVING COUNT(*) = 2;
This query should return ID=2, but it returns zero rows. What I am doing wrong?
Any help is greatly appreciated!
Edit: I had given a MWE but this is my actual query:
SELECT transactionline.total FROM transactionline
LEFT JOIN product_variant ON product_variant.SKU = transactionline.SKU
LEFT JOIN product ON product_variant.productID = product.productID
LEFT JOIN connect_option_product ON connect_option_product.productID = product.productID
LEFT JOIN productattribute_option ON productattribute_option.optionID = connect_option_product.optionID
WHERE productattribute_option.optionID = 4 OR productattribute_option.optionID = 9
GROUP BY transactionline.lineID
HAVING COUNT(*) = 1
AND SUM(productattribute_option.optionID = 4) = 0
AND SUM(productattribute_option.optionID = 9) > 0
A product can have multiple connections to the optionID's. The goal of this query is to select the total amount where some filters are true or false.
Your grouping is correct. But you need to count how many times the value you do not want is in your group. That count must be zero.
SELECT tab1ID
FROM table2
GROUP BY tab1ID
HAVING sum(propertyID = 6) > 0
AND sum(propertyID = 3) = 0

Using SUM to count three tables with relevant records

I'm trying to use the SUM function to count rows from 3 tables, which is however, not working effectively since when the total_files and total_notes are returned, they both are the same when there is at least one file and then total_files will take the same value as total_notes which I don't understand why it's doing that.
It should count the number of rows which is relevant to each record that will get return as a record list with a count of total files, total notes and total contacts assigned to the record per record row (the data of files, notes and contacts do not get displayed only counted).
My query is shown below:
SELECT rec.street_number,
rec.street_name,
rec.city,
rec.state,
rec.country,
rec.latitude,
rec.longitude,
LEFT(rec.description, 250) AS description,
usr.username,
usr.full_name,
ppl.person_id,
ppl.first_name,
ppl.last_name,
SUM(IF(rlk.record_id = rec.record_id, 1, 0)) AS total_contacts,
SUM(IF(files.record_id = rec.record_id, 1, 0)) AS total_files,
SUM(IF(notes.record_id = rec.record_id, 1, 0)) AS total_notes,
(
SELECT COUNT(DISTINCT rec.record_id)
FROM records rec
WHERE rec.marked_delete = 0 AND rec.is_archive = 0
) AS total_records
FROM
(
records rec
INNER JOIN members usr ON rec.user_id = usr.user_id
LEFT OUTER JOIN record_links rlk ON rec.record_id = rlk.record_id
LEFT OUTER JOIN people ppl ON ppl.person_id = rlk.person_id AND rlk.record_id = rec.record_id
LEFT OUTER JOIN files files ON files.record_id = rec.record_id
LEFT OUTER JOIN notes notes ON notes.record_id = rec.record_id
)
WHERE rec.marked_delete = 0 AND rec.is_archive = 0
GROUP BY rec.record_id
ORDER BY rec.submit_date DESC
LIMIT 0, 25
Basically as you can see there is three SUM which will count relevant rows that comes from those tables, but I seriously don't understand how total_files would be taking the same value as total_notes is there something wrong I'm doing here?
It's because rec is joined to both notes and files.
Suppose record 1 has 2 notes and 1 file, record 2 has two note and two files, and record 3 has a note but no files.
Then the table rec LEFT OUTER JOIN files ... LEFT OUTER JOIN notes will look like this:
+-----------+---------+---------+
| record_id | file_id | note_id |
+-----------+---------+---------+
| 1 | 1 | 1 |
| 1 | 2 | 1 |
| 2 | 3 | 2 |
| 2 | 3 | 3 |
| 2 | 4 | 2 |
| 2 | 4 | 3 |
| 3 | NULL | 4 |
+-----------+---------+---------+
Note how every file_id gets joined to every note_id (within the same record_id). Also, since you have SUM(IF(files.record_id = rec.record_id,1,0)) and the join condition is files.record_id = rec.record_id, you are actually counting COUNT(files)*COUNT(notes) per record_id.
I'd recommend you instead COUNT(DISTINCT files.id) and COUNT(DISTINCT records.id). The column in the COUNT would be your primary key on files/notes, not files.record_id:
SELECT rec.record_id,
COUNT(DISTINCT files.id) AS total_files,
COUNT(DISTINCT notes.id) AS total_notes
FROM rec
-- note: LEFT OUTER JOIN is the same as LEFT JOIN in MySQL
LEFT JOIN files ON files.record_id=rec.record_id
LEFT JOIN notes ON notes.record_id=rec.record_id
GROUP BY record_id
+-----------+-------------+-------------+
| record_id | total_files | total_notes |
+-----------+-------------+-------------+
| 1 | 2 | 1 |
| 2 | 2 | 2 |
| 3 | 0 | 1 |
+-----------+-------------+-------------+
Of course, adjust to your query as necessary (add in those extra columns/joins).

Query to find all no associated entities

I have two tables, USERS and USERS_ASSOCIATIONS
For simplistic sake they look like this
USERS USERS_ASSOCIATION
----- --------------------------
|id | |id |fk_id| fk_assoc_id |
----- --------------------------
| 1 | | 1 | 1 | 2 |
| 2 | | 2 | 1 | 3 |
| 3 | --------------------------
| 4 |
-----
users can be associated to each other so for this example user with ID of 1 is associated to user 2 and 3 but not to 4.
I am trying to create a query that will find all users that are not associated to a specific user. So for the example of user 1 the result of the query will be 4 , if the user were 2 then the result of the query would be 1 , 3 , and 4 because user two has no associations.
So far have have this
SELECT * from USERS WHERE AND USERS.id <> ( SELECT * FROM USERS_ASSOCIATION as UA INNER JOIN USESR as U ON UA.fk_assoc_id = U.id AND UA.fk_id = 1);
I know this is wrong, the sub query returns a list of all of the USER_ASSOCIATIONS that are found for a particular user.
Looks like you just need a "not in" rather than the "<>"... This should return the list of users that "are not in" the subquery.
SELECT *
from USERS
WHERE AND USERS.id not in ( SELECT * FROM USERS_ASSOCIATION as UA INNER JOIN USESR as U ON UA.fk_assoc_id = U.id AND UA.fk_id = 1);
select *
from USERS
where id <> 1
and id not in (select fk_id from USERS_ASSOCIATION where fk_assoc_id = 1)
and id not in (select fk_assoc_id from USERS_ASSOCIATION where fk_id = 1)