I have two tables...one that registers users and one that checks in users. A user will always have a single entry in the register table but a user may have 0 or multiple entries in the checkin table. For a raffle selector, I wrote a query that is picking 1 entry from the register table and then 1 entry from the checkin table - each sub query picks a random entry so long as that userID does not exist in a 3rd table that stores the raffle winners. After the two entries are returned than it randomly selects one of the two returned entries as the winnner.
However, I believe there should be a more efficient way of writing this so its ONLY picking an entry once....not picking two entries and then picking one of the two.
It took me quite a while to figure out how to correctly write the below query as I am not proficient in mysql at all. The query works and seems to work efficiently, but I believe there should be a better way of writing it that also consolidates the amount of query code.
Hoping someone here can help or advise.
Table note: clubusers/clubHistory have multiple overlapping columns but the tables are not the same:
register = clubUsers
checkins = clubHistory
winners = clubRaffleWinners
SELECT * FROM (
(SELECT ch.user_ID,ch.clID FROM clubHistory AS ch
LEFT OUTER JOIN clubRaffleWinners AS cr1 ON
ch.user_ID=cr1.user_ID
AND cr1.cID=1157
AND cr1.rafID=18
AND cr1.crID=1001
AND cr1.ceID=1167
AND cr1.chDate1='2022-06-04'
WHERE
ch.cID=1157
AND ch.crID=1001
AND ch.ceID=1167
AND ch.chDate='2022-06-04'
AND cr1.user_ID IS NULL
GROUP BY ch.user_ID ORDER BY RAND() LIMIT 1
)
UNION
(SELECT cu.user_ID,cu.clID FROM clubUsers AS cu
LEFT OUTER JOIN clubRaffleWinners AS cr2 ON
cu.user_ID=cr2.user_ID
AND cr2.cID=1157
AND cr2.rafID=18
AND cr2.crID=1001
AND cr2.ceID=1167
AND cr2.chDate1='2022-06-04'
WHERE
cu.cID=1157
AND cu.crID=1001
AND cu.ceID=1167
AND cu.calDate<='2022-06-04'
AND cr2.user_ID IS NULL
GROUP BY cu.user_ID ORDER BY RAND() LIMIT 1
)
) AS foo order by RAND() LIMIT 1 ;
UPDATE:
As #JettoMartinez points out below, my current query could in fact randomly return the same user from each table so the final returned entry would just be the same user. I didn't realize this in my struggles just to get the above query to work. Thus my original OP asking for a more optimized query simply selecting a single random entry from both tables (where that user is not already in the winners table) is applicable for yet another reason.
There are two ways I can think of (Do note that since I don't fully understand the tables, I'm not using all the conditions you used in your JOIN statements, meaning it might need more work):
Using a exclusive subquery:
SELECT
cu.user_ID,
cu.clID,
ch.cID
FROM
clubUsers cu
LEFT JOIN clubHistory ch ON ch.user_ID = cu.user_ID
WHERE user_ID NOT IN (
SELECT
user_ID
FROM
clubRaffleWinners
WHERE
-- other conditions
)
ORDER BY RAND() LIMIT 1;
Using a LEFT "OUTER" JOIN, as you asked for:
SELECT
cu.user_ID,
cu.clID,
ch.cID -- Or any relevant field from clubHistory, really
FROM
clubUsers cu
LEFT JOIN clubHistory ch ON ch.user_ID = cu.user_ID
LEFT JOIN clubRaffleWinners cr ON cr.user_ID = cu.user_ID
AND ... -- other conditions to ensure uniqueness
AND ... -- that could also be in the WHERE part
WHERE
cr.user_ID IS NULL -- this will filter out the INNER part of the JOIN
ORDER BY RAND() LIMIT 1;
I don't have a dataset to properly test this queries, so please take them as a concept. I also didn't queried in clubHistory since I honestly don't see the point of doing so. Interpolating clubRaggleWinners to clubUsers seems enough for me.
EDIT
Since the user_ID in clubHistory is relevant to the raffle, I added a LEFT JOIN to it and added a field from said table in the SELECT statement, so that the user_id repeats once per entry in clubHistory plus the row of clubUsers, meaning that every user has 1 + number of entries / number of users + number of entries - number of winners chances to win.
This logic can be applied to the first query with a subquery too, and if the added field needs to be out, the query could be wrapped in a CTE or a subquery.
From what you are describing, and I want to make sure I understand.
Every registered person is qualified 1 entry.
However, each time they have checked in, they get 1 entry for each time they checked in. So, for someone registered and has NEVER checked-in, they get 1 entry. But if someone registered, and checked in 3 times, they would get a total of JUST the 3 times they checked in, vs 4 just for being registered.
Regardless of who is POSSIBLE, you want to EXCLUDE all people who have already been a winner in the raffle.
You SHOULD be able to get results from this below. Since the columns appear to be the same filtering on the cID, crID, ceID and Date, I have the primary FROM based on the registered clubUsers.
From that, a left-join to the clubHistory will either allow that person's ID to be returned once if only registered, OR multiple times based on the times checked in such as the example.
From the given user, I am also directly left-joining to the raffle winning history on the same criteria. If its the same criteria to the club history join, and the same criteria to the raffle (with exception of rafID = 18), appearing to indicate a specific raffle being drawn for, If the person is found, or not, the final WHERE accounts to exclude if its the single entry, or multiple entries via the IS NULL test.
The query will return all entries single or multiple, that have not already won in the order by RAND() qualifier, and apply a single LIMIT 1 to get the final winner. I dont know why you needed what appeared to be the clubhouse ID when you only really care about WHO won, without any regard to being a clubhouse history entry or not.
SELECT
cu.user_ID
FROM
clubUsers AS cu
LEFT JOIN clubHistory ch
on cu.user_ID = ch.user_ID
AND cu.cID = ch.cID
AND cu.crID = ch.crID
AND cu.ceID = ch.ceID
AND ch.chDate = '2022-06-04'
LEFT JOIN clubRaffleWinners AS crw
ON cu.user_ID = crw.user_ID
AND cu.cID = crw.cID
AND cu.crID = crw.crID
AND cu.ceID = crw.ceID
AND crw.chDate1 = '2022-06-04'
AND crw.rafID = 18
WHERE
cu.cID = 1157
AND cu.crID = 1001
AND cu.ceID = 1167
AND cu.calDate <= '2022-06-04'
AND crw.user_id IS NULL
order by
RAND()
LIMIT 1
For performance purposes, I would ensure the following indexes
table index
clubUsers ( cid, crID, ceID, calDate, user_id )
clubHistory ( user_id, cID, crID, ceID, chDate )
clubRaffleWinners ( user_id, cID, crID, ceID, chDate1, rafID )
(Just a Comment, but need formatting.)
I would start by trying to put these 4 values in a single table, not repeated across 3 tables:
cu.cID=1157
AND cu.crID=1001
AND cu.ceID=1167
AND cu.calDate<='2022-06-04'
Please provide SHOW CREATE TABLE for each table; then I can assess whether the recommended indexes make sense.
I have two tables users and distance. In a page I need to list all users with a simple query such as select * from users where active=1 order by id desc.
Sometimes I need to output data from the distance table along with this query where the user ID field in users is matched in the distance table in EITHER of two columns, say userID_1 and userID_2. Also in the distance table either of the two mentioned columns must also match a specified id ($userID) as well in the where clause.
This is the best that I came up with:
select
a.*,
b.distance
from
users a,
distance b
where
((b.userID_1='$userID' and a.id=b.userID_2)
or (a.id=b.userID_1 and b.userID_2='$userID'))
and a.active=1
order by a.id desc
The only problem with this query is that if there is no entry in the distance table for the where clause to find a match, the query does not return anything at all. I still want it to return the row from the user table and return distance as null if there are no matches.
I cannot figure out if I need to use a JOIN, UNION, SUBQUERY or anything else for this situation.
Thanks.
Use a left join
select
a.*,
b.distance
from
users a
left join distance b on
(b.userID_1=? and a.id=b.userID_2)
or (b.userID_2=? and a.id=b.userID_1)
where
a.active=1
order by a.id desc
and use a prepared statement. Substituting text into a query is vulnerable to SQL Injection attacks.
You need a left join between 'users' and 'distance'. As a result (pun not intended), you will always get the rows from the 'users' table along with any matching rows (if any) from 'distance'.
I notice that you are using the SQL-89 join syntax ("implicit joins") as opposed to SQL-92 join syntax ("explicit joins"). I wrote about this once.
I suggest that you change your query to
select a.*, b.distance
from users a left join distance b
on ((b.userID_1='$userID' and a.id=b.userID_2)
or (a.id=b.userID_1 and b.userID_2='$userID'))
where a.active=1
order by a.id desc
Try this:
select a.*, b.distance
from users a
left join distance b on (a.id=b.userID_1 or a.id=b.userID_2) and
(b.userID_1 = '$userID' or b.userID_2 = '$userID')
where a.active=1
order by a.id desc
Given the following query…
SELECT DISTINCT *
FROM PAS_Post
WHERE post_user_id = 21
GROUP BY post_post_id
UNION
SELECT DISTINCT PAS_Post.*
FROM PAS_Follow LEFT JOIN PAS_Post ON (
PAS_Follow.folw_followed_user_id = PAS_Post.post_user_id
)
WHERE PAS_Follow.folw_follower_user_id = 21
GROUP BY post_post_id
ORDER BY post_posted_date DESC
I always get a row in the results that is just NULL's, unfortunately I need to preserve some NULL values in the data as the Post's table (PAS_Post) holds different types of information.
Can anyone steer me in the right direction to get rid of this null row.
I do not want or need the last row here
You're using a (left) outer join in the second part of the UNION, so any cases that do not satisfy the join criteria will result in data from the table on the left of the join (PAS_Follow), but NULL in every column of the table on the right of the join (PAS_Post); the subsequent selection only of columns from the latter table results in the NULL rows that you observe. Therefore, the simplest solution is to use an inner join (that completely excludes records where the join criteria is not met).
However, in your case, it appears that your query can be greatly simplified by simply using two possible conditions in a filter on the joined tables rather than a UNION:
SELECT p.*
FROM PAS_Post AS p
JOIN PAS_Follow AS f ON f.folw_followed_user_id = p.post_user_id
WHERE p.post_user_id = 21
OR f.folw_follower_user_id = 21
ORDER BY p.post_posted_date DESC
I have excluded the GROUP BY clause on the assumption that post_post_id is the primary key (or at very least is UNIQUE) in your PAS_Post table. If that assumption is incorrect, you may want to reintroduce it—but beware that MySQL will indeterminately select the values that will be returned from each group.
I have a query which is actually a members match with initial letter 'a' and the result also contain users friend.
What I would like is that the users friend must come before in the result and then remaining users.
Here is the query
SELECT `a`.`mem_id`
FROM `members` `a`
INNER JOIN
(
SELECT DISTINCT `n2`.`mem_id`
FROM `network` `n1`,`network` `n2`
WHERE `n1`.`frd_id` = `n2`.`mem_id`
AND `n1`.`mem_id`='777'
AND `n2`.`frd_id`='777'
) `b`
WHERE `a`.`mem_id`=`b`.`mem_id`
AND `a`.`profilenam` LIKE 'a%'
AND `a`.`deleted` ='N'
ORDER BY `profilenam`
As i understand your question, here is a query that will return the data you're looking for:
SELECT M.mem_id
FROM members M
LEFT OUTER JOIN (SELECT DISTINCT N1.mem_id
,N1.frd_id
FROM network N1
INNER JOIN network N2 ON N2.mem_id = N1.frd_id
AND N2.frd_id = N1.mem_id) F ON F.mem_id = M.mem_id
AND F.frd_id = '777'
WHERE M.profilename LIKE 'a%'
AND M.deleted = 'N'
ORDER BY CASE
WHEN F.mem_id IS NOT NULL THEN 0
ELSE 1
END, M.profilename
Some explanation about my query:
The table members has a LEFT OUTER JOIN on a query that returns every existing friendships (based on your query, i supposed that to consider two members as friends it's mandatory to have a two ways connection in table network).
This jointure with the condition F.frd_id = '777 ensure that the join is only made if the active member is a friend of the member with id '777'.
The last element is the key element that you were looking for, the ORDER BY clause to have the friends first and then the other members. The first condition of the clause is a simple SWITCH statement that tests if the jointure exists or not, if yes that means that the member is a friend, otherwise not. Every friends have the value 0 and the other members have the value 1, by doing an ascending sort on this value the result will be ordered as desired.
Hope this will help.
I'm trying to compare two set of resutls aving hard time to undesrtand how subqueries work and if they are efficient. I'm not gonna explain all my tables, but just think i have apair of arrays...i might do it in php but i wonder if i can do it in mysql right away...
this is my query to check how many items user 1 has in lists he owns
SELECT DISTINCT *
FROM list_tb
INNER JOIN item_to_list_tb
ON list_tb.list_id = item_to_list_tb.list_id
WHERE list_tb.user_id = 1
ORDER BY item_to_list_tb.item_id DESC
this is my query to check how many items user 2 has in lists he owns
SELECT DISTINCT *
FROM list_tb
INNER JOIN item_to_list_tb
ON list_tb.list_id = item_to_list_tb.list_id
WHERE list_tb.user_id = 1
ORDER BY item_to_list_tb.item_id DESC
now the problem is that i would intersect those results to check how many item_id they have in common...
thanks!!!
Unfortunately, MySQL does not support the Intersect predicate. However, one way to accomplish that goal would be to exclude List_Tb.UserId from your Select and Group By and then count by distinct User_Id:
Select ... -- everything except List_Tb.UserId
From List_Tb
Inner Join Item_To_List_Tb
On List_Tb.List_Id = Item_To_List_Tb.List_Id
Where List_Tb.User_Id In(1,2)
Group By ... -- everything except List_Tb.UserId
Having Count( Distinct List_Tb.User_Id ) = 2
Order By item_to_list_tb.item_id Desc
Obviously you would replace the ellipses with the actual columns you want to return and on which you wish to group.