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));
}
Related
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;
I got the two tables(Table1 and Table2):
Table1:
id hits url
1 11 a
2 5 b
3 6 c
4 99 d
5 14 e
Table2:
id url 2014.04.13 2014.04.14
1 a 0 5
2 b 0 1
3 c 0 3
4 d 0 60
5 e 0 10
hi all,
Table1 one contains the actual hits(which are always up-to-date) and Table2 to statistics(which are done every day at midnight). The columns id(unique number) and url are in both tables the same. So they got the same amount of rows.
So i create every day a new column(with the date of today) and copy the column hits from the table 'Table1' into the new created column into the table 'Table2'
First i alter Table2:
$st = $pdo->prepare("ALTER TABLE Table2 ADD `$today_date` INT(4) NOT NULL");
$st->execute();
Then i cache all entries i need from Table1:
$c = 0;
$id = array();
$hits = array();
$sql = "SELECT id, hits FROM Table1 ORDER BY id ASC";
$stmt = $pdo->query($sql);
while($row = $stmt->fetch(PDO::FETCH_ASSOC))
{
$id[$c] = $row['id'];
$hits[$c] = $row['hits'];
$c++;
}
At last i update Table2:
for ($d = 0 ; $d < $c ; $d++)
{
$id_insert = $id[$d];
$sql = "UPDATE DOWNLOADS_TEST SET `$datum_det_dwnloads`=? WHERE id=?";
$q = $pdo->prepare($sql);
$q->execute(array($hits[$d], $id[$d]));
if($q->rowCount() == 1 or $hits[$d] == 0) // success
$hits[$d] = 0;
else // error inserting (e.g. index not found)
$d_error = 1; // error :( //
}
So what i need is to copy(insert) a column from one table to another.
The two tables are having ~2000 elements and the copying as described above takes around 40 sec. The bottleneck is the last part (inserting into the Table2) as i found out.
One thing i found is to do multiple updates in one query. Is there anything i can do besides that?
I hope you realise that at some point your table will have irrational number of columns and will be highly inefficent. I strongly advise you to use other solution, for example another table that holds data for each row for each day.
Let's say you have a table with 2000 rows and two columns: ID and URL. Now you want to know the count of hits for each URL so you add column HITS. But then you realise you will need to know the count of hits for each URL for every date, so your best bet is to split the tables. At this moment you have one table:
Table A (A_ID, URL, HITS)
Now remove HITS from Table A and create Table B with ID and HITS attributes). Now you have:
Table A (A_ID, URL)
Table B (B_ID, HITS)
Next move is to connect those two tables:
Table A (A_ID, URL)
Table B (B_ID, A_ID, HITS)
Where A_ID is foreign key to attribute "A_ID" of Table A. In the end it's the same as first step. But now it's easy to add date attribute to Table B:
Table A (A_ID, URL)
Table B (B_ID, A_ID, HITS, DATE)
And you have your solution for database structure. You will have a lot of entries in table B, but it's still better than a lot of columns. Example of how it would look like:
Table A | A_ID | URL
0 index
1 contact
Table B | B_ID | A_ID | HITS | DATE
0 0 23 12.04.2013
1 1 12 12.04.2013
2 0 219 13.04.2013
3 1 99 13.04.2013
You can also make unique index of A_ID and DATE in Table B, but I prefer to work on IDs even on linking tables.
I need to sum the totals of a row except the first column.
Something similar too:
SELECT SUM( col2 + col3 + col4 +colN)
FROM numbers
WHERE user_name = 'person';
My table will continuously have columns added to it. So I want it to automatically pick up the sum of the new columns too without it needing to be hard coded into the query?
user_name | Col | col2 | Col3 | Col4 + other columns.
person1 | 2 | 3 | 76 | 56 etc. ---------> sum of row
person2 | 6 | 72 | 200 | 13 etc. ---------> sum of row
Thanks in advance for any help!
Not wishing to 'avoid' the question, but it looks like you could do with having a different data structure.
You should consider having a 'users' table with columns for id and user_name, and a new table (e.g. properties) with a row for each of the other columns in your current table (Col1, Col2 ... ColN). The new table would then have a column for user_name to link it to the users table.
That way you'd be able to do something like:
SELECT SUM(property_column) FROM properties WHERE user_name = <RequiredUserName>
I'd also recommend selecting users by ID (i.e. have the properties table with a user_id column, rather than a user_name column), unless you're confident that a user_name is never going to change (and even then...).
Maybe the easiest solution is to do it in PHP:
$res = mysql_query("select * from numbers where user = ...");
while ($row = mysql_fetch_assoc($res)) {
$row['Id'] = 0; // don't want to sum the Id
$sum = array_sum($row); // this is the required sum
....
}
As stated you would be better advised revisiting your database structure,
If you can't and
If you want to do it in PHP you can get the resultset back and then loop through the fields and exclude the fields you don't want then add up everything else, assuming it is of the right type, use mysql_field_type to find those of a specific type.
Lets see if I can explain this. I am displaying a table in PHP with up/downvote arrows. PHP calls a MySQL query to get the data, then places it in a table with a "while" loop. During this loop, I want to check and see if a user has already upvoted a row, and represent that with a different looking up arrow, etc. This is how I have gone about this so far.
If a user upvotes something, it is stored in a mysql db that looks something like this:
username| upvote| item_id
Bob | 1 | 2293
Bob | 1 | 2295
Sally | 1 | 2295
How do I tell php to check if "Bob" has a "1" on item "2293" in the middle of a while loop of a different MySQL array?
echo '<table>';
while ($row = mysqli_fetch_array($data)) {
echo '<tr>';
//insert php statement checking $row2 to see if Bob has upvoted the data in this row
//so I can place the appropriate arrow here
echo '</tr></table>';
}
What you need here is probably a MySQL join query. An example could be:
Your existing SQL:
SELECT * FROM `items`
We then join all rows from table "upvotes", but only those rows which the current user has placed:
The final SQL:
SELECT `items`.*, COUNT(`upvotes`.`item_id`) AS `upvotes` FROM `items` LEFT JOIN (`upvotes`) ON (`upvotes`.`username` = $currentUserName AND `items`.`id` = `upvotes`.`item_id`) GROUP BY `items`.`id`
Then you should be able to use the same PHP code, but now you can check if "$row['upvotes'] > 0".
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!';
}
}
}