Check large chunk of values for uniqueness in database - php

I need to create promo codes which should be short in length (~ 6 characters). The promo codes have to be unique, so I need to check their uniqueness in database as well. They need to be generated in batches of thousands, so a check in db with every coupon generation is not feasible. I have created a method which first generates the required number of coupons and then check for duplicates using where in(). Having duplicate count of greater than zero, makes it generate the count again.
public function generateCoupons($count, $length = 6)
{
$coupons = [];
while(count($coupons) < $count) {
do {
$coupon = strtoupper(str_random($length));
} while (in_array($coupon, $coupons));
$coupons[] = $coupon;
}
$existing = Offer::whereIn('coupon', $coupons)->count();
if ($existing > 0)
$coupons += $this->generateCoupons($existing, $length);
return (count($coupons) == 1) ? $coupons[0] : $coupons;
}
Need suggestions how to improve upon this? Or if I can have some other way to achieve the same.

Make sure the promo code is indexed in your DB. This will speed up the search for existing promo codes.
Otherwise, your method is good! you want to check as many codes as possible at once (which you do with the whereIn/count) and only re-generate the codes that were not unique.

Build a table new_codes with 1000 candidates. PRIMARY KEY(code).
DELETE new_codes
FROM new_codes
LEFT JOIN existing_codes ON existing_codes.code = new_codes.code
WHERE existing_codes.code IS NOT NULL;
That (if I did it right) will very quickly delete the dups. Now you will have not-quite-1000 'good' codes.

Related

How to set sequence data with existing random data?

I have a table entity named it as uniqueId where the entry generate randomly.Such as
$customer->uniqueId = $request->Input(['uniqueId']) ?: mt_rand(1000, 9999);
means if there is existing uniqueId it will store the existing one otherwise it will be set to the random number. Now instead of setting the random number i want to set it as sequentially . means from 1, 2, 3 like that.. as i can't delete the existing uniqueId which has already created how do I create new entry sequentially with the existing one?
If you simply set column to auto increment you will achieve this automatically you dont even need to call it.
In laravel you can achieve this in your migrations by
$table->increments('uniqueId');
OR
You can achieve this by
lets assume you have a customer Model
// find the last entry in you table
$oldCustomer = Customer::orderBy('uniqueId','DESC')->first();
$customer->uniqueId = ++($olderCustomer->uniqueId);
I hope this helps
**EDIT **
$customers = Customer::all();
$index = 1;
#foreach($customers as $customer)
{
$customer->uniqueID = $index++;
$customer->update();
}

Laravel - How to pass different values for pivot data table in sync() function

My form sends a $request->assets that I am able to sync using the following:
$user->assets()->sync($request->assets === null ? [] : $syncData);
The complexity (not a problem) arises when I try to retrieve values from a serial number array that is sent back as $request->serialnumber
I have set up the serial numbers in such a way that the serial number array index corresponds to my id column in the assets table. E.g. if Mobile has a value of 1 in the database, it is located in $request->serialnumber[1], and for something that would have an id of 2, would be placed in $request->serialnumber[2] and so on.
I have done the following so far, in order to insert the correct serial numbers for the correct asset() relationship:
$serialData = [];
$pivotData = [];
$syncData = [];
//for every assigned asset, build an array of their serial numbers from the serial number array...
if(isset($request->assets))
{
for($i = 0; $i<count($request->assets);$i++)
$serialData[$i] = $request->serialnumber[$i];
}
//if some serials were set, use them for pivot data
if(count($serialData)>0)
{
$filledArray = array_fill_keys($serialData,"serialnumber");
$pivotData = array_flip($filledArray);
}
$syncData = array_combine($request->assets, $pivotData);
I know its a long drawn out way, so I'm wondering if there's an easier way to do this?

Laravel Eloquent: how to filter multiple and/or criteria single table

I am making a real estate related app and I've been having a hard time figuring out how to set up the query so that it would return "Only Apartments or Duplexes within selected areas" I'd like to user to be able to find multiple types of property in multiple selected quadrants of the city.
I have a database with a column "type" which is either "Apartment", "House", "Duplex", "Mobile"
In another column I have quadrant_main with values: "NW", "SW", "NE", "SE".
My code works when there is only 1 quadrant selected, but when I select multiple quadrants, I seem to get results which includes ALL the property types from the second or third or 4th quadrant, instead of only "Apartment" and "Duplex" or whatever types the user selects... Any help will be appreciated! thx in advance.
My controller function looks like this:
public function quadrants()
{
$input = \Request::all();
$currentPage = null;
$column = "price";
$order = "desc";
//
// Looks like the input is like 0 => { key: value } ...
// (an Array of key/value pairs)
$q = Listing::where('status','=','Active')->where(function($query) {
$input = \Request::all();
$currentPage = null;
$typeCount = 0;
$quadrantCount = 0;
foreach( $input as $index => $object ) {
$tempObj = json_decode($object);
$key = key((array)$tempObj);
$val = current((array)$tempObj);
if ( $key == "type" ) {
if ( $typeCount > 0 ) {
$query->orWhere('type', '=', $val );
}
else {
$query->where('type', '=', $val );
$typeCount++;
}
}
if ( $key == "quadrant_main" ) {
if ( $quadrantCount > 0 ) {
$query->orWhere('quadrant_main', '=', $val );
}
else {
$query->where('quadrant_main', '=', $val );
$quadrantCount++;
}
}
// else {
// $query->orWhere($key,$val);
// }
}
if( $currentPage ) {
//Force Current Page to Page of Val
Paginator::currentPageResolver(function() use ($currentPage) {
return $currentPage;
});
}
});
$listings = $q->paginate(10);
return $listings;
Looking at your question, its a bit confusing and not much is given to answer definitely. Probable causes of your troubles may be bad data in database, or maybe corrupted input by user.
Disclaimer: Please note that chances are my answer will not work for you at all.
In that case please provide more information and we will work things
out.
There is one thing that I think you have overlooked and thus you are getting awry results. First let me assume a few things.
I think a sample user input should look like this:
array(
0: '{type: Apartment}',
1: '{type: Duplex}',
2: '{quadrant_main: NW}',
3: '{quadrant_main: SW}',
)
What the user meant was give me any apartment or duplex which belongs in NW or SW region.
So after your loop is over, the final SQL statement should be something like this:
Oh and while we are at SQL topic, you can also log the actual
generated SQL query in laravel so you can actually see what was the
final SQL getting generated. If you can post it here, it would help a
lot. Look here.
select * from listings where status = 'Active' and (type = 'Apartment' or type = 'Duplex' and quadrant_main = 'NW' or quadrant_main = 'SW');
What this query will actually produce is this:
Select any listing which is active and:
1. Type is an apartment, or,
2. Type is a duplex, or,
3. Quadrant is SW, and,
4. Quadrant is NW
So assuming you have a database like this:
id|type|quadrant_main
=====================
1|Apartment|NW
2|Apartment|SW
3|Apartment|NE
4|Apartment|SE
5|Duplex|NW
6|Duplex|SW
7|Duplex|NE
8|Duplex|SE
9|House|NW
10|House|SW
11|House|NE
12|House|SE
You will only receive 1, and 5 in the result set. This result set is obviously wrong, plus it is depended on NW because that was the and condition.
The correct SQL query would be:
select * from listings where status = 'Active' and (type = 'Apartment' or type = 'Duplex') and (quadrant_main = 'NW' or quadrant_main = 'SW');
So structure your L5 app such that it produces this kind of SQL query. Instead of trying to cram everything in one loop, have two loops. One loop should only handle type and another loop should only handle quadrant_main. This way you will have the necessary and condition in the right places.
As a side note:
Never directly use user input. Always sanitize it first.
Its not a best practice to put all your logic in the controller. Use repository pattern. See here.
Multiple where clauses are generally applied via Criteria. Check that out in the above linked repository pattern.
You code logic is very complicated and utterly un-necessary. Instead of sending JSON objects, simply send the state of checkboxes. Don't try to generalize the function by going in loop. Instead handle all checkboxes one by one i.e. is "Apartments" selected, if yes, add that to your clause, if not, don't add.

ORM Mapping two tables with PHP

Current situation
I have two tables in my database, one for posts, and one for ratings. These are linked with a relation in the MySQL so that one post may have 0, 1 or multiple ratings, but one rating can only be applied to one post.
When I fetch a list of posts, I also want to get ratings, but without having to make a separate call to the database for each post in the foreach loop.
To do this I have attempted to use an SQL query to fetch all posts with a LEFT JOIN on ratings so that it will return a result like this:
statusId|statusBody|rating
-----------------------------
1, post1, 0
1, post1, 1
2, post2, 0
3, post3, 1
3, post3, 1
The SQL works fine, and I get the data I ask for.
Ideally what I am trying to achieve now is to turn this table into a collection of objects, with each object storing the post information as well as a value depending on it's total ratings.
After using PDO to return the data result, this is the code I am using to map the data:
Code Logic
The logic of my code goes like this:
Get all statuses joined with ratings table
Create empty output array
Loop through PDO result
{
Create loop specific temp array
Push first row of result into temp array
Remove row from PDO result
Loop through PDO result for objects with matching statusId
{
If row matches statusId, add to temp buffer and remove from PDO result
}
Take first row of buffer and create status object
Loop through objects in temp array to calculate ratings and add onto above status object
Clear temp buffer
Add status object to output array
}
return output array
Actual Code
try
{
$result = $pdo->query($sql);
//if($result == false) return false;
$statuses = $result->fetchAll(PDO::FETCH_CLASS, 'status');
}
catch (PDOException $e)
{
return FALSE;
}
if (!$result) {
return FALSE;
}
//create empty output array to be filled up
$status_output = array();
//loop through all status
foreach($statuses as $s1key => $s1value)
{
//initialise temporary array;
$status_temp_buffer = array();
//create temp array for storing status with same ID in and add first row
array_push($status_temp_buffer, $s1value);
//remove from primary array
unset($statuses[$s1key]);
//loop through array for matching entries
foreach($statuses as $s2key => $s2value)
{
//if statusId matches original, add to array;
if($s2value->statusId == $s1value->statusId)
{
//add status to temp array
array_push($status_temp_buffer, $s2value);
//remove from primary array
unset($statuses[$s2key]);
}
//stop foreach if statusId can no longer be found
break;
}
//create new status object from data;
$statObj = $status_temp_buffer[0];
//loop through temp array to get all ratings
foreach($status_temp_buffer as $sr)
{
//check if status has a rating
if($sr->rating != NULL)
{
//if rating is positive...
if($sr->rating == 1)
{
//add one point to positive ratings
$statObj->totalPositiveRatings++;
}
//regardless add one point to total ratings
$statObj->totalAllRatings++;
}
}
//clear temporary array
$status_temp_buffer = NULL;
//add object to output array
array_push($status_output, $statObj);
}
Problem
The problem I am coming up against with this code is that although the ratings are fine, and it correctly calculates the ratings total for each post, it still shows duplicates where a post has more than one rating.
Any help with this would be greatly appreciated,
Thanks
As i understood it, the goal is to get the total rating of each Post entry. Instead of manually looping over each and every rating, there are two other path you could take:
compute the total in the query:
SELECT SUM(rating) AS total , .. FROM Posts LEFT JOIN .... GROUP BY statusID
You will receive a list of Post entries, each already with total rating calculated. This is a very good solution if you have a lot of writes to to the Ratings table, and much less reads.
the other way is to break the table normalization, but to increase read performance. What you would have to do is to add another column in the Posts table: total_rating. And have an TRIGGER on INSERT in the Ratings table, which changes the Posts.total_rating accordingly.
This way has a benefit of simplifying the request of Posts. At the same time Ratings table can now be use to ensure that total_rating has been calculated correctly, or to recalculate the value, if there are some large changes in the ratings: like banning of user, which results in removing all ratings made by this user.

Need advice in algorithm implementation and creation in PHP based shop

For several days now I am trying to cope with the algorithm implementation at the online shop which I am writing in PHP. I do not know whether the problem is only the implementation, or perhaps bad algorithm design. Hovewer, for me it seems fine. I only haven`t checked its complexity of it, but it's such a problem.
After a long deliberation on the same algorithm, without thinking on implementation I came up with the use of binary search tree (bst) with additional data inserted into list consist of user defined info (later about it). The whole orders list would be displayed, or returned using inorder method.
I write it like that:
If the input object date is greater than current object go right
If the input object date is less than current object go left
If the dates are the same stay at place
If the field is blank check if the product is in stock
If it is put into place and finish
If there is not do nothing and exit
If the field is full
{Check if on the list is this user id
If yes than check order priority
If no do nothing and exit
Check if there is product on stock
If yes replace record and exit
If no do nothing and exit
}
{If there is not user id on the list check if product is on stock
If yes then put element on the end
If no do nothing and exit
}
Maybe it looks a little bad, but I was not able to do indentation.
Data is transferred into algorithm in a loop until the end of orders list. The list is unordered.
This is my implementation:
class BinaryTree {
private $predescor = array(
'd'=>array('data'=>0),
'p'=>null,
'r'=>null,
'l'=>null
);
private $ancestor = array(
'd'=>array('data'=>0),
'p'=>null,
'r'=>null,
'l'=>null
);
private $i = 0;
public function insertIntoTree(&$root,$element)
{
$this->predescor = $root;
$this->predescor;
while($this->predescor)
{
if($element['d']['data']==$this->predescor['d']['data'])
{
$this->inertIntoList($element,$this->predescor['d']);
return true;
}
$this->predescor = $this->predescor;
if($element['d']['data']<$this->predescor['d']['data'])
{
$this->predescor = $this->predescor['l'];
}
else
{
$this->predescor = $this->predescor['r'];
}
}
$element['p'] = $this->predescor;
if(!$this->predescor)
{
$root = $element;
}
else if($element['d']['data']<$this->predescor['d']['data'])
{
$this->predescor['l'] = $element;
}
else
{
$this->predescor['r'] = $element;
}
return true;
}
public function putList(&$list,$root)
{
if($root!=null)
{
$this->putList($list, $root['l']);
$lista[$this->i] = $root;
$this->i++;
$this->putList($list, $root['r']);
}
return;
}
private function insertIntoList($element,&$position)
{
if($position == null)
{
$position = $element;
return true;
}
foreach($position['user'] as &$key)
{
if($key == $element['d']['user'])
{
if($key['priority']<$element['d']['user']['priority'])
{
return false;
}
else if($key['priority']==$element['d']['user']['priority'])
{
return false;
}
else
{
if(Orders::checkOrder($element['d']['user']['order']))
{
$key['order'] = $element['d']['user']['order'];
return true;
}
else
{
return false;
}
}
}
}
//#todo add at the end
return true;
}
}
I would like to advise whether there is a simpler way than using bst consisting of a quite complex arrays, which would also be easier to implement? Because now I can not inplement it in PHP.
Thank you in advance.
I wouldn't start by coding this in php at all.
I'd start by building this into the database. ("Orders" implies a database.) I'd start by clarifying a couple of points. Assuming that one order can have many line items . . .
The number of days since the last order seems to clearly apply to the order,
not to individual products.
The user can have "only one request carried at a time". Request for
what? Doesn't seem to make sense for this to apply either to an
order or to an order's line item.
The order priority seems to clearly apply to the order, not to line
items. But a line-item priority might make more sense. (What products does the customer need first?)
Whether the product is in stock seems to apply to the line items, not
to the order as a whole.
I'd start by creating two views. (Not because you'll eventually need two views, but because some things are still unclear.)
One view, which has to do with "ranking" as applied to an order, would calculate or display three things.
Number of days since the last order.
Is this order the "one request carried at a time"?
The order priority.
If the numbers assigned to these three things are consistent in scale, you can just sort on those three columns. But that's not likely. You'll probably need to weight each factor, possibly by multiplying by a "weighting" factor. A calculation on the result should let you put these in a useful order. It's not yet clear whether the calculation is best done in the view or in a stored procedure.
The other view would have to do with whether a line item is in stock. It's not clear whether one line item out of stock means the whole order is incomplete, or whether one line item out of stock changes the calculation of a weighted number that scales along with the others above. (You can make a good argument for each of those approaches.)

Categories