MySQL Result - "Group By" removing incorrect duplicates - php

Will do my best to describe the problem Im having :)
Each thread/topic in my forum represents one disc. Registered members of the forum use a series of checkboxes (one displayed next to each disc) to tick each disc that they have in their collection. When the form is $_POST'ed it stores the information in a table like so:
| user_id - disc_id |
+--------------------+
| 2 - 571 |
| 2 - 603 |
| 2 - 4532 |
When the user next views the forum I have the checkboxes ticked and disabled on discs that the user owns. This is done using:
$sql = 'SELECT id, poster, subject, posted, last_post, last_post_id,
last_poster, num_views, num_replies, closed, sticky, moved_to, topicimage,
c.user_id, c.disc_id FROM topics LEFT JOIN collections AS c ON c.disc_id=id
WHERE forum_id='.$id.' ORDER BY sticky DESC;
The above grabs all of the discs, which I then display using the following (stripped down) code:
$result = $db->query($sql) or error('Unable to fetch topic list '.$sql.'', __FILE__, __LINE__, $db->error());
// If there are topics in this forum
if ($db->num_rows($result))
{
while ($cur_topic = $db->fetch_assoc($result))
{
// If logged in users ID matches the current discs user_id (i.e if this user owns this disc)
if ($cur_topic['user_id']==$pun_user['id']) {
$read = ' I own this!';
} else {
$read = ' I own this!';
}
}
}
This works great, until a second user adds the same disc ID to his collection, eg:
| user_id - disc_id |
+--------------------+
| 2 - 571 |
| 2 - 603 |
| 6 - 571 |
This causes a duplicate thread to appear in the forum. One is correctly ticked (because I own it), the other is not ticked, though it shares all of the same information such as topic id and image.
My first thought was to try adding GROUP BY c.disc_id to the SQL, which does successfully remove the duplicate topic - However, it is removing the wrong one. The disc that I have ticked is no longer shown, leaving only the unticked version.
Hope that makes sense. Can anyone offer any insight or ideas?
Many Thanks.

This is a guess, since I don't know your schema, but I don't see you specifying the user's ID in your WHERE clause.
What about something like the following?
SELECT t.id, t.poster, t.subject, t.posted, t.last_post, t.last_post_id,
t.last_poster, t.num_views, t.num_replies, t.closed, t.sticky,
t.moved_to, t.topicimage, c.user_id, c.disc_id
FROM topics AS t LEFT JOIN collections AS c ON c.disc_id = t.id
WHERE forum_id = '.$id.'
AND c.user_id = '.$user_id.'
ORDER BY t.sticky DESC;
Also, you're joining on Topic ID = Disc ID. Is that intentional?

I can see two easy way for solving this:
first:
with two query, you group query and a second to fetch all the disc_id owned by the user
second:
with your first query:
if ($db->num_rows($result)) {
$array = Array();
while ($cur_topic = $db->fetch_assoc($result)) {
$id = $cur_topic['disc_id'];
if (!array_key_exists ($id, $array)) { // allow only result per disc_id
$array[$id] = $cur_topic;
$array[$id]['owned'] = false;
}
// If logged in users ID matches the current discs user_id (i.e if this user owns this disc)
if ($cur_topic['user_id']==$pun_user['id']) // check if one is owned by the user
$array['owned'] = true;
}
foreach ($array as $cur_topic) {
if ($cur_topic['owned']) {
$read = '<br /><input type="checkbox" disabled="disabled" checked="checked" /> <span style="color:#999">I own this!</span>';
} else {
$read = '<br /><input type="checkbox" name="discs[]" value="'.$cur_topic['id'].'" /> I own this!';
}
}
}

Related

SQL PHP Select columns with several conditions in same query

I can't figure out how to make a selection of the matches between 2 teams even if is at home or away.
Example:
I have this table:
MatchID | status | date | short (home) | opponent (Away)
1 ENDED XXX TEAM A TEAM B
2 ENDED XXX TEAM B TEAM A
3 ENDED XXX TEAM C TEAM B
4 ENDED XXX TEAM D TEAM A
I have a lot of matches and I want to make a module where I can show all previous matches between team A and team B. (even if is at home or away).
Right now this is my code, but is only showing 1 match of 2 possible matches.
$ergebnis = safe_query("SELECT * FROM table WHERE status='ENDED' AND ((opponent = '".$opp_match."' AND short = '".$short_match."') OR (opponent = '".$short_match."' AND short = '".$opp_match."')) ORDER BY date LIMIT 0,5");
And I want to limit 0,5. Just want the last 5 matches between the 2 teams.
"opp_match" and "short_match" are connections to the other module. When I'm check the match I can check home team and away team with those.
With or without the limits I can't show more than one result.
I just want to show matchID 1 and 2. But right now I'm just getting matchID 1.
EDIT: I tried another way but I'm loading all ended matches.
$ergebnis = safe_query("SELECT * FROM ".PREFIX."upcoming WHERE status='ENDED'");
$i=1;
while($ds=mysql_fetch_array($ergebnis)) {
if($ds['short']==$short_match AND $ds['opponent']==$opp_match) {
eval ("\$matches = \"".gettemplate("matches")."\";");
echo $matches;
}
elseif($ds['opponent']==$short_match AND $ds['short']==$opp_match) {
eval ("\$matches = \"".gettemplate("matches")."\";");
echo $matches;
}
else echo '';
$i++;
}
Will this work?
SELECT *
FROM table
WHERE status = "ENDED"
AND
(
(opponent = "team1" AND short = "team2")
OR
(opponent = "team2" AND short = "team1")
)
ORDER BY MatchID DESC
LIMIT 5;
This assumes that if one match has a smaller ID than another, it has been played before the other.

Enter into MySQL table column a user name if selected by a user who has also chosen them using php

I would like to set up a voting system to buddy up a fair few people in a table if they select each other. An example table would be
---------------------
User | vote | Buddy |
---------------------
A |C |no |
B |C |C |
C |B |B |
D |G |no |
E |no |no |
F |G |no |
G |A |no |
---------------------
The votes are cast by a radio button form, where users have a choice of anyone but themselves. These choices then update the vote column with the username of the person they have voted for. In this instance user E has not voted, and so has the default 'no'. I have built up to this point.
Next, I would like there to be a comparison of the user and vote column. If one user has voted for another, and they in turn have voted for them, I would like their username added to the users buddy column. In this instance, B would like to buddy with C and C would like to buddy with B, so there is a match and each others users names have been added to the buddy column. Although a couple of people voted for G, G voted for A, and so no further additions to the buddy column need to be added in this instance, so remain at the deafult 'no'.
I've guessed that a way to do it may be to cycle through the user column, select which one they have voted for and compare that choice with the choice of the user they voted for. However, putting this into code has proven very difficult, and so far I've only managed this code below. I tried to narrow it down to only users who have been voted for, before cycling through to save time. I'm not that keen on the 2 selections, I guess that one can go, and I know it won't be ideal to SELECT * for this basic function, but it's there whilst I work on it while the table is in trial mode in case I think of another column that may help.
$sql = <<<SQL
SELECT *
FROM `table`
SQL;
if(!$result = $db->query($sql)) {
die('There was an error running the query [' . $db->error . ']');
}
if ($result->num_rows > 0) {
// output data of each row
while($row = $result->fetch_assoc()) {
$vote = $row['vote'];
$sql2 = <<<SQL
SELECT *
FROM `players`
WHERE user = '$vote';
SQL;
if(!$res = $db->query($sql2)) {
die('There was an error running the query [' . $db->error . ']');
}
if ($res->num_rows > 0) {
while($line = $res->fetch_assoc()) {
$user = $line['user'];
echo $name;
}
}
}
}
Any ideas, answers or tutorials that may help will be much appreciated - let me know if you need any more info. Thanks, Toastie
You would just do this with an update:
update example e join
example e2
on e.user = e2.vote and e2.user = e.vote
set e.buddy = e.vote;

MODx Retrieve data from database and sort into a table

I'm trying to set up a table with scores from the MODX database, this data has been entered in via tv.variables in the MODX admin panel.
My SQL code retrieves my desired data with:
SELECT sc.pagetitle, cv.value, t.name
FROM pphc_site_tmplvar_contentvalues cv, pphc_site_content sc, pphc_site_tmplvars t
WHERE sc.id = cv.contentid
AND cv.tmplvarid = t.id
ORDER BY cv.value * 1 DESC
My SQL retrieves the team name, range of team scores (plays, wins, losses, draws, total points) and if they won/lost/drew.
Below is a screenshot of what I'm after (with the SQL I can get at the moment)
My code at the moment for dealing with the SQL/loop is:
//$output = $x; //get draws, losses, played, points and wins
$id = $x; //id of current page
function sort_by_value($a, $b) {
return $b["value"] - $a["value"];
}
$sql = "SELECT sc.pagetitle, cv.value, t.name
FROM pphc_site_tmplvar_contentvalues cv, pphc_site_content sc, pphc_site_tmplvars t
WHERE sc.id = cv.contentid
AND cv.tmplvarid = t.id
AND sc.id = $id
ORDER BY cv.value * 1 DESC";
$result = $modx->query($sql)->fetchAll();
usort($result, "sort_by_value");
//print_r($result);die();
$html = '';
foreach ($result as $row) {
$html .= ''. $row['value'] .'<br>';
}
return $html;
At the moment all this code does is retrieve the results of each team - Great!
I can't seem to figure out how to lay out the data in table form AND include the team names
Go back to your SQL & rewrite the query so that you get your results like this:
pagetitle | wins | losses | played | draws | points
Bath HC | 5 | 4 | 2 | 5 | 234
then you will be able to:
foreach ($result as $row) {
$html.= $modx->getChunk($rowTpl,$row);
}
return $html;
where $rowTpl is a chunk you have defined in modx ~ something like:
<tr>
<td>[[+pagetitle]]</td>
<td>[[+wins]]</td>
<td>[[+losses]]</td>
<td>[[+played]]</td>
<td>[[+draws]]</td>
<td>[[+points]]</td>
</tr>
It's just a matter of getting the query returning the data formatted in a more useful way.

Quering Friend/Blog database with Codeigniter

I'm making a social media kind of website, where people can post messages and become friends and such.
And so I have a database with the following tables.
Friends
+-----+-----------+----------+----------+--------+
| id | Bevriend | UserID1 | UserID2 | vType |
+-----+-----------+----------+----------+--------+
| 1 | 1 | 1 | 3 | 0 |
+-----+-----------+----------+----------+--------+
Blog
+-----+----------------+-------+---------+
| id | title | text | userid |
+-----+----------------+-------+---------+
| 1 | My first entry | Test | 1 |
+-----+----------------+-------+---------+
I want to write a query of sorts to get the blog posts only from the person I am friends with.
So long as either UserID1 or UserID2 contains the userID from my session AND in the database the bit known as "Bevriend" is set to 1 it should get the records.
I am at a loss, if I need to supply you with more information please let me know.
I am giving you a query that might help. It takes only items from blog which are posted by friend, but not current user. You query should look like (excluding blog posts where the current user is the author):
SELECT
b.*
FROM Blog b
INNER JOIN Friends f ON (b.userid = f.UserID1 OR b.userid = f.UserID2)
WHERE
f.Bevriend = 1
AND
(
b.userid = f.UserID1 AND f.UserID2 = '{$currentUserID}'
OR
b.user_id = f.UserID2 AND f.UserID1 = '{$currentUserID}'
)
What we do here is to JOIN the table this way that either UserID1 or UserID2 appear as Blog record "owner". Then in the where clause we say give me only these blog records where where the owner is my friend and not me, for each column UserID1 and UserID2.
This should look like in CI (including the blog posts where current user is the author):
$currentUserID = (int) $this->session->userdata('userID');
$page = (int) $this->input->get('page'); // example retrieving of desired page num
$pagesize = (int) $this->config->item('blog_pagesize'); // example retrieving of pagesize
// Prevent unexpected behavior
if(0 >= $page)
$page = 1;
// Prevent unexpected behavior
if(0 >= $pagesize)
$pagesize = 20; // If config is wrong we ensure it will continue to work
// Additional page checks go here...
$query = $this->db->query('
SELECT
b.*
FROM Blog b
INNER JOIN Friends f ON (b.userid = f.UserID1 OR b.userid = f.UserID2)
WHERE
f.Bevriend = 1
AND
( f.UserID2 = ? OR UserID1 = ?)
LIMIT ?, ?
', array($currentUserID, $currentUserID, ($page - 1) * $pagesize, $pagesize));
foreach ($query->result() as $row) {
echo $row->id;
echo $row->title;
echo $row->text;
}
// Clean up at the end
$query->free_result();
You could adapt the columns you want to retrieve, ordering and remove duplications with GROUP BY or DISTINCT. We run custom query here, because is much more cleaner that using $this->db->select, $this->db->from, $this->db->join,$this->db->where, etc. when the query got more complex. If you use them you have to pass third paramater true to disable each of these functions escaping and it will look like "ugly code".
You cannot use pagination directly with custom query through $this->db->query. This is because it is CI query, but not an active record. Below I translate the query into active record one.
To use the default CI active record functionality:
$results = $this->db->select('b.*')
->from('Blog b')
->join('Friends f', 'b.userid = f.UserID1 OR b.userid = f.UserID2')
->where('f.Bevriend', 1)
->where("(f.UserID1 = {$currentUserID} OR f.UserID2 = {$currentUserID})")
->group_by('b.id')
//->get()
//->results();
The example above should enable you to use the default pagination, although I find it not very useful and flexible.

PHP & Mysql updating many to many relationship with checkbox form

I have a many to many table structure and updating checkbox forms.
The goal is to relate the users table to the projects table with the users_project table. This way there can be many users per project and many projects per user.
The form would on each user edit page would look something like this
<form action="#" method="post">
<div>
<input type="checkbox" name="project_id[]" id="1" value="1">
<label for="1">project 1</label>
<br>
<input type="checkbox" name="project_id[]" id="2" value="2">
<label for="2">project 2</label>
<br>
<input type="hidden" name="editing">
<input type="submit" id="submit" value="submit">
</div>
</form>
Here are examples of the three tables.
users table
+----+-----------+
| id ¦ username ¦
+----+-----------+
| 1 ¦ user 1 ¦
| 2 ¦ user 2 ¦
+----+-----------+
projects table
+----+-----------+
¦ id ¦ title ¦
+----+-----------+
| 1 ¦ project 1 ¦
| 2 ¦ project 2 ¦
+----+-----------+
user_projects table
this table relates the two above tables based on their id
+----+-------------+---------+
| id ¦ project_id ¦ user_id |
+----+-------------+---------+
| 1 ¦ 1 ¦ 2 |
| 2 ¦ 2 ¦ 1 |
+----+-------------+---------+
I have made a checkbox form to add and edit these values. On each user page it displays all of the projects in the projects table. Then queries the user_projects table and finds a list of matches to add checks to the checkboxes.
But how do I edit these values to the database? How will I know if a user has unchecked a previously checked box or checked an empty box and update to the database without looping a query for a match on the users table for project_id and user_id?
Here is a rough concept of what I would like the end result to achieve.
if ($_POST['editing']) {
$totalprojects = $_POST['editing'];
$query = "
SELECT *
FROM user_projects
WHERE user_id = user_id
AND project_id = project_id
";
$result = $mysqli->query($query);
$count = $mysqli->affected_rows;
for($i=0; $i < $totalprojects; $i++) {
if ($count == 1) {
if ($box == checked){
//do nothing
}
else {
//delete from database
}
}
if ($count == 0) {
if ($box == checked){
//add to database
}
else {
//do nothing
}
}
}
}
This just doesn't seem like a good idea at all since I would have to query the database at least once for every project in the project table. There must be a better solution for what I imagine to be a common problem. I know I am just thinking about this the wrong way.
NOTE: I've thought about just serializing an array and sticking it in the user column, but this is not acceptable since I would not be able to relate project to user only user to project and defeat the purpose.
I would like this to be implemented without any javascript trickery.
Since your number of projects is small enough to display in one table, I'm totally in favour of rewriting the relevant part of the user_projects table on submit:
BEGIN TRANSACTION;
DELETE FROM user_projects WHERE user_id = $uid;
INSERT INTO user_projects (project_id, user_id)
VALUES ($proj1, $uid), ($proj2, $uid), ...;
COMMIT TRANSACTION;
Note the use of the extended INSERT syntax to write the association in one statement.
You can also drop the user_projects.id if you don't need it, saving a third of the space in the table.
That's how I solve this problem:
First of all, I'm using active column to check if an item is deleted (0) or not (1) as it is more secure then using DELETE sql statement
Next you need to define user_id and project_id columns as UNIQUE
ALTER TABLE `user_projects` ADD UNIQUE INDEX(user_id, user_project_id)
The code
$uid = $_POST['uid'];
$pids = $_POST['project_id'];
// "deleting" all entries
$chb = $db->prepare("UPDATE `user_projects` SET `active`=0 WHERE `user_id`=?");
$chb->execute(array($uid));
//updating checked checkboxes
foreach ($pids as $pid) {
$chb = $db->prepare("INSERT INTO `user_projects` VALUES (?, ?, 1) ON DUPLICATE KEY UPDATE `active`=1");
$chb->execute(array($uid, $pid));
}
-PDO is used in all examples
Well, assuming every user is eligible to work on every project, on your form-processing page you want to do something like this:
$query = 'SELECT p.id AS project_id, u_p.id AS user_project_id '.
'FROM projects AS p '.
'LEFT JOIN user_projects AS u_p ON (u_p.project_id = p.id AND u_p.user_id = ?)';
By left joining, this will give you a list of all the projects in the system, and the second column will only have a value if the user that you're editing is already on that project. For the example data you gave, this is the result that you would get, querying on user_id 1:
+------------+-----------------+
| project_id | user_project_id |
+------------+-----------------+
| 1 | |
| 2 | 2 |
+------------+-----------------+
Then loop over the results from this query, and for each result, check if a value in $_POST was sent for the corresponding project_id. If a value was sent (the checkbox was checked) and the row's user_project_id is empty, insert a row to user_projects to add this link. If a value was not sent (checkbox was unchecked) and the row's user_project_id is set, delete that row from user_projects using user_project_id as the key. Otherwise do nothing, they didn't change from the way it was before.
Hope that's clear, let me know if you want me to go into more detail.
You can group the actions and reduce the script to 3 queries.
Retreive the current selected option using the LEFT JOIN from Chad Birch anwser.
Before the loop:
$delete_ids = array();
$add_ids = array();
Inside the loop:
// delete from database
$delete_ids[] = $project_id;
and
// add to database
$add_values[] = '('.$user_id.', '.$project_id.')';
And after the for loop:
if (count($delete_ids) > 0) {
mysql_query('DELETE FROM user_projects WHERE user_id = '.$user_id.' AND project_id IN ('.implode(', ', $delete_ids).')');
}
if (count($add_ids) > 0) {
mysql_query('INSERT INTO user_projects (user_id, project_id) VALUES '.implode($add_values));
}

Categories