I have a permissions table:
| id | name | Other stuff | parent_id
| 1 | name1 | ... | 3
| 2 | name2 | ... | 3
| 3 | groupName1| ... | null
Inside the permissions table i have the permissions themsleves, which are the ones that do have parent_id, i also have the groups (parents) which do not have parent_id because they are the parents.
So let's say i want to organize the groups in 'li' tags and inside those i want to list all the permissions that belong to that group.
I need the SQL that shows all the group names, and foreach group show the permissions. I assume that requires a couple of 'joins', (i'm kinda of a noob in sql).
Thanks in advance.
EDIT
Here's the current result: open this
See where it says 'Ver' , 'Opção 3' and 'ADMIN' , they are aligned, i wish the same result to both colums above those ('listagem' and 'manutenções').
Here's my current code for reference:
<li class="dropdown dropdown-large">
MENU <b class="caret"></b>
<ul class="dropdown-menu dropdown-menu-large row">
<?php
$campos = DB::table('permissions as permission')
->select('permission.name as Grupo' , 'p.id' , 'p.name', 'p.parent_id', 'p.url')
->join('permissions as p','permission.id','=','p.parent_id')
->orderBy('permission.name')
->distinct()->get();
$nomeAntigo = null;
foreach($campos as $campo) {
$data = (array) $campo;
//SO METE AS PERMISSOES QUE O TIPO DE PERFIL PERMITE
if (Auth::user()->can($data['name'])){
if($nomeAntigo !== $data['Grupo']) {
$nomeAntigo = $data['Grupo'];
echo '<li class="col-sm-3">';
echo '<ul>';
echo ' <li class="dropdown-header"><b>'.$data['Grupo'].'</b></li>';
echo '</ul>';
echo '</li>';
}
echo '<li>'.$data['name'].'</li>';
}
}
?>
</ul>
</li>
there are different solutions.
1) use multiple querys (prepared statements would be perfect). First query returns the groups. For each group query again against the database to return the permissions for each group.
$prep = $pdo->prepare("SELECT id, name FROM permissions WHERE parant_id=?");
foreach($pdo->query("SELECT name FROM permissions WHERE parent_id IS NULL") as $row) {
$r = $prep->execute([$row['id']]);
// loop through it and echo
}
2) I'm not sure what's the correct name for the following is in english. In German it's "Gruppenbruch". Translated into englisch it's something like "control break". (if anybody knows the correct name, please edit or leave a comment)
SELECT pg.name as groupName, p.id, p.name, p.parent_id FROM permissions as pg
INNER JOIN permissions as p ON pg.id = p.parent_id
ORDER BY pg.name, p.name
Here's a working sqlfiddle for the query: http://sqlfiddle.com/#!9/e8fda1/2
now you can do something like
$resultset = $pdo->query(QUERY_FROM_ABOVE); // pdo
$oldGroupName = null;
foreach($resultset as $record) {
if($oldGroupName != $record['groupName']) {
if($oldGroupName != null) echo "</ul>";
echo "<h1>".htmlentities($record['groupName'])."</h1>";
echo "<ul>";
}
echo "<li>".$record['name']."</li>";
}
echo "</ul>";
Related
I have these 2 while loops.
Loop. Left side screen. Shows users attached to a task.
$getcurrentusers = $conn->prepare("SELECT u.userid, u.username, u.Fname, u.inactive, r.userid, r.job_id FROM users AS u INNER JOIN job_responsible AS r ON u.userid = r.userid WHERE u.inactive = 0 AND r.job_id = $getPostID ORDER BY u.Fname");
$getcurrentusers->execute();
$resultgetcurrentusers = $getcurrentusers->get_result();
$getcurrentusers->close();
if ($resultgetcurrentusers->num_rows > 0) {
while($row = $resultgetcurrentusers->fetch_assoc()) {
echo $row["username"]." (".$row["Fname"].")<br />";
}
} else {
echo "Der er ikke valgt nogle ansvarlige til denne opgave.";
}
Loop. Right side screen. List all possible users that can be added to the task
$getdepartmentview = $SetDepartmentView;
$explodegetdepartmentview = explode(",",$getdepartmentview);
$implodegetdepartmentview = "'".implode("','",$explodegetdepartmentview)."'";
$getusers = $conn->prepare("SELECT userid, username, Fname, inactive FROM users WHERE departmentid IN ($implodegetdepartmentview) AND inactive = 0 ORDER BY Fname");
$getusers->execute();
$resultgetusers = $getusers->get_result();
$getusers->close();
if ($resultgetusers->num_rows > 0) {
while($row = $resultgetusers->fetch_assoc()) {
echo "<input type=\"checkbox\" name=\"choose_responsible[]\" value=\"".$row["userid"].",\" />".$row["Fname"]."<br />";
}
}
The problem is, I would really like, that the input checkbox is 'checked' on the right side when there is a match from the left side 1. loop.
So..
Loop 1 | Loop 2
name3 | name1
name5 | name2
name7 | name3 - Inputbox checked
name8 | name4
| name5 - Inputbox checked
| name6
| name7 - Inputbox checked
| name8 - Inputbox checked
| name9
| name10
In the first loop you can store user_id in an array
$user_ids[] = $row["userid"]
In the second loop you check the current user id if it exists in the previous saved $user_ids array
$checked = false;
if (in_array($row["userid"], $user_ids)) {
$checked = true;
}
This task calls for a LEFT JOIN in a single query to avoid multiple trips to the database and to avoid inefficient php processes on the result set.
$sql = <<<SQL
SELECT u.userid,
u.username,
u.Fname,
u.inactive,
r.userid responsibleUserId,
r.job_id
FROM users AS u
LEFT JOIN job_responsible AS r
ON u.userid = r.userid
AND r.job = ?
WHERE u.inactive = 0
ORDER BY u.Fname
SQL;
$stmt = $conn->prepare($sql);
$stmt->bind_param("i", $getPostID);
$result = $stmt->execute();
$users = $result->fetch_all(MYSQLI_ASSOC);
Now you have all of the data that you need and therefore no more reasons to visit the database.
To list the users who are assigned to the targeted job, you iterate the array of associative arrays and show users where job_id is not null.
foreach ($users as $user) {
if ($user['job_id']) {
printf('%s (%s)<br />', $user['username'], $user['Fname']);
}
}
To display all users and conditionally check checboxes next to names that are already "responsible", iterate the array again and rely on job_id to determine the checkbox.
foreach ($users as $user) {
printf(
'<input type="checkbox" name="choose_responsible[]" value="%d"%s/>%s<br />',
$user['userid'],
$user['job_id'] ? ' checked' : '',
$user['Fname']
);
}
I have a table comments, thats look like this, added some mockup content as well:
+------------+---------+----------+-------------------+------------------------------------+---------------------------+
| comment_id | user_id | movie_id | comment_parent_id | comment_content | comment_creation_datetime |
+------------+---------+----------+-------------------+------------------------------------+---------------------------+
| 26 | 1 | 16329 | 0 | Första | 2016-01-24 10:42:49 |
| 27 | 1 | 16329 | 26 | Svar till första | 2016-01-24 10:42:55 |
| 28 | 1 | 16329 | 26 | Andra svar till förta | 2016-01-24 10:43:06 |
| 29 | 1 | 16329 | 28 | Svar till "andra svar till första" | 2016-01-24 10:43:23 |
+------------+---------+----------+-------------------+------------------------------------+---------------------------+
Im trying to display the comments Reddit style, like this image:
Im trying to fetch all comments SELECT * FROM comments WHERE movie_id = :movie_id ORDER BY comment_creation_datetime DESC and then recursively echo them out.
I have tried a bunch of foreachloops, but none is working as expected
foreach($this->comments as $value){ ?>
<div class="comment">
Comment content <?php echo $value->comment_content; ?>
<?php if($value->comment_parent_id > 0){
foreach($value as $sub_comment){ ?>
<div class="comment">
comment comment on comment: <?php echo $value->comment_content; ?>
</div>
<?php }} ?>
</div>
<?php }
My question:
How do I echo out the comments in a nested Reddit style with foreach loop?
You need to both make a list of root comments, and hierarchically organize all of them. You can do both in one go:
$roots = [];
$all = [];
foreach($comments as $comment)
{
// Make sure all have a list of children
$comment->comments = [];
// Store all by ID in associative array
$all[$comment->comment_id] = $comment;
// Store the root comments in the roots array, and the others in their parent
if(empty($comment->comment_parent_id))
$roots[] = $comment;
else
$all[$comment->comment_parent_id]->comments[] = $comment;
}
// Check it's all done correctly!
print_r($roots);
You presorted the list by date, that's preserved in this approach. Also, as you only reorganized by reference this is lightning fast, and ready to be used in templating engines or anything - no need to print out inline like the other answers.
Working with the adjacency list model can be more problematic with SQL. You need to retrieves all the rows with a single query and store a reference of any parent's child in a lookup table.
$sth = $pdo->prepare("SELECT * FROM comments WHERE movie_id = ? ORDER BY comment_creation_datetime DESC");
$sth->execute([$movie_id]);
$comments = $sth->fetchAll(PDO::FETCH_ASSOC);
$lookup_table = [];
foreach ($comments as $comment_key => $comment) {
$lookup_table[$comment['comment_parent_id']][$comment_key] = $comment['comment_id'];
}
Now you can display them with
function recursive_child_display($comments, $lookup_table, $root = 0, $deep = 0)
{
if (isset($lookup_table[$root])) {
foreach ($lookup_table[$root] as $comment_key => $comment_id) {
// You can use $deep to test if you're in a comment of a comment
echo '<div class="comment">';
echo 'Comment content ', $comments[$comment_key]['comment_content'];
recursive_child_display($comments, $lookup_table, $comment_id, $deep+1);
echo '</div>';
}
}
}
Example:
// display all the comments from the root
recursive_child_display($comments, $lookup_table, 0);
// display all comments that are parent of comment_id 26
recursive_child_display($comments, $lookup_table, 26);
I would use some recursive function, you start with the ones with parent_id == 0 and recursively print all those who are their direct children.
This code is not tested, but you can get the idea:
function printComment($comment, $comments)
{
foreach($comments as $c)
{
if($c->parent_id == $comment->comment_id)
{
$output .= "<li>".printCommment($c)."</li>";
}
}
$output = "<ul>".$comment->comment_content."</ul>".$output;
return $output;
}
foreach($this->comments as $comment)
{
if($comment->parent_id == 0)
{
echo printComment($comment,$this->comments);
}
}
Basically I have a database with 66 bible books; some from old testament some from new. The bname value is the NAME of the book, while bsect has a value of O or N(new or old), how can I make my dropdown box dynamically display a book into an old or new optgroup based on whether its' bsect is O or N? My teacher said I have to make some array, but i have no idea how to do it. Any thoughts?
My database sample:
+-----------+-------+
| bname | bsect |
+-----------+-------+
| Genesis | O |
| Exodus | O |
| Leviticus | O |
+-----------+-------+
I don't want to have to rely on manually setting opgroups based on the NUMBER OF THE ENTRY, I want it to be dynamic based on value of bsect.
Right now I just have the following query with a select dropdown which puts the book into old or new based on its record number, but It will break if more books were to be added
$query = $mysqli->query("select distinct bname as Name from kjv");
?>
<select name="book">
<?php
$i=1;
while($option = $query->fetch_object()){
if($i==1) echo "<optgroup label='Old Testament'>";
else if($i==40) echo "<optgroup label='New Testament'>";
echo "<option value='$i'>".$option->Name."</option>";
$i++;
}
?>
Simply order by bsect and display different optgroups dynamically
<?php
$query = $mysqli->query("SELECT DISTINCT bsect, bname AS Name FROM kjv ORDER BY bsect");
?>
<select name="book">
<?php
$i = 1;
$bsect = "";
while($option = $query->fetch_object()){
if($bsect != $option->bsect) {
$bsect = $option->bsect;
echo "<optgroup label='{$bsect}'>";
}
else if($i==40) echo "<optgroup label='New Testament'>";
echo "<option value='$i'>".$option->Name."</option>";
$i++;
}
?>
Of course, then your books may be out of order. So what you would want to do is add a book-order column (border) that stores a number defining how to order the books in a given group, e.g.
ALTER TABLE kjy
ADD COLUMN border INT U?NSIGNED NOT NULL DEFAULT 0;
Then you can update the data to have the proper book order and do a query like this:
SELECT DISTINCT bsect, bname AS Name FROM kjv ORDER BY bsect, border;
Of course, this being the Bible, you aren't going to be adding books, so you can probably just define a static Book ID that defines the ordinality of each book. Then you could just sort by ID and know that "Old" and "New" books are coming out in the right order.
ALTER TABLE kjy
ADD COLUMN id INT UNSIGNED NOT NULL PRIMARY KEY BEFORE (bname);
This is how you can create 2 arrays in php,
$query = $mysqli->query("select distinct bname,bsect from kjv");
while($option = $query->fetch_object()){
if ($option->bsect == 'O'){
$books_old[] = $option->bname;
} elseif ($option->bsect == 'N'){
$books_new[] = $option->bname;
} else {
# Error collection - bsect not 'O' or 'N'
}
}
now you have 2 arrays which are lists of books; $books_old and $books_new.
I'd use the name as the value rather than an arbitrary index;
echo "<optgroup label='Old Testament'>";
foreach($books_old as $this_book){
echo "<option value=\"$this_book\">$this_book</option>";
}
echo "<optgroup label='New Testament'>";
foreach($books_new as $this_book){
echo "<option value=\"$this_book\">$this_book</option>";
}
I have two tables, who are joined and the ID of each table and element underneath are similar.
parentID | objectName | subID ID| className| subName |
_____________________________ ________________________
84 | Test | 14 14| BOM | Test
84 | More | 16 14| PDF | Test
84 | Sub | 15 15| Schematics | Test2
I want to list the categoryname and the subID of the related elements. Several ObjectNames will have several related classes.
PHP code:
$objects = mysqli_query($con,"SELECT * from subobject");
$join = mysqli_query($con, "SELECT * FROM subrelation AS subrelation INNER JOIN subobject AS subobject ON subobject.subId = subrelation.ID;");
echo "<ul>";
while($obj = mysqli_fetch_array($objects) and $row = mysqli_fetch_array($join))
{
echo "<li>". $obj['objectName'];
echo "<ul>";
//ITERATION GOES HERE
if($obj['objectName'] == $row['subName'])
echo "<li>". "$row[className]" . "</li>";
//END OF ITTERATION
echo "</ul>";
echo "</li>";
}
echo "</ul>";
?>
and output list:
-Test
-BOM
-Sub
-Schematics
-More
under each field there are supposed to be more listed values.
It looks like you need to simplify your code a bit. My guess is that your problem is occurring because you have different amounts of rows in each result set. This makes your while loop exit when it finishes going through the smaller result set (probably $objects), even though there's still more elements in the larger set.
A solution is to sort the results of your query, use just one condition in your while loop, and keep track of which objectName you're currently on using a string $curr_objectName:
$join = mysqli_query($con, 'SELECT * FROM subrelation AS subrelation INNER JOIN subobject AS subobject ON subobject.subId = subrelation.ID ORDER BY subobject.objectName;');
$curr_objectName = '';
echo '<ul>';
while($row = mysqli_fetch_array($join)) {
$subName = $row['subName'];
if($subName != $curr_objectName)) {
if($curr_objectName != '') {
#close the previous list
#will be skipped on the first loop iteration
echo '</ul>';
echo '</li>';
}
#start a new list
$curr_objectName = $subName;
echo '<li>'. $obj['objectName'];
echo '<ul>';
} else {
echo '<li>'. $row['className'] . '</li>';
}
}
echo '</ul>';
I have an table structure like this
mysql> SELECT id, name, parent_id FROM categories;
+-------+------------+-----------+
| id | name | parent_id |
+-------+------------+-----------+
| 15790 | Test | 0 |
| 15791 | Test2 | 0 |
| 16079 | Subtest | 15790 |
| 16080 | Subtest 2 | 15790 |
| 16081 | Subsubtest | 16079 |
+-------+------------+-----------+
Now I want to look up the parent for every children and sibling and give it back in the right order for deletion.
So my output in this case would be:
Array
(
16081,
16080,
16079,
15791,
15790
)
I can't delete just by reversing the parent ids, because this should be solid walking back the tree.
Also I am not able/allowed to change the structure of the table. So building kind of an index is necessary.
Assuming you don't have access to TRUNCATE, SET (so you could do SET FOREIGN_KEY_CHECKS=0;), ALTER, etc. etc., and absolutely must use a script:
Since the question is tagged with php, this should do the trick:
function reversetree($src_arr, $currentid = 0)
{
$cats = array();
foreach($src_arr as $id => $parent)
{
if($parent == $currentid)
{
$cats[] = $id;
$cats = array_merge($cats, reversetree($src_arr, $id));
}
}
return !$currentid ? array_reverse($cats) : $cats;
}
$rs = array();
foreach($pdo->query('SELECT id, parent_id FROM categories') as $row)
$rs[$row['id']] = $row['parent_id'];
$stmt = $pdo->prepare('DELETE FROM categories WHERE id = ?');
$pdo->beginTransaction();
foreach(reversetree($rs) as $v)
$stmt->execute(array($v));
$pdo->commit();
I don't understand why you need the IDs in a particular order. You can delete them with a transaction and they will all be deleted simultaneously.
DELETE FROM categories WHERE ID IN (15790,15791,16079,16080,16081);
You could add FOREIGN KEY constraint with CASCADE on DELETE.
The foreign key will point to the same table on the parent id field.
When you delete the parent, all the children (no matter what level) are removed automatically.
<?php
// housekeeping
$pdo = new PDO($dsn, $user, $password);
$select = $pdo->prepare(
"SELECT parent.id AS parent_id, child.id AS child_id
FROM categories AS parent
JOIN categories AS child ON parent.id = child.parent_id
WHERE parent.id = ?"
);
$delete = $pdo->prepare('DELETE FROM categories WHERE id = ?');
// deletes $node_id, deletes its children first if required
function delete_node($node_id){
$select->execute( array($node_id) );
$children = $select->fetchAll(PDO::FETCH_NUM);
if (count($children) !== 0) { // if 0, then the category does not exist, or it has no child
foreach ($children as $child) { // call delete_node() recursively on each child
delete_node ($child[1]);
}
}
$delete->execute( array($node_id) ); // then delete this node (or do nothing if $node_id does not exist)
}
// to delete one category and all its sub-categories
delete_node(15790);
// to delete all contents
$allTopLevel = $pdo->exec('SELECT id FROM categories WHERE parent_id = 0')->fetchAll(PDO::FETCH_NUM);
foreach ($allTopLevel as $node) {
delete_node($node[0]);
}
Not tested, not even sure if it "compiles", but you get the idea. Make sure to lock the table (or start a transaction) before calling delete_node().
Sorry, i can't help much, because SQL is not my thing. But perhaps someone could transfer java pseudo code to the solution
delete(table.getFirstRow().get(id));
delete(id_a){
for(data_set : table)
if(data_set.get(parent_id) == id_a)delete(data_set.get(id));
}
table.remove(id_a);
}
edit: no iteration about elements? So something like this?
delete(list){
if(list.size() == 0)return;
idList = list.getAll(id);
plist = table.getAllWhichEquals(parent_id, idList);
delete(plist);
table.remove(idList);
}
ah, forget it, i'm deleting not all at the same time, was just a try ^^