Geohash in redis with php - php

I am using Redis with php and its library phpredis.
I have the following structure:
city:1 {
lat->14.02,
lon->10.00,
hash->sp2f1h60w5j
}
city:2 {
lat->14.03,
lon->10.1,
hash->sp2f1h60w5m
}
But I haven't found a way to search by hash. I would like to search for example the exact same hash or approximately the same hash.
Should I change my structure?
Thanks.

You could follow Josiah Carlson's advice from this thread, and convert the hash into a number, and use that as a score in a sorted set. Something like this:
city:1 {
lat->14.02,
lon->10.00,
hash->sp2f1h60w5j
}
city:2 {
lat->14.03,
lon->10.1,
hash->sp2f1h60w5m
}
To use the geohashes as numbers, you need to decode them from base 32 using a particular character map -- see Geohash on Wikipedia. I'll use example numbers below.
cities { (1, 4711), (2, 4712) }
Now you can use zrangebyscore to find cities in an area:
zrangebyscore cities 4000 5000

Why you don't use the key in redis to get the elements.
like:
city-sp2f1h60w5j = 1
city-sp2f1h60w5m = 2
then if you wish the closets:
get by "key":
city-sp2f1h60w*
if will return "array" of keys... or city id's. Can't remember the exact return data.
cheers

Do you want to find all items near a specific point? Since you're looking for exact or near match, I assume that's the case. Redis just released brand new Geo functionality yesterday (http://redis.io/commands#geo).
GeoRadius and GeoRadiusByMember are the ones you're looking for.

Related

PHP increment booking number according to the last booking number in database

I'm using PHP 7 with Phalcon PHP and I'm trying to create a method to generate a booking number. Here is my current method :
public function generateNumber($company_code) {
// Build the prefix : COMPANY20190820
$prefix = $company_code . date('Ymd');
// It's like SELECT count(*) FROM bookings WHERE number LIKE 'COMPANY20190820%'
$counter = Bookings::count(array(
"number LIKE :number:",
"bind" => array('number' => $prefix.'%')
));
// Concat prefix with bookings counter with str_pad
// COMPANY20190820 + 005 (if 4 bookings in DB)
$booking_number = $prefix . str_pad($counter + 1, 3, 0, STR_PAD_LEFT);
// Return COMPANY20190820005
return $booking_number;
}
So I have a problem because sometime I have to delete 1 or multiple bookings so I can get :
COMPANY20190820001
COMPANY20190820002
COMPANY20190820005
COMPANY20190820006
COMPANY20190820007
And I need to add after the last in my DB so here 007, because I can get duplicated booking number if I count like that.
So how can I do to take the last and increment according the last booking number of the current day ?
You need to rethink what you want to do here as it will never work that way.
As I see it you have at least two options:
Use an auto-increment id and use that in combination with the prefix
Use a random fairly unique string (e.g. UUID4)
You should never manually try to get the current maximum id as that may and most likely will at some point result in race conditions and brittle code as a result of that.
So I found a solution, maybe there is a better way to do that but my function works now:
public function generateNumber($company_code) {
// Build the prefix : COMPANY20190820
$prefix = $company_code . date('Ymd');
// Get the last booking with the today prefix
// e.g : COMPANY20190820005
$last_booking = Bookings::maximum(array(
"column" => "number",
"number LIKE :number:",
"bind" => array('number' => $prefix.'%')
));
// Get the last number by removing the prefix (e.g 005)
$last_number = str_replace($prefix, "", $last_booking);
// trim left 0 if exist to get only the current number
// cast to in to increment my counter (e.g 5 + 1 = 6)
$counter = intval(ltrim($last_number, "0")) + 1;
// Concat prefix + counter with pad 006
$booking_number = $prefix . str_pad($counter, 3, 0, STR_PAD_LEFT);
// Return COMPANY20190820006
return $booking_number;
}
I reckon that the use case you describe does not justify the hassle of writing a custom sequence generator in PHP. Additionally, in a scenario where booking deletion is expected to happen, ID reusing feels more a bug than a feature, so your system should store a permanent counter to avoid reusing, making it less simple. Don't take me wrong, it can be done and it isn't rocket science, but it's time and energy you don't need to spend.
Your database engine surely has a native tool to generate autoincremented primary keys, with varying names and implementations (SQL Server has identity, Oracle has sequences and identity, MySQL has auto_increment...). Use that instead.
Keep internal data and user display separated. More specifically, don't use the latter to regenerate the former. Your COMPANY20190820007 example is trivial to compose from individual fields, either in PHP:
$booking_number = sprintf('%s%s%03d',
$company_code,
$booking_date->format('Ymd'),
$booking_id
);
... or in SQL:
-- This is MySQL dialect, other engines use their own variations
SELECT CONCAT(company_code, DATE_FORMAT(booking_date, '%Y%m%d'), LPAD(booking_id, 3, '0')) AS booking_number
FROM ...
You can (and probably should) save the resulting booking_number, but you cannot use it as source for further calculations. It's exactly the same case as dates: don't need to store dates in plain English in order to eventually display them to the end-user and you definitively don't want to parse English dates back to actual dates in order to do anything else beyond printing.
You also mention the possibility of generating long pure-digit identifiers, as Bookings.com does. There're many ways to do it and we can't know which one they use, but you may want to considering generating a numeric hash out of your auto-incremented PK via integer obfuscation.
you could split your database field in two parts, so you hold the prefix and the counter separately.
then, you simply select the highest counter for your desired prefix and increment that one.
if you can't change the table structure, you could alternatively order by the id descendingly and select the first. then you can extract its counter manually. keep in mind you should pad the numbers then, or you get #9 even if #10 exists.
if padding is not an option, you can direct the database to replace your prefix. that way, you can cast the remaining string to a number and let the database sort - this will cost some performance, though, so keep the amount of records low.

Order by properly on SNMP oids

I've searched and tried lots of things but I can't find a solution.
I am storing some SNMP OIDs in a database, and displaying them in a table with datatables.
I want the OIDs to be displayed in the correct order so for example:
1.3.6.1.2.1.1
1.3.6.1.2.1.10
1.3.6.1.2.1.2
In correct order would be:
1.3.6.1.2.1.1
1.3.6.1.2.1.2
1.3.6.1.2.1.10
A SQL query with order by on the column storing the OID string would order them:
1.3.6.1.2.1.1
1.3.6.1.2.1.10
1.3.6.1.2.1.2
I'm using serverside processing with either PHP or preferably python flask. Currently I am building the table myself in flask and have written a function that orders them by converting the OIDs to tuples and sorting. This works but I would like to use datatables to get the pagination and responsiveness.
One thing to note is there isn't a limit on the length of the OID.
Any ideas would be much appreciated.
This is kind of a hack, but it might work. If each element in the OID has a max value < 100, then create a second column in the database, where each element is converted to a 2-digit 0-filled value:
real_oid sorting_oid
1.3.6.1.2.1.1 01.03.06.01.02.01.01
1.3.6.1.2.1.10 01.03.06.01.02.01.10
1.3.6.1.2.1.2 01.03.06.01.02.01.02
You could even eliminate the periods to save space, once you tested that is is all working.
First split the string on the period and typecast to int. Then use sorted and operator.itemgetter to sort by multiple attributes. Then re-join using a period. Something like the following:
original_oids = [...]
split_and_typecast_oids = [map(int, oid.split(".")) for oid in original_oids]
sorted_oids = sorted(split_and_typecast_oids, operator.itemgetter(1,2,3,4,5,6,7))
rejoined_oids = [".".join(map(str, oid)) for oid in sorted_oids]

Laravel decrement column value but not negative

I know i can reduce the column value in laravel using this query
DB::table('users')->decrement('votes', 5);
But i want to restrict the value from being become negative value.
Is there anyway to do this with laravel?
You'll need to use raw queries for that.
The following code should do the trick for all DB engines that support GREATEST function:
DB::table('users')->update(['votes' => DB::raw('GREATEST(votes - 5, 0)')]);
It will decrement votes column in users table by 5, but won't go below zero - that's what GREATEST function is used for.
If you really want to use decrement in this case (could be handy if you're accessing it through a relationship for example), you could do something like:
$thing->decrement('votes', $thing->votes - $amount <= 0 ? $thing->votes : $amount);
Where $amount is 5 in your case. It's pretty ugly in my opinion. Worth noting if you already have $thing (say via a relationship) it won't trigger an additional query when accessing votes.
If you are only incrementing by 1, a simply if wrapped around is cleaner:
if($thing->votes > 0) {
$thing->decrement('votes');
}

Calculate a boundary from set of geo locations

I have list of latitudes and longitudes.
These are acquired from reverse zip look up database and it's under a specific city (Dayton, OH).
39.721286 -84.133892
39.760000 -84.195900
39.757800 -84.177700
39.845339 -84.123287
...
I have a property database in mongodb and, each property has the location defined (lat/lng), when user enters "Dayton, OH", I'm grabbing Dayton's all the zip codes with relevant geo coordinates and what I want to do is to create a boundary and add 5 miles to it and search the property database which covers the lat/lng of dayton zip codes.
What's the best way to create a boundary (square/circle) to search a database within the range using this list of cordinates?
You can use the $near operator in MongoDB.
For example:
db.cities.find( { loc : { $near : { $geometry : { type : "Point" , coordinates : [ lon, lat] }, $maxDistance : 1/111.12 } } } )
The above query fiends everything near specific coordinates within a 1km radius (see $maxDistance).
For more information about the $near operator, take a look at the MongoDB Manual.
Found the solution, apparently the best approach is to calculate the border from application end using "convex hull" algorithm and then query.

Using Array "Keys" In a MySQL WHERE Clause

I have a data set that is generated by a Zip Code range search:
$zips:
key -> value
11967 -> 0.5
11951 -> 1.3
The key is the Zip Code (Which I need to query the Database for), and the value is the miles from the user entered zip code. I need to take the key (Zip Code) and search the database, preferably using a MySQL query similar to my current one:
$getlistings = mysql_query("SELECT * FROM stores WHERE zip IN ($zips)");
The other alternative is to change the array somehow in my code. I tried looking in code for where the array is generated originally but I couldn't find it. Any help would be greatly appreciated!! Thanks :)
You could convert the array keys to a SQL-compatible string. For example:
'11967', '11951'
and then use the string in the query.
Since the SQL query doesn't know what a php array is and there's no good way (that I know of) to extract just the keys and surround them in quotes, so this may be your best bet.
EDIT: As Ionut G. Stan wrote (and gave an example for), using the implode and array_map functions will get you there. However, I believe the solution provided will only work if your column definition is numeric. Character columns would require that elements be surrounded by apostrophes in the IN clause.
array_keys should be what you're looking for.
$zip = array_keys($zips); # gives you simple array(11967, 11951);
implode(', ', $zip); # results in: '11967, 11951'
Cannot comment the other answers, so one additional remark from my side. Depending on the country you are in and what you do with the data... In Germany there are ZIP-Codes starting with "0" so you should make sure that you either do not store them as numerical value if you want to compare them to other data (e.g. ZIP <-> geocoord-mappings) or make sure that you convert them to int everywhere and use filtering on the output.
Old ZIP codes had four numbers, new ones have five. So displaying a new ZIP with four numbers because the leading 0 is missing will lead to confusion.
Regarding use of a temporary table i would say it depends on the size of the table and how many zip codes are used in the query.
This should do it:
// array_map sanitizes the data
$zip_codes = implode(', ', array_map('intval', array_keys($zips)));
$getlistings = mysql_query("SELECT * FROM stores WHERE zip IN ($zip_codes)");
For best performance, you should create a temporary table, fill it with your ZIP codes and query like this:
SELECT *
FROM stores
JOIN temptable
ON zip = tempvalue
Of course this will be more efficient only if you ZIP column is indexed.
I just want to throw in that the previous code snippets gave me some syntax errors and the database just spitted out one entry instead of all relevant data. The following snippet worked for me:
implode("','", $zipNumbers);

Categories