Update rows based on previous select statement - mysql

I need to do as many updates as needed based on a select.
Let's say I have two tables "Groups" and "Members".
"Groups"
GroupName GroupID
Genius 1
Clever 2
Normal 3
Stupid 4
Donkey 5
"Members"
MemberName GroupID
John 1
Peter 3
Mary 1
Ashley 2
Robin 1
Louis 5
Bill 4
Paul 5
I want to change members from a GroupID to another.
I.e: Members from "Clever" to "Donkey".
select MemberName from Members where GroupID='1';
while($arr = mysqli_fetch_array($rs, MYSQLI_ASSOC))
{
$name = $arr['MemberName'];
}
Then I will update all the selected members into the new group:
$sql .= update Members set GroupID='5' where MemberName='$name';
I know I have to put all names into an array in order to update each one separately, but I'm a bit confused also in the correct update syntax.

Why not simply
UPDATE Members SET GroupID=5 WHERE GroupID=1
?
Your method would only be required if you had to do some ugly/complicated processing that couldn't be done in SQL (or would be even uglier in SQL).

One way you can do this is by following:
SELECT #DonkeyGrpID:=GroupID
FROM Groups
WHERE GroupName = 'Donkey';
UPDATE Members M, Groups G
SET M.GroupID = #DonkeyGrpID
WHERE M.GroupID = G.GroupID AND G.GroupName = 'Clever' ;

Related

Excluding unique ID in a query if at least one criteria is met

I'm having this problem which I'm unsure how to resolve.
Here's the situation : I want to get a list of all individuals who have not completed a survey. It is however possible for someone to start/complete multiple surveys.
Therefore, I want the list of individuals who have not completed at least one survey.
Here's what my query looks likes to get the list of people with incomplete surveys :
SELECT Survey.UserID, Survey.Fullname
FROM [...]
WHERE Survey.SurveySubmitted = 0 -- 0 = Unsubmitted, 1 = submitted
Now this is what the database could look like
UserID Fullname SurveySubmitted
1 John Smith 0
2 Jane Doe 1
3 Tom Glass 0
3 Tom Glass 1
Now the above query will select both John Smith and Tom Glass. However, since Tom Glass already completed at least one survey, he should be excluded.
Any ideas to proceed? It most likely needs a SELECT within another SELECT but I'm having trouble picturing it.
You could check for the user not in the user that have submited/completed
select Survey.UserID, Survey.Fullname
from [.....]
where UserID NOT IN (
SELECT Survey.UserID, Survey.Fullname
FROM [...]
WHERE Survey.SurveySubmitted = 1
)
You should group by whatever uniquely identifies a User/Survey combination and then sum the # of surveys that have been submitted. You can then use a having clause to filter out rows > 0:
select *
from Survey
group by UserId, FullName
having sum(SurveySubmitted) = 0;
SQLFiddle Example

Manipulate rows using multiple clauses in mysql

I have a table that goes something like this:
samples
sample_id | field | value | list_id
1 country US 10
2 state tx 10
3 country US 11
4 state tx 11
5 emp_size 100 11
I have a query that retrieves list_ids 10 and 11 using the ff code;
select * from samples where (field='country' and value='US') OR (field='state' and value='tx')
However I realized later on that this is not the setup that I want. Let say I include in my clause (field='emp_size' and value='100') because I want to get list_id 11 only, it still includes list_id 10 because I use OR in my query. And right now I'm not sure if there's a workaround for this using plain mysql only or if I should just manipulate the data using php.
Edit
For clarification, I want to get the list_ids based on the given parameters, say, I want US and TX, it should return list_ids 10 and 11. But if I add another parameter, say, emp_size, it should only return list_id 11.
You've got an EAV style data structure, so the best solution here is to self-join the table for each parameter/value combination that you are searching on.
SELECT s1.list_id
FROM samples s1
INNER JOIN samples s2
ON s1.list_id = s2.list_id
AND s2.field = 'state'
AND s2.value = 'tx'
INNER JOIN samples s3
ON s1.list_id = s3.list_id
AND s3.field = 'emp_size'
AND s3.value = '100'
WHERE s1.field = 'country'
AND s1.value = 'US';

MySQL subquery overview

Alright I am guessing I need a subquery to solve this and I am little rusty on these. So I have 3 tables:
tblAccount - Has User information and AccountID
tblItem - Has Item information and ItemID
tblAccountItem - Has 3 fields - AccountItemID / AccountID / ItemID
An account can have many items and an item can have many accounts. Example data:
tblAccount
AccountID AccountName AccountEmail
1 John Smith john#smith.com
2 Fred John fred#john.com
3 George Mike george#mike.com
tblItem
ItemID ItemName ItemDescription
1 Hammer Smashes things
2 Axe Breaks things
Ok so lets say the Hammer belongs to John,Fred and George. Axe only belongs to John and Fred.
tblAccountItem
AccountItemID AccountID ItemID
1 1 1
2 2 1
3 3 1
4 1 2
5 2 2
So I want to show what items John has and also show who else owns that item. The output I want to show is:
ItemName ItemDescription OtherOwners
Hammer Smashes things Fred, George
Axe Breaks things Fred
Any help would be greatly appreciated!
The answer by ctrahey is perfect but I have a slight condition to add. There are 2 types of accounts in tblAccount denoted by a field.
tblAccount
AccountID AccountName AccountEmail AccountDescription AccountTypeID
1 John Smith john#smith.com NULL 1
2 Fred John fred#john.com NULL 1
3 George Mike george#mike.com Runner 2
tblAccountTypeID
AccountTypeID AccountType
1 User
2 Admin
If the AccountTypeID is 1 then I need to output the AccountEmail. If the AccountTypeID is 2 I need to output the AccountDescription. Eg output (same story as above):
ItemName ItemDescription OtherOwners
Hammer Smashes things Fred, Runner
Axe Breaks things Fred
Going off the query that ctrahey I am guessing there needs to be an ALIAS field created. Something like:
WHERE AccountTypeID = 1 (SELECT AccountName)
WHERE AccountTypeID = 2 (SELECT AccountDescription)
I hope this makes sense, thanks for all the help so far!
Subqueries are very rarely actually needed, and are often replaced (with improved performance) by a well-designed JOIN.
Here, we start with AccountItem table (the WHERE clause immediately limits the query to only items owned by our account of interest); then we join the same table (aliasing it to 'others_items_join'), telling it to join with the same itemID but not owned by our account if interest. That's the essence of the entire query, the next two joins are only to bring in the actual strings we want to be in our output (the other people's names and the item names/descriptions). GROUP BY is used to give just one row per item which our account of interest has.
SELECT
ItemName,
ItemDescription,
GROUP_CONCAT(others.AccountName) as OtherOwners
FROM
tblAccountItem as my_items
LEFT JOIN tblAccountItem as others_items_join
ON others_items_join.ItemID = my_items.ItemID AND others_items_join.AccountID != ?
LEFT JOIN tblAccount as others
ON others_items_join.AccountID = others.AccountID
JOIN tblItems ON my_items.ItemID = tblItems.ItemID
WHERE my_items.AccountID = ?
GROUP BY ItemName
You better use a coding to resole this , here rough query that's may helps you get an idea :
SELECT AccountName
FROM tblAccount
WHERE AccountID = (SELECT AccoundID
FROM tblAccountItem
WHERE itemID = (SELECT itemID
FROM tblAccountItem
WHERE AccountID = 1 (john Id as example)));
Hope this helps
SELECT ItemName, ItemDescription, AccountItemID FROM tblitem RIGHT JOIN tblaccountitem ON tblitem.ItemID=tblaccountitem.ItemID
I hope this helps lead to your answer. I am still looking into this

mysql: order by nearest id

i have a query that returns some users related to a specific user (Bob).
I need to retrieve the nearest records, meaning, i must return users whose ID column is near Bob's ID.
For example:
ID
Tom 5
Mike 8
Bob 10
Jack 12
Brian 13
The query:
SELECT users.* FROM users
INNER JOIN neighboors on neighboors.neighboor_id = users.id #ignore this join, just to exemplify
WHERE neighboors.user_id = 10 # bobs id
ORDER BY something
LIMIT 3 # i want to return only the 3 nearest users (according to the table above:mike, jack and brian)
How can i achieve this?
updated
the logic is, users can plant trees, each tree has an specie. The query should return users that have planted the same tree specie.
And why is important order by proximity of id? the client want this way :) there is no other reason.
Try with this, should do what you need :
SELECT users.* FROM users
INNER JOIN neighboors ON neighboors.neighboor_id = users.id
WHERE neighboors.user_id = 10
ORDER BY ABS(neighboors.user_id - 10)
LIMIT 3
The ABS function in this case it is used to calculate the "distance" from user_id selected value (the value filtered by the WHERE ... ).
To obtain better performance on large tables you have to index(if not yet) the column : neighboors.user_id .
One way to do this is to store the differences as a separate column in an inner query and then query for the smallest differences. A good example for nested queries is at :
http://dev.mysql.com/tech-resources/articles/subqueries_part_1.html
The problem is that nearness works in both a positive and negative direction.
If you had:
Tom 5
Mike 8
Sally 9
Bob 10
Sarah 11
Jack 12
Brian 13
Then do you want to return Mike, Sally and Sarah, or Sally, Sarah and Jack? Do you prefer ascending proximity or descending proximity?
It will help to know exactly what business logic this is trying to implement. Why is it important to select by proximity of the ID? How does the ID relate users to each other?
I'd be interested in helping if you can provide more details.

How do I compute a ranking with MySQL stored procedures?

Let's assume we have this very simple table:
|class |student|
---------------
Math Alice
Math Bob
Math Peter
Math Anne
Music Bob
Music Chis
Music Debbie
Music Emily
Music David
Sports Alice
Sports Chris
Sports Emily
.
.
.
Now I want to find out, who I have the most classes in common with.
So basically I want a query that gets as input a list of classes (some subset of all classes)
and returns a list like:
|student |common classes|
Brad 6
Melissa 4
Chris 3
Bob 3
.
.
.
What I'm doing right now is a single query for every class. Merging the results is done on the client side. This is very slow, because I am a very hardworking student and I'm attending around 1000 classes - and so do most of the other students. I'd like to reduce the transactions and do the processing on the server side using stored procedures. I have never worked with sprocs, so I'd be glad if someone could give me some hints on how to do that.
(note: I'm using a MySQL cluster, because it's a very big school with 1 million classes and several million students)
UPDATE
Ok, it's obvious that I'm not a DB expert ;) 4 times the nearly the same answer means it's too easy.
Thank you anyway! I tested the following SQL statement and it's returning what I need, although it is very slow on the cluster (but that will be another question, I guess).
SELECT student, COUNT(class) as common_classes
FROM classes_table
WHERE class in (my_subject_list)
GROUP BY student
ORDER BY common_classes DESC
But actually I simplified my problem a bit too much, so let's make a bit it harder:
Some classes are more important than others, so they are weighted:
| class | importance |
Music 0.8
Math 0.7
Sports 0.01
English 0.5
...
Additionally, students can be more ore less important.
(In case you're wondering what this is all about... it's an analogy. And it's getting worse. So please just accept that fact. It has to do with normalizing.)
|student | importance |
Bob 3.5
Anne 4.2
Chris 0.3
...
This means a simple COUNT() won't do it anymore.
In order to find out who I have the most in common with, I want to do the following:
map<Student,float> studentRanking;
foreach (Class c in myClasses)
{
float myScoreForClassC = getMyScoreForClass(c);
List students = getStudentsAttendingClass(c);
foreach (Student s in students)
{
float studentScoreForClassC = c.classImportance*s.Importance;
studentRanking[s] += min(studentScoreForClassC, myScoreForClassC);
}
}
I hope it's not getting too confusing.
I should also mention that I myself am not in the database, so I have to tell the SELECT statement / stored procedure, which classes I'm attending.
SELECT
tbl.student,
COUNT(tbl.class) AS common_classes
FROM
tbl
WHERE tbl.class IN (SELECT
sub.class
FROM
tbl AS sub
WHERE
(sub.student = "BEN")) -- substitue "BEN" as appropriate
GROUP BY tbl.student
ORDER BY common_classes DESC;
SELECT student, COUNT(class) as common_classes
FROM classes_table
WHERE class in (my_subject_list)
GROUP BY student
ORDER BY common_classes DESC
Update re your question update.
Assuming there's a table class_importance and student_importance as you describe above:
SELECT classes.student, SUM(ci.importance*si.importance) AS weighted_importance
FROM classes
LEFT JOIN class_importance ci ON classes.class=ci.class
LEFT JOIN student_importance si ON classes.student=si.student
WHERE classes.class in (my_subject_list)
GROUP BY classes.student
ORDER BY weighted_importance DESC
The only thing this doesn't have is the LEAST(weighted_importance, myScoreForClassC) because I don't know how you calculate that.
Supposing you have another table myScores:
class | score
Math 10
Sports 0
Music 0.8
...
You can combine it all like this (see the extra LEAST inside the SUM):
SELECT classes.student, SUM(LEAST(m.score,ci.importance*si.importance)) -- min
AS weighted_importance
FROM classes
LEFT JOIN class_importance ci ON classes.class=ci.class
LEFT JOIN student_importance si ON classes.student=si.student
LEFT JOIN myScores m ON classes.class=m.class -- add in myScores
WHERE classes.class in (my_subject_list)
GROUP BY classes.student
ORDER BY weighted_importance DESC
If your myScores didn't have a score for a particular class and you wanted to assign some default, you could use IFNULL(m.score,defaultvalue).
As I understand your question, you can simply run a query like this:
SELECT `student`, COUNT(`class`) AS `commonClasses`
FROM `classes_to_students`
WHERE `class` IN ('Math', 'Music', 'Sport')
GROUP BY `student`
ORDER BY `commonClasses` DESC
Do you need to specify the classes? Or could you just specify the student? Knowing the student would let you get their classes and then get the list of other students who share those classes.
SELECT
otherStudents.Student,
COUNT(*) AS sharedClasses
FROM
class_student_map AS myClasses
INNER JOIN
class_student_map AS otherStudents
ON otherStudents.class = myClasses.class
AND otherStudents.student != myClasses.student
WHERE
myClasses.student = 'Ben'
GROUP BY
otherStudents.Student
EDIT
To follow up your edit, you just need to join on the new table and do your calculation.
Using the SQL example you gave in the edit...
SELECT
classes_table.student,
MIN(class_importance.importance * student_importance.importance) as rank
FROM
classes_table
INNER JOIN
class_important
ON classes_table.class = class_importance.class
INNER JOIN
student_important
ON classes_table.student = student_importance.student
WHERE
classes_table.class in (my_subject_list)
GROUP BY
classes_table.student
ORDER BY
2