Foreach group and subgroup dynamic loop - php

I have this problem. I have a table (below) of groups. It's a recursive sort of table because each new group can have a parent group in the same table. So effectively we have a group > subgroup > subgroup > subgroup kinda model.
**id | label | parent_id**
1 | Ceiling| 0
2 | Window | 0
3 | Wall | 0
4 | Small | 2
5 | Large | 2
6 | Large| 1
7 | Paint | 4
So this would give something that looks like this:
Window > Small window > Paint
I've created the forms and table for creating the groups but it's the database query and loops that I'm having trouble with actually getting data into the above format. Bit too much for my brain to handle :(
I'm doing it in this format because I want there to be complete control over the groups and the depth of the subgroups.
I don't really have code to give an example because it's more the problem solving I'm after.
** UPDATE **
A bit more specific: I want to list each parent group (so a group that has a 0 set in parent_id) and it's immediate subgroup, then that groups immediate subgroup (if it has any) etc, etc.

If you want to do it like this, you will allways have to fetch whole table to PHP and then perform the search with php.
However, there is one similar method how you can managet such structure, it is very vell described here:
http://www.sitepoint.com/hierarchical-data-database-2/

Related

Recursive tree in laravel with totals

I'm thoroughly having an issue coming up with a solution to create a recursive hierarchy in mysql while summing the results as I go. Here's the quick structure to keep it simple.
----------------------
id | name | parent_id
----------------------
1 | A | 0
2 | B | 1
3 | C | 1
4 | D | 2
5 | E | 2
6 | F | 3
7 | G | 3
I can recursively create this menu successfully as a php loop or in mysql:
A
-B
--D
--E
-C
--F
--G
However, I these IDs reference another table (contacts) and these are types of contacts. The issue is that only the leafs are assigned to the contacts, but I need to rollup the totals to each level. So I can get to:
A=0
-B=0
--D=100
--E=100
-C=0
--F=200
--G=200
But what I need is to roll up each subsection and sum that to the parent (without a lot of queries) In reality, this tree is several hundred elements in length. This is just a simplified version, but I can't figure out how to walk back up and end up with:
A=600
-B=200
--D=100
--E=100
-C=400
--F=200
--G=200
I'd be happy with a MySQL or PHP implementation. Really just anything to get me headed in the right direction would be much appreciated.
If the number of elements in tree is small enough, you can use ranged IDs.
For example top most parent you can say id will be between 100000 - 199999, first child of this node can be between 100000-109999, second child between 110000-119999 etc. So you know for each node its children ids will be in certain range.
When you want count for a particular node, you just check if id is in that range. I hope this helps.

Group coordinates by proximity to each other

I'm building a REST API so the answer can't include google maps or javascript stuff.
In our app, we have a table containing posts that looks like that :
ID | latitude | longitude | other_sutff
1 | 50.4371243 | 5.9681102 | ...
2 | 50.3305477 | 6.9420498 | ...
3 | -33.4510148 | 149.5519662 | ...
We have a view with a map that shows all the posts around the world.
Hopefully, we will have a lot of posts and it will be ridiculous to show thousands and thousands of markers in the map. So we want to group them by proximity so we can have something like 2-3 markers by continent.
To be clear, we need this :
Image from https://github.com/googlemaps/js-marker-clusterer
I've done some research and found that k-means seems to be part of the solution.
As I am really really bad at Math, I tried a couple of php libraries like this one : https://github.com/bdelespierre/php-kmeans that seems to do a decent job.
However, there is a drawback : I have to parse all the table each time the map is loaded. Performance-wise, it's awful.
So I would like to know if someone already got through this problematic or if there is a better solution.
I kept searching and I've found an alternative to KMeans : GEOHASH
Wikipedia will explain better than me what it is : Wiki geohash
But to summarize, The world map is divided in a grid of 32 cells and to each one is given an alpha-numeric character.
Each cell is also divided into 32 cells and so on for 12 levels.
So if I do a GROUP BY on the first letter of hash I will get my clusters for the lowest zoom level, if I want more precision, I just need to group by the first N letters of my hash.
So, what I've done is only added one field to my table and generate the hash corresponding to my coordinates:
ID | latitude | longitude | geohash | other_sutff
1 | 50.4371243 | 5.9681102 | csyqm73ymkh2 | ...
2 | 50.3305477 | 6.9420498 | p24k1mmh98eu | ...
3 | -33.4510148 | 149.5519662 | 8x2s9674nd57 | ...
Now, if I want to get my clusters, I just have to do a simple query :
SELECT count(*) as nb_markers FROM mtable GROUP BY SUBSTRING(geohash,1,2);
In the substring, 2 is level of precision and must be between 1 and 12
PS : Lib I used to generate my hash

MySql db structure to store a list of items in sequence

I need to store and retrieve items of a course plan in sequence. I also need to be able to add or remove items at any point.
The data looks like this:
-- chapter 1
--- section 1
----- lesson a
----- lesson b
----- drill b
...
I need to be able to identify the sequence so that when the student completes lesson a, I know that he needs to move to lesson b. I also need to be able to insert items in the sequence, like say drill a, and of course now the student goes from lesson a to drill a instead of going to lesson b.
I understand relational databases are not intended for sequences. Originally, I thought about using a simple autoincrement column and use that to handle the sequence, but the insert requirement makes it unworkable.
I have seen this question and the first answer is interesting:
items table
item_id | item
1 | section 1
2 | lesson a
3 | lesson b
4 | drill a
sequence table
item_id | sequence
1 | 1
2 | 2
3 | 4
4 | 3
That way, I would keep adding items in the items table with whatever id and work out the sequence in the sequence table. The only problem with that system is that I need to change the sequence numbers for all items in the sequence table after an insertion. For instance, if I want to insert quiz a before drill a I need to update the sequence numbers.
Not a huge deal but the solutions seems a little overcomplicated. Is there an easier, smarter way to handle this?
Just relate records to the parent and use a sequence flag. You will still need to update all the records when you insert in the middle but I can't really think of a simple way around that without leaving yourself space to begin with.
items table:
id | name | parent_id | sequence
--------------------------------------
1 | chapter 1 | null | 1
2 | section 1 | 1 | 2
3 | lesson a | 2 | 3
4 | lesson b | 2 | 5
5 | drill a | 2 | 4
When you need to insert a record in the middle a query like this will work:
UPDATE items SET sequence=sequence+1 WHERE sequence > 3;
insert into items (name, parent_id, sequence) values('quiz a', 2, 4);
To select the data in order your query will look like:
select * from items order by sequence;

Tags Feature - Storing relationships between two tables (3NF)

I'm working on a tags feature and this is my database structure so far:
`tags` Table: `content` Table:
id | tagname id | title | tags
------------ -----------------------
1 | fire 1 | Charizard | 1 3
2 | water 2 | Blastoise | 2
3 | flying 3 | Charmander | 1
What is the best method for using these two tables and outputting the proper tags for each item of content?
If I was to call for my tags in this manner:
$query = mysql_query("SELECT * FROM `content`");
while ($tableData = mysql_fetch_array($query)) {
echo $tableData["tags"];
}
It would obviously output the raw text content 1 3 and 2 and 1 respectively.
I want it to display fire flying and water and fire respectively.
What should I do to grab those tagnames using the information given from the tags column in the content table?
Yes, you should study normalization and change your table to 3NF. First of all this will save you some huge headaches down the line, and second, normalization is really cool. Here's a very fast summary of what you'd be doing:
Having all your tags in one field is bad practice. It means that everything that will ever query your database has to know exactly how you're storing them and how to pull them apart. It also means that you won't be able to use pure SQL to ask questions like "how many pokemon have a fire attribute" or "which tag is the most popular" because SQL doesn't know what your space-delimited list of tags means. Instead, you'd split content into two tables: monsters and attributes. Monsters looks like this:
id | name
---|---------
1 |Charizard
2 |Blastoise
...
And attributes looks like this:
monsterid | tagid
----------|----------
1 | 1
1 | 3
2 | 2
...
To get a monster's attributes, you find its name in the monsters table, get its ID, and use its ID to find its attributes from the attributes table. This is trivial using JOIN and it gives you a great deal of power to retrieve your information in interesting ways.
To answer your original question, you'd do something like this:
SELECT monsters.id as monsterid, monsters.name, tags.tagname from monsters
INNER JOIN attributes ON (monsters.id = attributes.monsterid)
INNER JOIN tags ON (tags.id = attributes.tagid)
ORDER BY monsters.id
And you'd get a results table that looks like this:
monsterid | name | tagname
----------|-----------|--------
1 | charizard | fire
1 | charizard | flying
2 | blastoise | water
....
Then you could iterate over the rows that were returned and do something like:
$monsters = array();
while ($tableData = mysql_fetch_array($query)) {
$monstername = $tabledata['name'];
if(!isset($monsters[$monstername])) { $monsters[$monstername] = array(); }
$monsters[$monstername][] = $tabledata['tagname'];
}
And then finally you'll have what you wanted, an array where each element is identified by a monster's name, and the value is an array with all that monster's tags.
... Seriously, I know this sounds hellishly complicated compared to what you were originally asking, but I believe it's the simplest way to do it that's not likely to blow up in your face. Hopefully someone will correct me if I'm badly wrong.
warning - the code here was written off the top of my head. There may be some errors in it, but it should show you how to implement the solution.
Here, as #Interrobang said, the best way is to implement 3NF
So, you need to create a new table which stores relationships between tags and contents
content_tags(id,content_id,tag_id).
where content_id, tag_id are the primary key of the resp tab.
Following 3NF will let you for easy delete and update.
Eg: Later, there will be as need that a tag 3 to be deleted from content 1
If u have applied 3NF, you can easily find the specific record and delete it
whereas
if it was stored space or comma separated then you need to fetch using string func then not to forget the previous value(1,3) remove 3 again store the string 3.
3NF will be useful for tag search too.
eg:
For content 1 the reationship would be stored as foll:
id | content_id | tag_id
---------------------------------
1 | 1 | 1
2 | 1 | 3
Later,
select content.id,content,tags
from content inner join on content_tags on (content.id = content_tags.content_id)
inner join tags on (content_tags.tag_id = tags.id)

MySQL hierarchical storage: searching through all parent/grandparent/etc. nodes given a child node id?

I'm storing categories using a hierarchical model like so:
CATEGORIES
id | parent_id | name
---------------------
1 | 0 | Cars
2 | 0 | Planes
3 | 1 | Hatchbacks
4 | 1 | Convertibles
5 | 2 | Jets
6 | 3 | Peugeot
7 | 3 | BMW
8 | 6 | 206
9 | 6 | 306
I then store actual data with one of these category ids like so:
CARS
vehicle_id | category_id | name
-------------------------------
1 | 8 | Really fast silver Peugeot 206
2 | 9 | Really fast silver Peugeot 306
3 | 5 | Really fast Boeing 747
4 | 3 | Another Peugeot but only in Hatchbacks category
When searching for any of this data, I would like to find all child / grandchild / great grandchild etc. etc. nodes. So if someone wants to see all "Cars", they see everything with a parent_id of "Hatchbacks", and so everything with a parent_id of "Peugeot", and so on, to an arbitrary level.
So if I list a "really fast Peugeot 206" with a category_id of either 1, 3, 6, or 8, my query should be able to "travel up" the tree and find any higher categories which are parents/grandparents of that child category. E.g. a user searching for Peugeots in category "8" should find any Peugeots listed with categories 6, 3, or 1 - all of which category 8's descendants.
E.g. using the above data, searching for "Peugeot" in category 3 should actually find vehicles 1, 2 and 4, because vehicles 1 and 2 have a category ancestor trail which leads back up to category 3. See?
Sorry if I haven't explained this well. It's difficult! Thank you, though.
Note: I have read the MySQL dev article on hierarchies.
Normalized models are great, but not when you actually have to query them.
Just store the "path" to your category in category table. Like this: path = /1/3/4 and when query you database like "select .... where path like '/1/3/%'" It will be much more simple and fast than multiple hierarchical queries...
This article can help you http://www.phpro.org/tutorials/Managing-Hierarchical-Data-with-PHP-and-MySQL.html
I like the explanation provided by SitePoint. It gives you code and explains the theory behind it.
http://blogs.sitepoint.com/hierarchical-data-database/
Note: this method is better for reads than for writes. If you're constantly writing to the tree, I'd use a different algorithm. This method is optimized for reads (lookups).
You've represented your data as an Adjacency List model, whose querying in MySQL is best done using session variables. Now, this is not the only way you can represent a hierarchy in a relational database. For your particular problem, I would probably use a materialized path approach instead, where you do away with the actual categories table and instead have a column on your cars table that looks like Cars/Hatchbacks/Peugeot on a per record basis and use LIKE queries. Unfortunately that would be slow as the number of records grew. Now, if you know the maximum depth of your hierarchy (e.g. four levels) you could break that out into separate columns instead, which you allow you to take advantage of indexing.

Categories