I'm creating a script that will search the database and look for customers that are in the Realtors latitude and longitude boundary range. If the customer lat and long coordinates is within the range of the realtor's lat and long boundaries then this script will email only the Realtor in that customers range. I'm using a CRON job to run the php script. I got the script to email each person that is in range of the Realtors but when a third Realtor is entered into the database the email goes to the third Realtor even though the lat and long is out of range.
How do I write a better loop where each row gets checked if the client is in range of that Realtor and only email that Realtor only? Thanks.
Here is my SQL code.
CREATE TABLE `realtors` (
`rid` int(11) NOT NULL AUTO_INCREMENT,
`rEmail` varchar(255) NOT NULL,
`rZipCode` int(10) NOT NULL,
`rDist` int(11) NOT NULL,
`rlatitude` numeric(30,15) NOT NULL,
`rlongitude` numeric(30,15) NOT NULL,
PRIMARY KEY (`rid`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
CREATE TABLE `customers` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`eMail` varchar(255) NOT NULL,
`zipCode` int(11) NOT NULL,
`clatitude` numeric(30,15) NOT NULL,
`clongitude` numeric(30,15) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
Here is my php code.
<?php
use geocodeloc\GeoLocation as GeoLocation;
require_once 'geocodeloc/GeoLocation.php';
//require_once 'phpmailer/PHPMailerAutoload.php';
$db = getDB();
//database prep for customers
$cust = $db->prepare("SELECT fullName, eMail, clatitude, clongitude FROM customers ORDER BY id DESC");
$cust->bindParam("fullName", $fullName,PDO::PARAM_STR);
$cust->bindParam("zipCode", $zipCode,PDO::PARAM_STR);
$cust->bindParam("eMail", $email,PDO::PARAM_STR);
$cust->bindParam("clatitude", $clatitude,PDO::PARAM_STR);
$cust->bindParam("clongitude", $clongitude,PDO::PARAM_STR);
$cust->execute();
$cust->rowCount();
//database prep for realtors
$realt = $db->prepare("SELECT rEmail, rDist, rlatitude, rlongitude FROM realtors ORDER BY rid DESC");
$realt->bindParam("rZipCode", $rZipCode,PDO::PARAM_STR);
$realt->bindParam("rEmail", $rEmail,PDO::PARAM_STR);
$realt->bindParam("rDist", $rDist,PDO::PARAM_STR);
$realt->bindParam("rlatitude", $rlatitude,PDO::PARAM_STR);
$realt->bindParam("rlongitude", $rlongitude,PDO::PARAM_STR);
$realt->execute();
$realt->rowCount();
$i = -1;
while ($realtor_row = $realt ->fetch(PDO::FETCH_ASSOC) AND $customers_row = $cust ->fetch(PDO::FETCH_ASSOC)) {
$i++;
$realtLatLong = GeoLocation::fromDegrees( $realtor_row['rlatitude'], $realtor_row['rlongitude']);
$coordinates = $realtLatLong->boundingCoordinates($realtor_row['rDist'], 'miles');
//look to see if customers latitude and longitude is within range of the realtors lat and long boundaries.
if($customers_row['clatitude'] && $customers_row['clongitude'] <= $coordinates){
//email the realtor
// the message
$msgBody = "This is a test";
// use wordwrap() if lines are longer than 70 characters
$msgBody = wordwrap($msgBody,70);
$Mailto = $realtor_row['rEmail'];
$FromName = $customers_row['fullName'];
// send email
mail($Mailto, $FromName , $msgBody);
}else{
//send to debug log
}
};
?>
Looping through the entire result set and doing the calculations is going to kill your database very quickly. Looping through one table and then looping through another to do a distance comparison is going to kill your database even faster. Luckily this is a re invention of the wheel. Mysql has built in functionality for this by way of ST_Distance
SELECT * FROM realtors INNER JOIN customers WHERE ST_within(customers.loc, realtors.loc) < 10; /* location in degrees */
Where one degree is approximately 111 kilometer. You whould need to change your table as follows
CREATE TABLE `realtors` (
`rid` int(11) NOT NULL AUTO_INCREMENT,
`rEmail` varchar(255) NOT NULL,
`rZipCode` int(10) NOT NULL,
`rDist` int(11) NOT NULL,
`loc` point NOT NULL,
PRIMARY KEY (`rid`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
CREATE TABLE `customers` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`eMail` varchar(255) NOT NULL,
`zipCode` int(11) NOT NULL,
`loc` POINT not null,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
of course this requires mysql 5.7
Using a spatial data type means that you can use an index for spatial looksup. In an RDBS if a table contains N rows, having an indes means you do not need to check through all those N number of rows to find a result. Thus using spatial data here + an index you can avoid the NxM time complexity you might have with lat,lng in separate columns.
No matter how fast you can make your code, the complexity will still be NxM.
First thing you should do is to create a relationship between Customer and Realtor, i.e. a table with Customer.id and Realtor.id. Take a hit the first time you populate this table (no need to change your code). After that, you just need to create a relationship everytime a Customer or a Realtor got added.
When it's time to send your email, you just need to look at the relationship table.
Related
I am creating a visitor counter for each my pages. According to PageId and visitor Ip, I created two tables such as pageView and pageTotalView. But my code is not working. I found some codes in php. How do I convert these codes to Codeigniter in working?
Database Tables:
CREATE TABLE `pageView` (
`pageViewId` int(11) NOT NULL AUTO_INCREMENT,
`pageViewPageId` int(11) NULL,
`pageViewUserIP` text NULL,
PRIMARY KEY (`id`)
) ENGINE=MyISAM AUTO_INCREMENT=2 DEFAULT CHARSET=latin1
// totalview
CREATE TABLE `pageTotalView` (
`pageTotalViewId` int(11) NOT NULL AUTO_INCREMENT,
`pageTotalViewPId` int(11) NULL,
`pageTotalVisit` text NULL,
PRIMARY KEY (`id`)
) ENGINE=MyISAM AUTO_INCREMENT=2 DEFAULT CHARSET=latin1
PHP Codes:
// gets the user IP Address
$pageViewUserIP = $_SERVER['REMOTE_ADDR'];
$check_ip = mysql_query("select pageViewUserIP from pageViewwhere pageViewPageId = 'I dont know How I can get viewing Page Id' and pageViewUserIP = '$pageViewUserIP'");
if(mysql_num_rows($check_ip)>=1)
{
}
else
{
$insertview = mysql_query("insert into pageView values('','I dont know How I can get viewing Page Id','$pageViewUserIP')");
$updateview = mysql_query("update pageTotalView set pageTotalVisit = pageTotalVisit+1 where pageViewPageId ='I dont know How I can get viewing Page Id' ");
}
To sum up, these codes maybe working on php, but How can I use these codes in Codeigniter?
Here is my function which i am using to un-follow users.It first DELETE the relationship between users and all the notifications that are related to this relationship.Then it INSERT a new notification for user which we are going to un-follow and then UPDATE his followers count (as one follower has left).I am using multi_query and this query seems to be bit slower on large database and i want to know whether it's a good practice or not or is there is any more complex form of query to get the job done.
PHP Function
// 'By' is the array that hold logged user and 'followed' is the user id which we are going to unfollow
function unFollowUser($followed,$by) {
$following = $this->getUserByID($followed);// Return fetch_assoc of user row
if(!empty($following['idu'])) { // if user exists
// return user followers as number of rows
$followers = $this->db->real_escape_string($this->numberFollowers($following['idu'])) - 1;
$followed_esc = $this->db->real_escape_string($following['idu']);
$by_user_esc = $this->db->real_escape_string($by['idu']);
// delete relationship
$query = "DELETE FROM `relationships` WHERE `relationships`.`user2` = '$followed_esc' AND `relationships`.`user1` = '$by_user_esc' ;" ;
// delete notification (user started following you )
$query.= "DELETE FROM `notifications` WHERE `notifications`.`not_from` = '$by_user_esc' AND `notifications`.`not_to` = '$followed_esc' ;" ;
// Insert a new notification( user has unfollowed you)
$query.= "INSERT INTO `notifications`(`id`, `not_from`, `not_to`, `not_content_id`,`not_content`,`not_type`,`not_read`, `not_time`) VALUES (NULL, '$by_user_esc', '$followed_esc', '0','0','5','0', CURRENT_TIMESTAMP) ;" ;
// update user followers (-1)
$query .= "UPDATE `users` SET `followers` = '$followers' WHERE `users`.`idu` = '$followed_esc' ;" ;
if($this->db->multi_query($query) === TRUE) {
return 1;
} else {
return 0;
}
} else {
return 0;
}
}
Table structures
--
-- Table structure for table `notifications`
--
CREATE TABLE IF NOT EXISTS `notifications` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`not_from` int(11) NOT NULL,
`not_to` int(11) NOT NULL,
`not_content_id` int(11) NOT NULL,
`not_content` int(11) NOT NULL,
`not_type` int(11) NOT NULL,
`not_read` int(11) NOT NULL,
`not_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
UNIQUE KEY `id` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
--
-- Table structure for table `relationships`
--
CREATE TABLE IF NOT EXISTS `relationships` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`user1` int(11) NOT NULL,
`user2` int(11) NOT NULL,
`status` int(11) NOT NULL,
`time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
--
-- Table structure for table `users`
--
CREATE TABLE IF NOT EXISTS `users` (
`idu` int(11) NOT NULL AUTO_INCREMENT,
`username` varchar(32) NOT NULL,
`password` varchar(256) NOT NULL,
`email` varchar(256) NOT NULL,
`first_name` varchar(32) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL,
`last_name` varchar(32) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL,
`verified` int(11) NOT NULL,
`posts` text CHARACTER SET utf32 NOT NULL,
`photos` text CHARACTER SET utf32 NOT NULL,
`followers` text CHARACTER SET utf32 NOT NULL,
UNIQUE KEY `id` (`idu`),
UNIQUE KEY `idu` (`idu`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
In my testing, multi_query has been the fastest way to execute multiple different queries. Why do you feel it's running slow? Compared to what?
Anyway, improvements could come from adding indexes to some of the columns you search frequently:
relationships.users2
relationships.users1
notifications.not_from
notifications.not_to
users.idu
Adding indexes makes searching faster, but it has at least two downsides:
Makes the DB a lot more resource hungry, which could affect your server performance
Makes writing operations take longer
I don't see any problem with your current queries. Really consider whether the slow performance you're seeing comes from the DB queries themselves, or from the rest of your PHP process. Try measuring the script time with the queries, then skipping the queries and taking another measurement (you could hardcode query results). It will give you an idea of whether the slowness is attributable to something else.
Either way, benchmark.
Try creating index on user where deletes are running , this may speed up query
I have been creating Wordpress plugin for a while. This is example of mysql table:
$sql = "CREATE TABLE IF NOT EXISTS $table_name (
id INT(11) NOT NULL AUTO_INCREMENT,
email VARCHAR(100) DEFAULT NULL,
telephone VARCHAR(15) DEFAULT NULL,
PRIMARY KEY(id),
UNIQUE (email, telephone)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='WP plugin sesa_players db' AUTO_INCREMENT=1 ;
";
Email should be unique, right? phpMyAdmin says it it.
This is wordpress code that inserts data into that table:
$err = $wpdb->insert($wpdb->prefix.$table_name, $data, $format);
var_dump($err);
It works, even more than it should. Assume email is m#m.com. First insert goes well. Second try fails because of duplicate entry as it should. var_dump is false.
BUT if I refresh wp page, third try with same email passes flawlessly, var_dump 1. Any repeated wp refresh opens db for duplicate entry.
Why? What am I doing wrong?
No, email is not UNIQUE here. Pair of email and telephone is UNIQUE in your table definition.
$sql = "CREATE TABLE IF NOT EXISTS $table_name (
id INT(11) NOT NULL AUTO_INCREMENT,
email VARCHAR(100) DEFAULT NULL,
telephone VARCHAR(15) DEFAULT NULL,
PRIMARY KEY(id),
UNIQUE (email),
UNIQUE (telephone)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='WP plugin sesa_players db' AUTO_INCREMENT=1 ;
";
Probably this is what you want.
I'm making a simple PHP Forum, whereby tags are created alongside the main topic.
The table looks like this:
CREATE TABLE IF NOT EXISTS `topic` (
`topic_id ` int(100) NOT NULL AUTO_INCREMENT,
`topic_head` varchar(5) NOT NULL,
`topic_body` varchar(20) NOT NULL,
`topic_tag` varchar(20) NOT NULL,
`topic_date` varchar(20) NOT NULL,
`topic_owner` varchar(20) NOT NULL,
PRIMARY KEY (`topic_id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1 AUTO_INCREMENT=4 ;
Specifically for the tags, I'll be performing a select query in the form of:
$tags = mysql_query("SELECT DISTINCT topic_tags
FROM forum_topics")
while($tags = mysql_fetch_assoc($tags){
$split_tags = "$tags";
$pieces = explode(",", $split_tags);
Currently, topic_tags are in the format tag1,tag2,tag3,tag4
How can I have it in such a way that each topic tag will be associated with each topic?
If I've understood correctly, what you want is another table for tags and then a third table to store the relationships. So:
CREATE TABLE `tags` (
`t_id` int(11) NOT NULL AUTO_INCREMENT,
`t_text` varchar(150) NOT NULL,
`t_datetime` datetime NOT NULL,
PRIMARY KEY (`t_id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1 AUTO_INCREMENT=1 ;
CREATE TABLE `tag_pairs` (
`tp_id` int(11) NOT NULL AUTO_INCREMENT,
`tp_topic_id` int(11) NOT NULL,
`tp_tag_id` int(11) NOT NULL,
`tp_datetime` datetime NOT NULL,
PRIMARY KEY (`tp_id`),
FOREIGN KEY (`tp_topic_id`) REFERENCES topic('topic_id'),
FOREIGN KEY (`tp_tag_id`) REFERENCES tags('t_id')
) ENGINE=InnoDB DEFAULT CHARSET=latin1 AUTO_INCREMENT=1 ;
Then, to get tags for a topic based on its ID ($THIS_ID):
$query = mysql_query("
SELECT tags.t_text
FROM tags, tag_pairs, topic
WHERE topic.topic_id = '$THIS_ID'
AND tag_pairs.tp_topic_id = topic.topic_id
AND tag_pairs.tp_tag_id = tags.t_id
");
Call the columns just: id, head, body, tag_id (FK), date and user_id (FK)
This is much more understandable and also easier to use. Let me explain:
Right now you are using the columns as: topic_id, but it should be: topic.id. How do you get this? By simply using the tablename + column. So when the table is called topics and you have a column called id, you can use it by saying: topics.id. In the answer above from da5id, I can see that he is using topics.topic_id, a bit overkill isn't it? ;)
Also, read this article about database normal form 3 and google a bit yourself with database+3nf
I have a simple table as below.
CREATE TABLE `stats` (
`id` int(11) NOT NULL auto_increment,
`zones` varchar(100) default NULL,
`date` date default NULL,
`hits` int(100) default NULL,
PRIMARY KEY (`id`)
) ENGINE=MyISAM AUTO_INCREMENT=7 DEFAULT CHARSET=latin1;
So just storing simple hits counter per zone per day.
But I just want to increment the hits value for the same day.
I have tried the MYSQL DUPLICATE KEY UPDATE but this wont work as I may have many zones on different dates so I cant make them unique or dates.
So the only way I can think is first to do a query to see if a date exists then do a simple if() for insert/update
Is their a better way of doing such a task as there maybe be many 1000's hits per day.
Hope this makes sense :-).
And thanks if you can advise.
Declare the tuple (zone, date) as unique in your CREATE statement. This will make INSERT ... ON DUPLICATE UPDATE work as expected:
CREATE TABLE `stats` (
`id` int(11) NOT NULL auto_increment,
`zone` varchar(100) default NULL,
`date` date default NULL,
`hits` int(100) default NULL,
PRIMARY KEY (`id`),
UNIQUE (`zone`, `date`)
) ENGINE=MyISAM AUTO_INCREMENT=7 DEFAULT CHARSET=latin1;
INSERT INTO stats (zone, date, hits) values ('zone1', 'date1', 1) ON DUPLICATE KEY UPDATE hits = hits + 1;
$result = mysql_query("SELECT id FROM stats WHERE zone=$zone AND date=$today LIMIT 1");
if(mysql_num_rows($result)) {
$id = mysql_result($result,0);
mysql_query("UPDATE stats SET hits=hits+1 WHERE id=$id");
} else {
mysql_query("INSERT INTO stats (zone, date, hits) VALUES ($zone, $today, 1)");
}
Something like that, if I've interpreted you correctly... that's completely untested. You can figure out what the variables are.