Creating a directory tree with SQL in MySQL - php

I am using php to make directory tree list. Directory(data) are from a MySQL database table but I do not have the idea to write the sql to get the records with the order I want:
Create sql of the table:
CREATE TABLE section (
section_id tinyint(4) NOT NULL auto_increment,
name varchar(500) default NULL,
parent_id tinyint(4) default NULL,
lineage varchar(45) default NULL,
level tinyint(4) default NULL,
PRIMARY KEY (section_id)
) ENGINE=MyISAM AUTO_INCREMENT=15 DEFAULT CHARSET=utf8;
And the insert SQL of data:
INSERT INTO section (
section_id,name,lineage,parent_id,level)
VALUES
('1', 'Dashboard', '1', '0', '1'),
('2', 'Slider', '2', '0', '1'),
('3', 'Column', '3', '0', '1'),
('4', 'Column list', '3-4', '3', '2'),
('5', 'Add column', '3-5', '3', '2'),
('6', 'Permission', '6', '0', '1'),
('7', 'Permission Group', '6-7', '6', '2'),
('8', 'User List', '6-8', '6', '2'),
('9', 'Section permission', '6-9', '6', '2'),
('10', 'Add permission', '6-7-10', '7', '3'),
('11', 'Add user', '6-8-11', '8', '3'),
('12', 'Add section', '6-9-12', '9', '3');
All records of the table which's created:
section_id name parent_id level
-----------------------------------------------------
1 Dashboard 0 1
2 Slider 0 1
3 Column 0 1
4 Column list 3 2
5 Add column 3 2
6 Permission 0 1
7 Permission Group 6 2
8 User List 6 2
9 Section permission 6 2
10 Add permission 7 3
11 Add user 8 3
12 Add section 9 3
I would like to use a/some SQL get the rows in this order:
section_id name parent_id level
-----------------------------------------------------
1 Dashboard 0 1
2 Slider 0 1
3 Column 0 1
4 Column list 3 2
5 Add column 3 2
6 Permission 0 1
7 Permission Group 6 2
10 Add permission 7 3
8 User List 6 2
11 Add user 8 3
9 Section permission 6 2
12 Add section 9 3
For making this directory tree:
- Dashboard
- Slider
- Column
- Column list
- Add column
- Permission
- Permission Group
- Add permission
- User List
- Add user
- Section permission
- Add Section
I have an idea using many SQL to get the directories and store the data into an php object.
And this is the sql to get sub-directory:
SELECT * FROM tbl_section AS a1 WHERE a1.parent_id = 0;
SELECT * FROM tbl_section AS a1 WHERE a1.parent_id = 1;
SELECT * FROM tbl_section AS a1 WHERE a1.parent_id = 2;
SELECT * FROM tbl_section AS a1 WHERE a1.parent_id = 3;
and so on.
However, it needs run the select sql 100 times if there is 100 directories,
i don't think it's a good method, any others idea?

This method (Lineage Column) may not is the best and most dynamic way to list out the directory tree which get data from MySQL table, but this is quite easy and fast:
Related question in stackoverflow:
What are the options for storing hierarchical data in a relational database?
Referenced article:
Lineage Column (a.k.a. Materialized Path, Path Enumeration)
http://www.ferdychristant.com/blog/articles/DOMM-7QJPM7
This is the Select SQL by using "Lineage Column" method:
SELECT c.section_id, c.name, c.lineage, c.level, c.parent_id,
(SELECT COUNT(*) FROM section
where section.lineage
LIKE (CONCAT(c.lineage,'%')) AND
section.lineage!=c.lineage) as replies
FROM section as c
order by c.lineage;
And it returns the record with the order i want:
section_id name lineage level parent_id replies
----------------------------------------------------------------------
1 Dashboard 1 1 0 0
2 Slider 2 1 0 0
3 Column 3 1 0 2
4 Column list 3-4 2 3 0
5 Add column 3-5 2 3 0
6 Permission 6 1 0 6
7 Permission Group 6-7 2 6 1
10 Add permission 6-7-10 3 7 0
8 User List 6-8 2 6 1
11 Add user 6-8-11 3 8 0
9 Section permission 6-9 2 6 1
12 Add section 6-9-12 3 9 0
This is the php code to echo the directory mentioned at the question:
<?php
CONST SERVERNAME = ""; //your mysql server name
CONST USERNAME = ""; //your mysql user name
CONST PASSWORD = ""; //your mysql password
CONST DATABASE = ""; //your mysql database name
// Create connection
$conn = new mysqli(SERVERNAME, USERNAME, PASSWORD, DATABASE);
// Check connection
if ($conn->connect_error) {
die("Connection failed: " . $conn->connect_error);
}
$sql = "SELECT c.section_id, c.name, c.lineage, c.level, c.parent_id,
(SELECT COUNT(*) FROM section
where section.lineage
LIKE (CONCAT(c.lineage,'%')) AND
section.lineage!=c.lineage) as replies
FROM section as c
order by c.lineage";
$html = "";
foreach ($conn->query($sql) as $row) {
switch ($row['level']){
case "1": $html.="-".$row['name'] . "<br />"; break;
case "2": $html.=" -".$row['name'] . "<br />"; break;
case "3": $html.=" -".$row['name'] . "<br />"; break;
}
}
print $html;
?>
Result of the php code:
-Dashboard
-Slider
-Column
-Column list
-Add column
-Permission
-Permission Group
-Add permission
-User List
-Add user
-Section permission
-Add section

Related

How to manipulate and collate data in an associate array

I have the following array of data
2889 1 1062
2889 8 John Smith
2889 6 0.29
2891 1 1117
2891 8 Jamie Dean
2891 6 2
2892 1 1062
2892 8 John Smith
2892 6 4
The First column is a list of entry IDs relating to form entries from a website, the second column is a list of meta_keys relating to fields in the form, and the final column is the data from those fields.
What I need to be able to do is collate the data in the array so that for each person I have:
ID Number(Meta_key 1)
Name(Meta_key 8)
Sum(Hours Owed(Meta_key 6))
I am lost on how to even start this task, any help would be very much appreciated.
This data has all been pulled from a database with the following query:
select
entry_id,
meta_key,
meta_value
from
staff_gf_entry_meta
where
form_id = 48
and
entry_id in (
select
entry_id
from
staff_gf_entry_meta
where
meta_key = 7
and
form_id = 48
and
meta_value <= '2018-12-18'
and
meta_value >= '2018-12-12'
)
and (
meta_key = 1
or
meta_key = 8
or
meta_key = 6)
If needed the query can be altered.
You might want a GROUP BY statement with the SUM aggregate function. To get all the key/value pairs in a row, you need to JOIN multiple queries.
SELECT
`mk1`.`meta_value` `Number`,
`mk8`.`meta_value` `Name`,
SUM(`mk6`.`meta_value`) `Hours Owed`
FROM
`staff_gf_entry_meta` `mk1`
INNER JOIN
`staff_gf_entry_meta` `mk6`
USING
(`entry_id`, `form_id`)
INNER JOIN
`staff_gf_entry_meta` `mk7`
USING
(`entry_id`, `form_id`)
INNER JOIN
`staff_gf_entry_meta` `mk8`
USING
(`entry_id`, `form_id`)
WHERE
`mk1`.`meta_key` = 1
AND
`mk6`.`meta_key` = 6
AND
`mk7`.`meta_key` = 7
AND
`mk8`.`meta_key` = 8
AND
`mk1`.`form_id` = 48
AND
`mk7`.`meta_value` BETWEEN '2018-12-12' AND '2018-12-18'
GROUP BY `mk1`.`meta_value`,`mk1`.`form_id`
;
I've assumed the following table structure and data:
CREATE TABLE `staff_gf_entry_meta`
(
`form_id` int(11) NOT NULL,
`entry_id` int(11) NOT NULL,
`meta_key` int(11) NOT NULL,
`meta_value` varchar(45) DEFAULT NULL,
PRIMARY KEY (`entry_id`,`form_id`,`meta_key`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
;
INSERT INTO `staff_gf_entry_meta` (form_id, entry_id, meta_key, meta_value)
VALUES
(48, 2889, 1, 1062),
(48, 2889, 8, 'John Smith'),
(48, 2889, 6, 0.29),
(48, 2891, 1, 1117),
(48, 2891, 8, 'Jamie Dean'),
(48, 2891, 6, 2),
(48, 2892, 1, 1062),
(48, 2892, 8, 'John Smith'),
(48, 2892, 6, 4)
;
INSERT INTO `staff_gf_entry_meta`
SELECT DISTINCT form_id, entry_id, 7 meta_key, '2018-12-17' meta_value
FROM testdb.staff_gf_entry_meta
;
Result:
# Number, Name, Hours Owed
'1062', 'John Smith', '4.29'
'1117', 'Jamie Dean', '2'
Start with something like below, and build from there.
$meta_labels = [
1 => 'ID'
8 => 'name'
6 => 'hours'
];
$output = [];
foreach($results as result) {
$eid = $result['entry_id'];
$label = $meta_labels[$result['meta_key']];
$output[$eid][$label] = $result['meta_value'];
}
And ideally the mapping between metadata IDs and their labels should be stored in the database, not hard-coded in the application.

using mysql (too much data) to get statics about activity of different users

I'm trying to figure out how to make a query to get the data of top then users of some list ...later I will use php to statics and graphs but what I want is to get the sum of every single user but I find it difficult because in one month one single user can make many registers on database and I want to manage it and then get the top ten of users who have more activities (on this case with more quantity per month).
Example
user_id quantity date kind
user1 23 17/03/18 free
user1 3 17/03/18 charge
user2 5 17/03/18 free
user2 5 17/03/18 free
user2 8 18/03/18 free
user3 9 19/03/18 free
user3 1 20/03/18 free
user3 1 20/03/18 charge
user3 3 20/03/18 charge
user3 5 20/03/18 charge
You can store arrays in a database by serializing them, inserting them into the database, and then unserializing them when you retrieve from the database.
Arrays can be multidimensional in PHP, that means that arrays can store arrays and so on. Also arrays can be relational, meaning that you can store values and give an identifier ('key') to each value.
Knowing that, you can do stuff like:
$con = new mysqli($host, $username, $password, $db_name);
$stmt = $con->prepare('SELECT * FROM users WHERE user_id=?;');
$stmt->bind_param('s', $id);
$stmt->execute();
$result = $stmt->get_result()->fetch_array();
if ($array_data = unserialize($result['data'])) {
echo 'The quantity related to ', $result['name'], ' is ', $array_data['quantity'];
} else {
echo 'It seems that there\'s nothing related to ', $result['name'], ' yet. Adding a field now.';
$array_data = array(
'quantity' => 23,
'date' => date('l jS \of F Y h:i:s A'),
'kind' => 'free'
);
}
$array_data = serialize($array_data);
$stmt2 = $con->prepare('UPDATE users SET data=? WHERE user_id=?;');
$stmt2->bind_param('ss', $array_data, $id);
$stmt2->execute();
The example above is pretty abstract, but you can get a grasp of what you can do with serialization and arrays. MySQL does not have an "array" type, but you can use serialize and unserialize with almost all objects in PHP to store them in databases.
Note that the serialized fields' type in MySQL should be "BLOB".
I think that a simple count query will do the job that you want. Something like this:
Select user_id, sum(quantity) as activity-count
From ActivityTable
Group By user_id
Where kind = something; # (Optional)
I don't have so much information here about your database, so all I got is a direction for your target task.
A little test that I have run:
Table:
userid, quantity, date, kind
'0', '3', '2017-03-18', 'charge'
'0', '23', '2017-03-18', 'free'
'0', '3', '2018-03-18', 'charge'
'2', '5', '2017-03-18', 'charge'
'2', '5', '2017-03-18', 'free'
'2', '8', '2018-03-18', 'free'
'3', '9', '2019-03-18', 'free'
'3', '1', '2020-03-18', 'charge'
'3', '1', '2020-03-18', 'free'
'3', '3', '2021-03-18', 'charge'
'3', '3', '2022-03-18', 'charge'
Query:
Select userid, month(date) as 'month', sum(quantity) as activity_count
From table1
Group By userid, month(date)
Result:
userid, month, activity_count
'0', '3', '29'
'2', '3', '18'
'3', '3', '17'
This query should return you the list of top 10 users for the month:
select user_id, SUM(quantity) as qty
from table_name
group by user_id
where MONTH(CURDATE())= MONTH(date) AND
YEAR(CURRENT_DATE()) = YEAR(date)
Order by qty DESC
LIMIT 0,10;

How can i remove the oldest entries in my SQL result?

I want to get only the last reports, for all items but only the most recent report for each item_id.
Here is my current solution:
SELECT distinct * FROM t.reports ORDER BY created DESC LIMIT 100
My table consists of the following columns:
id | user | item_id | created
'2', '1', '2643', '2017-06-13 16:28:34'
'3', '1', '19333', '2017-06-13 19:26:56'
'4', '1', '19333', '2017-06-13 19:29:24'
'5', '1', '1319', '2017-06-13 19:29:56'
'6', '1', '1319', '2017-06-13 19:30:16'
'7', '1', '1319', '2017-06-13 19:30:17'
'8', '1', '1319', '2017-06-13 19:30:18'
'9', '1', '1319', '2017-06-13 19:30:25'
'10','1', '1319', '2017-06-13 19:31:51'
I want no duplicate item_ids AND only the most recent entry for that item.
BUT i also want ALL reports, but no duplicate item reports!
EXAMPLE:
i expect that when i execute my query, i only get row 2,4 and 10 returned.
Try this:
select a.id,a.user,a.item_id,a.created
from reports as a
where a.created=(select max(created)
from reports as b
where a.item_id=b.item_id)
Try This let me know if this works.
SELECT *
FROM t.reports
where item_id in (
select distinct item_id
from t.reports
)
ORDER BY updated DESC
LIMIT 100
Try with this I have tested it on my test database:
SELECT DISTINCT item_id, MAX(created), id, user
FROM `t.reports`
GROUP BY item_id
ORDER BY MAX(created) DESC
You can use group by :
SELECT * FROM t.reports WHERE created IN (SELECT max(created) FROM t.reports)
GROUP BY item_id
LIMIT 100

Order by specific field values in CakePHP

I have array $productsTop
(int) 0 => '6
(int) 1 => '4',
(int) 2 => '1',
(int) 3 => '2',
(int) 4 => '3',
(int) 5 => '5'
where 6 4 1 2 3 5 are id's of product table.
I want to display only 5 products from my products table but not order by ID ASC or DSC,
I want to order them by ID exactly like they are ordered in arrar so it can first show product ID 6, then ID 4... to ID 5.
Can anybody help please.
$productTop = [6,4,1,2,3,5];
$product = $this->Product->find('all', array(
'conditions'=>array('Product.id'=>$productTop),
'limit'=>5
));
In this way it shows me all products but ordered by their ID so instead of showing me:
6,4,1,2,3,5
it shows:
1,2,3,4,5,6
You can order by specified values like this:
'order' => "FIELD(id, '6', '4', '1', '3', '2', '5')"
You can add like this. :)
->order(["FIELD(Product.id, '228')"=>'DESC', "Product.id"=>'DESC'])

Checking users friendship

Continuing this question,
in my web app, I want to allow users to add friends, like facebook, in my previous question, I finally decided to have the database structure as #yiding said:
I would de-normalize the relation such that it's symmetric. That is,
if 1 and 2 are friends, i'd have two rows (1,2) and (2,1).
The disadvantage is that it's twice the size, and you have to do 2
writes when forming and breaking friendships. The advantage is all
your read queries are simpler. This is probably a good trade-off
because most of the time you are reading instead of writing.
This has the added advantage that if you eventually outgrow one
database and decide to do user-sharding, you don't have to traverse
every other db shard to find out who a person's friends are.
So, now if user 1 adds user 2, and user 5 adds 2, something like this will go into the db:
ROW_ID USER_ID FRIEND_ID STATUS
1 1 2 0
2 2 1 0
3 5 2 0
4 2 5 0
As you see, we insert the row of the "REQUEST SENDER" first, so now imagine that user 5 is logged in, and we want to show him the friendship requests, here is my query:
$check_requests = mysql_query("SELECT * FROM friends_tbl WHERE FRIEND_ID = '5'");
the above query, will fetch ROW_ID = 4, this means with the above query shows us that user 2 has added 5, but he has NOT, actually the user 5 added user 2, so here we should not show any friendship requests for user 5, instead we need to show it for user 2.
How I'm supposed to check this correctly?
This is an edited answer.
Your SQL query should look like this:
SELECT USER_ID, FRIEND_ID FROM friends_tbl WHERE FRIEND_ID = '5' OR USER_ID = '5'
Then you have to parse your result in this way. Assuming you have got a php array like this:
$result = array(
0 => array(
'USER_ID' => 5,
'FRIEND_ID' => 2
),
1 => array(
'USER_ID' => 2,
'FRIEND_ID' => 5
)
2 => array(
'USER_ID' => 5,
'FRIEND_ID' => 8
),
3 => array(
'USER_ID' => 8,
'FRIEND_ID' => 5
)
)
You just have to get the even rows:
$result_final = array();
for($i = 0; $i < count($result); $i++) {
if($i % 2 == 0) $result_final[] = $result[$i];
}
Then you will have an array like this:
$result = array(
0 => array(
'USER_ID' => 5,
'FRIEND_ID' => 2
),
1 => array(
'USER_ID' => 5,
'FRIEND_ID' => 8
)
)
Alternative method: Make your SQL look like this:
SELECT FRIEND_ID FROM friends_tbl WHERE USER_ID = '5'
That's all.
Friendship query notifies should be placed in something like message inbox. Relation you described is meant to hold, well, friendship relations, not the fact of the event happening itself. You should consider create relation to hold notifies and fill it properly alongside with two inserts on friends_tbl
You'll need to hold a temporary table (or fixed - for data mining) which has all the requests made from one user to another, for example:
table: friendRequest
inviterId inviteeId status tstamp
2 5 0 NOW()
5 8 0 NOW()
assuming that 0 is unapproved.
Than you'll query for all pending requests
SELECT * FROM friendRequest WHERE invitee_id = :currentLoggedUserId AND status = 0
Once a user approved a user, you'll create a transaction, describing this newly formed relation and updating the friendRequests table
You could also query this way assymetric relations, where a user has many followers, by looking for un-mutual friendships.

Categories