MYSQL Consolidate Conditional Update from Picklist Array - php

I am struggling to find a good way to approach this code with MYSQL and PHP.
I have a table of users and a table of groups. Each user will only belong to one group, or will belong to group 0, (not in a group).
I have an administration edit group page, which has the JS Picklist Plugin in it.
It lists the users which are in the group, and those that are not.
On submission of the form on this page, I get an array of users which are the only ones which should be in the group after the sql query.
So I need to do two things:
Remove the group id from any users which currently are in the group, but are not in the array returned by the form.
Ensure any users in the array returned have the group id updated to them.
For this example, I have a group ID of 25, after the form has been submitted there are two be two users in this group (44 and 45). No other users should remain in that group. But if the other users are in a different group such as 30, they should not be modified either.
I could have several hundred users, and whilst performance isn't the greatest concern, it just feels a bit hackish to me.
This is what I've failed at so far. For performance reasons we are talking maybe 300 users, so nothing to massive. The two queries do work as expected, just seems very hackish. Must be a better way, here to learn!
update users set user_group = 0 where (user_group = 25 AND uid != 45) AND (user_group = 25 AND uid != 44)
update users set user_group = 25 where ( uid = 44 OR uid = 45 )
Thanks!

UPDATE users
SET user_group = CASE
WHEN uid IN (...ids from post) THEN $group_id
ELSE 0
END
WHERE user_group = $group_id OR uid IN (...ids from post)
Untested and posted from my phone but the concept is there

Related

mysql query for grouping datga based on certain condition

I am working on a quiz based project where user visits and gives quiz. I want to list count of users who must have appeared and failed in each of the 5 modules Quizzes. I am getting this properly. Now if a user reappears in any of the 5 quizzes and passes in it he/she must not be included in the earlier mentioned count.
My query for counting number of users appeared and failed in all 5 modules is,
SELECT count(DISTINCT module_id) as module_id from tbl WHERE user_id = $userId AND cleared = 0,
where user_id is user's id and cleared is set to 0 if failed and 1 if passed.
Although the above query counts number of modules that a user has passed. I am using this into a function and then comparing in if statement.
I just want to figure out what changes can I bring in here so that if a user passes in any module, they should not be included in this. I have tried but got no correct query buildup. Any suggestion will be helpful.
Use conditional aggregation:
SELECT user_id
FROM tbl
GROUP BY user_id
HAVING COUNT(DISTINCT module_id) = 5 AND SUM(cleared) = 0;
-- ^^ appeared in all 5 modules ^^ and failed them all
If you want to restrict the above query to just one or a few users, then you may add back the WHERE clause.

Ordering users with most matching user's skills

I want to show recommended users to a user in descending order where skills match the most.
Issue is that I am storing skill in single field in this form
user_skill
musician,pop,singer
Note: this will be input musician,pop,singer..
so what I want to achieve I want to show users have all three skills at top,then those having two at last those having only one.
so out put will be like
**user_name skills**
sam musician,pop,singer
smith musician,pop,singer
ali musician,singer
nasira musicain,pop
siri musician
taylor pop
andrew singer
Can this be achieved by single mysql query?
If this not possible can this be done by php code.I don't want to change table structure as this will require lots of redo.
Thanks for your help.
You might be able to do with with SQL but the query would be so complex i probably wouldnt.
On the php side you could iterate over the result set and tally the skills for each user_name within a given set of skills you are looking for, and then just sort them:
$desired = explode(',', $input);
$users = array();
// i presume you have the query worked out to find users with any one of input
// skills attributed to them so lets say that $stmt is the PDO statement where you
// have executed that query
while (false === ($row = $stmt->fetch(PDO::FETCH_ASSOC))) {
$row['skills'] = explode(',', $row['skills']);
// assign an array containing only skills that were in your $input
// to $row['desired_skills']
$row['desired_skills'] = array_intersect($desired, $row['skills']);
$row['nb_desired_skills'] = count($row['desired_skills']);
$user[] = $row;
}
usort($users, function ($a, $b) {
return $b['nb_desired_skills'] - $a['nb_desired_skills'];
});
// now you can loop over $users and display the fields you need
However, the definition and attribution of these skills is a key part of you application, you should just normalize the tables now... its only going to become a larger refactor by putting it off.
I don't want to change table structure as this will require lots of redo.
I strongly (!) recommend you make a separate skills table, and link that to your users table using foreign keys. You will be infinitely thankful to yourself once your project gets even slightly more complex.
If you don't know how, I'm sure people on this site would help you with a script that does the conversion if you were to post another question.
MySQL
CREATE TABLE skills
(
name VARCHAR(64) NOT NULL,
user_id INT NOT NULL,
PRIMARY KEY (name, user_id),
FOREIGN KEY (user_id) REFERENCES users(id)
);
This way you can easily select users by most skills using a simple subquery (assuming you have a users table with an id column):
MySQL
SELECT *,
(SELECT COUNT(*)
FROM skills
WHERE user_id = u.id) AS num_skills
FROM users u
ORDER BY num_skills DESC;
So why would you take this approach instead of relying on PHP?
Creates minimal extra overhead in database,
significantly reduces chatter between database and webserver by not requiring to query the entire users table if you just want, for example, only the top 10 users,
enables more complex and varying queries in the future easily.
The following MySQL snippet queries only those users who have a given skill (for example, "singer"), then sorts them in descdending order based on number of skills. The query does not return users with zero skills:
MySQL
SELECT *,
(SELECT COUNT(*)
FROM skills
WHERE user_id = u.id
AND name = 'singer') AS num_skills
FROM users u
WHERE num_skills > 0
ORDER BY num_skills DESC;
Of course, you can also search for skills by id just replace the second part of the WHERE clause inside the subquery with AND id = 3, which will query those users, who have a skill with ID of 3.
The next step towards a more optimal database would be creating a real skills table, which stores only the skills that you have registered in your database, and a user_skills table, that links it and the users table together.
This would enable you to significantly reduce database size on the long run, and be able to run complex but 'clean' queries that do not depend on the webserver.

Implementing Discussion Group System

I am going through the process of adding discussion groups to my site, currently users can post on topics then other users can reply, pretty simple.
Here is how I have set up the group tables
Disc_group
ID(int)
Name(varchar)
Description(varchar)
Created(date)
Creator(int) = user_id
invite_rule(enum 0,1) 0 = open invite, 1 = invite must be approved
Disc Users
ID(int)
User_id(int)
Group_id(int)
datejoined(date)
accepted(enum 0,1) 1 = accepted
admin(enum 0,1) 1 = admin
My question is I am trying to think of the most efficient way to implement this with the current topics, posts and tags tables.
I thought I could just add "group_id" into topics, so topics with group_id of 3 for example, only display if you are viewing the group page id=3.
I don't want any one to write code for me, I'm wanting to know what file structure would be recommended, such as
Non grouped posts are displayed in "topics.php?id=1" topics in groups displayed in "gtopics.php?id=1" for example, hopefully my issue isn't too confusing.
invite_rule(enum 0,1) 0 = open invite, 1 = invite must be approved
accepted(enum 0,1) 1 = accepted
admin(enum 0,1) 1 = admin
We can already see the above forming into a set of permissions, I highly recommend implementing a bit masking system that way you can store the user permissions in the database and perform a logical AND when you generate your authorization token.
Disc Users
ID(int)
User_id(int)
Group_id(int)
datejoined(date)
accepted(enum 0,1) 1 = accepted
admin(enum 0,1) 1 = admin
Is going to start hurting when it comes to normalization, I would propose the following structures to replace this:
DISC_GROUPS
GroupName
GroupId (not needed if group names need to be unique)
DISC_USER_GROUPS
UserId
GroupId
DISC_USERS
USER_ID
NAME
PERMISSIONS
DATE_JOINED
//other information about users
//This table may be unnecessary, unless you want to add some audit information perhaps
DISC_INFORMATION (THIS IS YOUR OLD DISC_USERS table)
ID
USERID
Notice how we don't duplicate information and we rely on the USER_ID relationship if we need more details.

Building a SQL query - Return some rows once, some not

I need to build a SQL query which is beyond my programming abilities.
Okay, here is my request:
Let's say I have a table, with: id, user_id, email and amount columns. This query, should SELECT user_id only once! If I have matched user_id once, the query shall continue, but if the same user_id is matched again in another row, we should skip it.
Here comes the main problem...
Imagining that we grabbed user_id once, and skipped all same other rows with that user_id, now I need to sum all the contents from the amount column for the same user_id.
I think I complicated this a bit, I'll try illustrating my issue:
If this problem is not solvable via SQL only, then a PHP answer would work too.
I'm trying to create a list of users (no duplicate users) and add the amount they paid.
According to the image, user_id 56 paid 90.00 (12 + 45 + 33)
Can someone tell me a way how to achieve this?
Assuming the name of the table is users
SELECT user_id, email, SUM(amount) FROM users GROUP BY user_id

What is the most efficient way to always make a database query that I know will only return a result very occasionally?

I am trying to work out how to implement a new piece of functionality on my website. My site allows people to download things uploaded by members of a group. The new piece of functionality will allow 1 member of the group to be a 'super' user whos uploads cannot be downloaded. So at the moment my query (more or less) is -
SELECT * FROM uploads WHERE group = $groupid
I am thinking I will store the 'super' status of the member in the 'members' table so will probably look up this to get the member id of the super member before doing the query.
$all_group_members = SELECT members FROM groups WHERE group_id = $groupid
$super_member = SELECT memberid FROM members WHERE memberid IN ($all_group_members) AND super_status = 1
Then modify the query to be -
SELECT * FROM uploads WHERE group = $groupid AND uploader ! = $super_member
However there may not always be a super member in a group, so my question is is there a more effective way to do this, for example store the super members ids in the session cookie? What about storing the super user id in the group table - still involves doing a query -
SELECT super_user FROM groups WHERE groupid = $group_id
But atleast thats one less query.
Is there just no way around this - I know that there will only be a super user about 5% of the time so 95% of the time this will be wasted query. Perhaps the session cookie could store a group_has_super_user variable.
Any other ways of doing this that im missing?
thanks
I assume, other group members can't download anything from my group's stuff. So, simply add a downloadable field to the uploads, and set it to
1, if a simple member has uploaded it, or
0, if a super member has.
Then the only query you have to run is:
SELECT * FROM uploads WHERE group = $groupid AND downloadable != 0
Also, if the membership is changing (e.g. someone else gets the super right), these flags should be recalculated in the group.
Don't forget to create index for (or set the primary key to) groupid + downloadable (or downloadable + groupid)!
CREATE TABLE uploads (id, user_id, …)
CREATE TABLE groups (id, …)
CREATE TABLE group_users (group_id, user_id, is_super, …)
SELECT u.*
FROM group_users gu
JOIN uploads u
ON u.user_id = gu.user_id
WHERE gu.group_id = $group_id
AND gu.is_super = 0

Categories