How to implement tree like structure in mysql and laravel - php

A question struck in my mind for 2 days and wondering whether it is possible to implement this type of tree structure in laravel and MySQL.
(First, take a look at the image attached. Thanks)
Suppose our platform uses refer system, and initially, a user 'A' join. Now, this user 'A' further refers 3 persons 'B','C','D'. Now, the total refer on A is 3 (because it refers 3 persons).
Now, let B further refers 'E','F' and 'C' further refers 'G','H', 'I' and 'D' refers 0. So, now refer of each person is "D = 0", "C = 3", "B = 2". and these refers will also add up on "A". So, it has "A = 8".
Now, 'G' refers 'J', so 'G' gets +1 and 'C' also gets +1 and 'C' is referred by 'A', so 'A' also gets +1. Now, total refer to each person is :
"j = 0","G=1","H=0","I=0", "D=0","E=0","f=0","B=2","C=4 (beacuse G refers J also)","A=9(beacuase 9 childers are refered by him)"
The chain continues until A gets total refer of 40.
In simple, if a person refers another person then it will get +1 and it's parent whom he gets refer also get +1 and so on until parent reaches 40, the chain continues.
I know, this is One-Many relationship between a user and refer and we can use a pivot table, but, How can we implement this type of logic. Give me some hints. Thanks.

I have written out something that should hopefully help you with this, using a while loop.
public function totalReferredBy(User $user)
{
// Initialise the queue to contain only the provided user
$queue = collect([$user]);
// This collection will eventually contain all of the "child"/referred users
$results = collect();
while ($queue->isNotEmpty() > 0) {
// Run a where in query to select all the referred users of the users in the queue.
$referredUsers = User::whereIn('referred_by', $queue->pluck('id'))->get();
// Merge the referredUsers we have found in the database with the results collection, so we can later count.
$results = $results->merge($referredUsers);
// Make the referredUsers we have just found in the database, the new queue. If the query did not return any
// referred users, the queue count would be 0 and the loop will exit.
$queue = $referredUsers;
}
// Now we should have all of the given user's "children" and "children of children" in the $results collection.
// We just need to return the count of that collection to get the total number of users that have been referred.
return $results->count();
}
You can use it like this:
$user = User::find(1);
$totalReferred = $this->totalReferredBy($user);
Then if your application does something when the user reaches 40 or more referred, you can just do:
if ($this->totalReferredBy($user) > 40) {
// Do something
}
This assumes that you have a referred_by column on the users table.

Related

Looping through the values of an array intersect

I have different communities in my database, such as: community_newsfeed, community_marketplace, community_games_requested, community_games_offered, events and custom_posts.
Each of those communities stores a post_id, as well as the user_id.
Then I have a community_users table that stores user_id and community_id.
I want to create a mechanism which I, as the authenticated user, can view all the posts of a user, as long as that user and I belong to the same community.
And on that regard, I must view only the posts for the communities which we both belong to.
So I do the following:
Get all the community id's of the communities which I(auth user) belong to:
$community_user_member = DB::table('community_users')->whereUserId($user->id)->pluck('community_id')->toArray();
Then I do the same for the user which I want to see the posts:
$requested_user = DB::table('community_users')->whereUserId($user_id)->pluck('community_id')->toArray();
Fine. Now I use array interect to give me all the community_id that we share only:
$members = array_intersect($community_user_member, $requested_user);
Now, this is where I get stuck.
How can I get the communities which the id match the values I get in the $members value(not the key) and the user id matches $user_id( this is passed in the url )?
if(!empty($members)){
$newsfeed = CommunityNewsfeed::whereIn('newsfeed_community_id', $members)->whereUserId($user_id)->get();
}
I am sure that this user has 4 posts from community_newsfeed table, but I only see two posts, and also I dont see the correct posts.
Live example:
User A belongs to community_newsfeed.
User B belongs to community_newsfeed and community_marketplace.
User A must see the community_newsfeed data where user_id is the id of User B.
Updated
Another option I tried was to use the in array function:
$common = [];
foreach ($community_user_member as $key => $member) {
if (in_array($member, $requested_user)) {
$common[] = $member;
}
}
print_r($common);
The end result should be something like this:
$common = [ '4','5','10','20' ];
Because then I would be able to loop through $common array and do whatever I want.
But I get the following error when I refresh the page:
SyntaxError: JSON.parse: unexpected character at line 1 column 1 of the JSON data

Ranking based on users placement instead of score

I have a issue that I cannot wrap my head around.
I am using the Laravel Framework.
I am trying to make a ranking table based on placement (Meaning the user does not have any SCORE, they just have placements)
How I want it to work is the following way:
User A = Placement: 1
User B = Placement: 10
User B wins over User A, then User B gets placed as number 1 and User A gets placed as number 2, and then I want it to update all the other users accordingly.
I can't seem to find a reliable way of doing this.
I don't think this is a Laravel challenge but an SQL one. And it may be simple to solve: basically, you will ask for the actual position of the defeated person, if the position is greater than the winner, you do nothing, otherwise you will assign the position of the loser to the new winner and update the rest of the table with a +1 in the position column.
In code it would be something like this:
$winner_player = User::where('id', userA->id)->first();
$loser_player = User::where('id', userB->id)->first();
if($winner_player->position < $loser_player->position) {
//Update the rest of the users.
//We add 2 because we need space for the new winner and for
//the loser that is still above of the rest of the players.
DB::table('users')
->where('position', '>', $loser_player->position)
->update(DB::raw('position+2'));
//Set the winner with the actual position of the loser.
$winner_player->position = $loser_player->position;
$winner_player->save();
//Set the looser with the new position (+1 of his actual).
$loser_player->position = $loser_player->position + 1;
$loser_player->save();
}
UPDATED LOGIC
As Classified pointed out, it moves the rows around but doesn't do it correctly, so I'm updating the logic to make it work as it is supposed to, and it will be a little simpler too.
$winner_player = User::where('id', userA->id)->first();
$loser_player = User::where('id', userB->id)->first();
if($winner_player->position < $loser_player->position) {
//Set the winner with the actual position of the loser.
$winner_player->position = $loser_player->position;
//Update the users between the swap. There is no need to update
//the whole table, we only update the records between the swap.
DB::table('users')
->where([['position', '<', $winner_player->position],
['position', '>=', $loser_player->position]])
->update(DB::raw('position+1'));
//Save the value of the winner AFTER updating the positions
//between winner and loser.
$winner_player->save();
}

Symfony: Delete all the records in a table who have the id in the string in table's column

[Please note that I do not have code for this problem, I need code, I have tried to explain it the best way, and if you can help, it will be great]
so here is the deal, I have a field in the table named "order" for every user. The main job of the user is to bring other users to the system and when they bring a new user their id is sticked to (concated with) the referring user's id and stored in his "order field"
for eg.
user 'a' has id 31. 'a' brings in 'b' whose is assigned 32, now b's therefore b has the value: '31-32' stored in his 'order' field. simililarly if b brings in 'c' whose id is 35, the order for c will be: '31-32-35' and it goes so on.
Now when I delete 'a' I want ALL the users who have his id in their order fields, that is if i decide to delete 'a' in this above field, all the users should be deleted from he system too!
I want to do this via symfony controller, and I think that can be done by findAll() function in symfony but I have no clue how to use it.
Please help, I am really stuck!
I did by using a parent field in each table for the user:
and here is the code if such thing arises for someone:
<?php
$repo = $em->getRepository('SystemBundle:Distributor');
$temp = $slug;
$pd = $repo->findAll();
for ($i = 1; $i <= count($pd); $i++) {
$u = $repo->findOneBy(['parent' => $temp]);
if ($u) {
$temp = $u->getId();
$em->remove($u);
$em->flush();
} else {
break;
}
}
$dis = $repo->findOneBy(["id" => $slug]);
$em->remove($dis);
$em->flush();
Using for loop it was possible to do this

MySQLi append more/deeper results

I'm having the hardest time figuring this out and it's probably because I'm not using the correct terms. If someone could point me in the right direction, that would be amazing. I'm going to use a hypothetical situation to make things easier:
I have a database with two tables:
tableA contains records for a house sale (house ID,address, price, current owner ID, etc)
tableB contains records for realtors who have shown a house (house ID, realtor ID, time and date, notes, etc).
I would like to have a query that can search a current owner ID and pull down all of their houses with information on everyone who showed the house. What I would like to retrieve is a JSON array that has the info from each tableB record appended/attached/added to a single record from tableA.
For example, if I search the the houses that are owned by ownerX (who owns two houses), I would like it to return two main items with sub items for each related entry in tableB. In the example below, ownerX has two houses. The first house on 1234 Fake St had 2 different realtors make a total of 3 visits. The second house on 555 Nowhere St had 1 realtor visit twice.
Here's how I'd like to retrieve the info:
tableA - Result 1 (House at address 1234 Fake St)
tableB - Result 1 (Realtor ID 1234, etc)
tableB - Result 2 (Realtor ID 1234, etc)
tableB - Result 3 (Realtor ID 2222, etc)
tableA - Result 2 (House at address 555 Nowhere St)
tableB - Result 1 (Realtor ID 1111, etc)
tableB - Result 2 (Realtor ID 1111, etc)
Instead, what I'm getting is this:
tableA - Result 1 (House at address 1234 Fake St),tableB(Realtor ID 1234, etc)
tableA - Result 2 (House at address 1234 Fake St),tableB(Realtor ID 1234, etc)
tableA - Result 3 (House at address 1234 Fake St),tableB(Realtor ID 2222, etc)
tableA - Result 4 (House at address 555 Nowhere St),tableB(Realtor ID 2222, etc)
tableA - Result 5 (House at address 555 Nowhere St),tableB(Realtor ID 2222, etc)
I don't don't want to retrieve tableA information each time. I only need that once, then each sub-result from tableB. This is important because I'm returning the data to an app that creates a new list. I'm currently using mysqli_multi_query
$sql = "SELECT * FROM tableA WHERE ownerID = "ownerX";";
$sql. = "SELECT tableB.*, tableA.houseID FROM tableB,tableA WHERE tableB.houseID = tableA.houseID;";
Again, the actual content is just a hypothetical. I'm looking for more of a, "You're an idiot, you should be using _____" and not, "You misspelled realtor and that's probably causing the problem.".
Also, please note that I'm not asking for the results to be formatted with the dashes and parentheses as they are above. I'm just simply writing it that way so it's easier to understand. I'm looking for a way to have sub-objects in a JSON array.
Any help pointing me in the correct direction would be much appreciated! Thanks to whoever takes the time to take a stab at this!
Tony
Additional information:
Here's the code I'm using to run the query:
$sql = "SELECT * FROM clocks WHERE user_key='".$userkey."';";
$sql .= "SELECT * FROM milestones WHERE (SELECT clock_key FROM clocks WHERE user_key='".$userkey."') = milestones.clock_key";
if (mysqli_multi_query($con,$sql))
{
do
{
if ($result=mysqli_store_result($con)) {
while ($row=mysqli_fetch_row($result))
{
$myArray[] = $row;
}
echo json_encode($myArray);
mysqli_free_result($result);
}
}
while(mysqli_more_results($con) && mysqli_next_result($con));
}
UPDATE WITH ANSWER:
Thanks for #vmachan's post below, I ended up getting all of my data at once, then ran through some loops to adjust the array. I'm going to use the house/relator example from above.
I used his code to get my results ($house_id is a variable input id):
$sql = "SELECT * FROM tableA INNER JOIN tableB ON tableA.houseID = tableB.houseID WHERE tableA.houseID='".$house_id."';";
I was given an array with 5 items because tableB had 5 entries. Since there are only 2 house entries in tableA, it looked like this:
["houseID"=>"1","price"=>"50000", "owner" => "Mike G", "state"=>"CA", "realtor" => "Jane D", "visitDay"=>"Tuesday", "notes" => "They liked the house"],
["houseID"=>"1","price"=>"50000", "owner" => "Mike G", "state"=>"CA", "realtor" => "Jane D", "visitDay"=>"Wednesday", "notes" => "They loved the house"],
["houseID"=>"1","price"=>"50000", "owner" => "Mike G", "state"=>"CA", "realtor" => "Stephanie W", "visitDay"=>"Friday", "notes" => "They didn't like the house"],
["houseID"=>"2","price"=>"65000", "owner" => "Michelle K", "state"=>"AL", "realtor" => "Mark S", "visitDay"=>"Tuesday", "notes" => "They made an offer"],
["houseID"=>"2","price"=>"65000", "owner" => "Michelle K", "state"=>"AL", "realtor" => "Jim L", "visitDay"=>"Monday", "notes" => "They stole stuff"]
The first 3 elements are from tableA and don't change. So, I used a loop to basically check the houseID, if it's a new house, create a new house array item, otherwise, add the details from tableB to the current house element:
<?php
//$house is an array will hold all of our indiviaul houses and their infomation.
$houseArray = array();
//Start the foreach loop
foreach($items as $item){
//$item["houseID"] is the houseID from our database that we got from the above code.
$houseID =$item["houseID"];
//$currentID is a varible that is set after the first iteration.
//This checks to see if we're still working with the same house, or a new house.
if($currentID!=$houseID){
//Create an array to hold all of the relator visit information arrays.
//This is created within the loop as it will erased if a new houseID is found in the array.
$relatorVisitArray = array();
//This is a secondary loop that checks the same array. This time, we are only working with the new houseID that from the condition above.
foreach($items as $rv){
//This cheecks to see if there is a match between the current houseID that we're working with and the other houseIDs in the array. Since we're going through the same array that we're already iterating, it will find itself (which is good).
if($houseID==$rv["houseID"]){
//Once is gets a match, it will create a temporary array to hold the "Relator Visit" information. The array is created within the loop as it needs to be cleared through each iteration.
$tempRealitorVisit = array(
'name' => $rv["name"],
'day' => $rv["day"],
'houseID' => $rv["houseID"],
'notes' => $rv["notes"]
);
//At the end of each iteation, we add the temporary to the main $relatorVisitArray.
$relatorVisitArray[] = $tempRealitorVisit;
}
}
//At this point, the subloop has ended and we're created an array ($relatorVisitArray) which contains all of the $tempRealitorVisit arrays.
//Remember, were are still within the first loop and have determined that this is a new house.
//Now we'll create a new house array based on the current houseID in this iteration.
//This array is created within the loop because we want it to cear at the next iteation when it's determined that it's a new house.
$house = array(
'houseID' => $item["houseID"],
'owner' => $item["owner"],
'price' => $item["price"],
'location' => $item["location"],
'relatorVisits' =>
//Here, we simply add the $relatorVisitArray to a key called, "relatorVisits" (ie an array within an array).
$relatorVisitArray
);
//We then add the $house to the $houseArray.
$houseArray[] = $house;
//Finally, we set $currentID to $item["houseID"]. At the next iteration, it will check this id against the next house ID. If they are the same, this entire code will skip until a new houseID appears from your database.
$currentID= $item["houseID"];
}
}
//This prints all of the information so it's easy to read.
echo '<pre>';
print_r($houseArray);
echo '</pre>';
}
?>
In the end, I'm left with one array that contains two sub arrays. The first sub array (House 1) contains 3 sub arrays (3 visits to that house). The second sub array (House 2) contains 2 sub arrays (2 visits to that house).
I hope this helps anyone that had the same issue as me. If anyone knows of a cleaner way to do this, please post it here! Thanks for the guidance!
Tony
I think you can combine the SQL statements as shown below to JOIN the clocks and milestones tables on the clock_key for a user-provided value i.e. $userkey. Then in your code you could loop thru the results and then check for consecutive house_ids.
$sql = "SELECT * FROM clocks INNER JOIN milestones ON clocks.clock_key = milestones.clock_key WHERE clocks.user_key='".$userkey."';";
You can then use the code similar to the one in ths SO posting. You would need to change it so that inside the loop you check if the previous 'house_id' is the same as the current one and if not, you would start a new parent array other wise keep adding to the existing array. At the end of the loop you could then call the encode to get your JSON format.
Hope this helps.
I have not got time to actually write (and test!) proper code but I would suggest that you collect your data into two php associative arrays: $houses with the owner as the key and $visits with the houseID as a key. Assuming that an owner can have more than one property on the market and knowing that a realtor can pay more than one visit to each property the entries in these two arrays will then themselves be "array of array"s.
sample:
$houses={'ownerx':{'houseID_1':['address_1','price_1'],
'houseID_2':['address_2','price_2']}},
'ownery':{'houseID_3':['address_3','price_3'],
'houseID_4':['address_4','price_4']}}
};
$visits={'houseID_1':['realtorID_1','realtorID_2', ...],
'houseID_2':['realtorID_3']
};
// I used JSON notation for simplicity ...
Doing this would require you to set up a proper structure once but it will save you from querying the same data again and again. The retrieval of the data from the associative arrays should also work very efficiently.

Complex Zend Query from same user table

I have a rather unique set of conditions and orders in which I need to retrieve data from a "sellers" table for an application I'm building in Zend framework.
The client is basically requesting an application where the directory page lists sellers in a very particular order, which is:
Sellers who have been approved in the last 7 days (then order by #4 below)
Then, selllers who have paid for upgraded features on the site, and are more the 7 days old (then order by #4 below)
Then, Sellers who are more than 7 days old and are more than 7 days old (then order by #4 below)
For all of the above, secondary order by would be their launch date, then alpha by business name
I'm trying to figure out the most effective way to write an action helper that will return the data in the correct sequence above, knowing that some of my views only need 1,2 (and 4), whereas other views within the application will need all 4.
Right now, I've been writing two or three separate queries, and passing them to 2 or 3 partialloop's inside the view, but I strive for properly written code, and would like to either combine my 3 queries into one object I can pass to one partial loop, or.... write one query. How can this be done?
Here's my helper at the moment:
class Plugin_Controller_Action_Helper_ListSellers extends Zend_Controller_Action_Helper_Abstract
{
//put your code here
public function direct($regulars = false, $filter = false)
{
$dateMod = $this->dateMod = new DateTime();
$dateMod->modify('-7 days');
$formattedDate = $dateMod->format('Y-m-d H:i:s');
// get sellers initialized in last 7 days
$sellerTable = new Application_Model_DbTable_Seller();
// get sellers initialized in last 7 days
$select = $sellerTable->select()->setIntegrityCheck(false);
$select->from(array('b' => 'seller'),array('sellerID', 'businessName','sellerPicture'));
// select firstName, lastName, picture from user table, and businessName and sellerID from seller table. All records from seller table
$select->join(array('u' => 'user'), 's.userID = u.userID', array('firstName', 'lastName'));
$select->order('s.launchDate DESC','s.businessName ASC');
$select->where('s.active = 1 AND s.contentApproval = 1 AND s.paymentApproval = 1');
$select->where('s.launchDate > ?', $formattedDate);
if($filter){ $select->where('s.categoryID = ?', $filter);}
$newSellers = $sellerTable->fetchAll($select);
$query = $sellerTable->select()->setIntegrityCheck(false);
$query->from(array('b' => 'seller'),array('sellerID', 'businessName','sellerPicture'));
// select firstName, lastName, picture from user table, and businessName and sellerID from seller table. All records from seller table
$query->join(array('u' => 'user'), 's.userID = u.userID', array('firstName', 'lastName'));
$query->order('s.launchDate DESC','s.businessName ASC');
$query->where('s.active = 1 AND s.contentApproval = 1 AND s.paymentApproval = 1 AND s.featured = 1');
$query->where('s.launchDate < ?', $formattedDate);
if($filter){ $select->where('s.categoryID = ?', $filter);}
$featuredSellers = $sellerTable->fetchAll($query);
if($regulars){
$where = $sellerTable->select()->setIntegrityCheck(false);
$where->from(array('b' => 'seller'),array('sellerID', 'businessName','sellerPicture'));
// select firstName, lastName, picture from user table, and businessName and sellerID from seller table. All records from seller table
$where->join(array('u' => 'user'), 's.userID = u.userID', array('firstName', 'lastName'));
$where->order('s.launchDate DESC','s.businessName ASC');
$where->where('s.active = 1 AND s.contentApproval = 1 AND s.paymentApproval = 1 AND s.featured IS NULL');
$where->where('s.launchDate < ?', $formattedDate);
$regularSellers = $sellerTable->fetchAll($where);
}
}
}
I don't see any limits being applied to your queries. So does that mean you really want to select all matching records? For scalability reasons I'd guess that the answer should be no, there will be limits applied. In this case, you may just have to do 3 different queries.
But if there are no limits to be applied, then you could do a single simple query that selects all sellers, unfiltered and unsorted, and do your sorting and filtering in view helpers or just in your views.
Regardless, I recommend not putting database queries inside your controller layer, assuming you want to use the Model-View-Controller pattern which Zend is built for. Controllers should be thin. Your models should handle all database queries and just spit out the results into your controllers. I use the Data Mapper pattern extensively. Something like:
$mapper = new Application_Model_SellerMapper();
$newSellers = $mapper->fetchNewSellers();
$featuredSellers = $mapper->fetchFeaturedSellers();
$regularSellers = $mapper->fetchRegularSellers();
Each of your fetchX() methods would return an array of Application_Model_Seller instances, rather than Zend_Db_Table_Row instances.
This way you maintain Separation of Concerns and Single Responsibility Principle better, for more maintainable code. Even if you're the only developer on the project over the long-term, 6 months from now you won't remember what you wrote and why. And if someone else comes on the project, clarity becomes really important.

Categories