MySQL > sum up into a single query (incl. google maps) - php

This Q is posted as a follow up to a comment on one of my Qs by #Vincent Savard.
Currently I'm trying (with some success - hitting timeout after 60 sec. for google-maps XML requests) to split data from a (pretty) large table into lots of smaller tables - plus converting/modifying/etc them on the fly. For this task I'm using php close to the following example:
// The following happens inside some functions.
// The main table has some "groups" of content.
// fn_a creates the new small tables
"
CREATE TABLE {$small_table_a}
col_group_a_id int UNSIGNED NOT NULL AUTO_INCREMENT,
col_group_a_fname tinytext,
col_group_a_lname tinytext,
col_group_a_valA tinytext,
col_group_a_valB tinytext,
col_group_a_address tinytext,
col_group_a_lat decimal(9,3),
col_group_a_lng decimal(9,3),
PRIMARY KEY (id)
"
"
CREATE TABLE {$small_table_b}
col_group_b_id int UNSIGNED NOT NULL AUTO_INCREMENT,
col_group_b_fname tinytext,
col_group_b_lname tinytext,
col_group_b_valA tinytext,
col_group_b_valB tinytext,
col_group_b_address tinytext,
col_group_b_lat decimal(9,3),
col_group_b_lng decimal(9,3),
PRIMARY KEY (id)
"
// fn_b loads the content from the big table, modifies it and saves it row per row into the small tables
$sql = "
SELECT *
FROM {$big_table}
"
foreach ( $sql as $data )
{
$id = $data->id;
$group_a_fname = $data->group_a_fname;
$group_a_lname = $data->group_a_lname;
$group_a_lname = "{$group_a_fname}, {$group_a_lname}";
$group_a_valA = $data->group_a_valA ? $data->group_a_valA : '-';
$group_a_valA = $data->group_a_valB ? $data->group_a_valB : 'none';
$group_a_valA = $data->group_a_address;
$group_b_fname = $data->group_b_fname;
$group_b_lname = $data->group_b_lname;
$group_b_name = "{$group_b_fname}, {$group_b_lname}";
$group_b_valA = $data->group_b_valA ? $data->group_b_valA : '/';
$group_b_valA = $data->group_b_valB ? "€ {$data->group_b_valB}" : null;
"
INSERT INTO {$small_table_a} ... VALUES ...
"
}
// fn_c pulls in data from the small tables, asks the google map API for lat & lng and _should_ update the small table
$sql = "
SELECT *
FROM {$small_table_a}
"
foreach ( $sql as $data )
{
$output['id'] = $data->id;
$address = urlencode( $data->address );
$url = "http://maps.google.com/maps/api/geocode/xml?address={$address}&sensor=false";
$content = file_get_contents( $url );
$file_data = new SimpleXMLElement( $content );
$file_data = $file_data->result ? $file_data->result : null;
if ( ! $file_data )
continue;
$location = $file_data->geometry->location;
$output['lat'] = (string) $location->lat;
$output['lng'] = (string) $location->lng;
}
foreach ( $output as $data )
{
"
UPDATE {$table}
SET lat=SET lat={$data['lat']}, lng={$data['lng']}
WHERE id=$data['id']
}
Question: How could I do this in one query? Or how could I reduce DB-queries? And how would I add the lat/lng to the tables without interrupting the query building when my geocoding limit was exceeded for today - I don't want to drop everything just because I've gone over my limit.
Thanks!
Note: The example was written by hand straight out of my mind. Failures may be in there.

We need to know what the INSERT INTO query is in you foreach loop, because this is the one that can be summed into one query. Basically, here is the idea:
INSERT INTO {$small_table} -- you can specify which columns to fill,
-- i.e. INSERT INTO table (col_a, col_b)
SELECT group_a_fname, group_a_lname,
group_a_valA, group_a_valB,
group_a_address, group_b_fname,
group_b_lname, group_b_valA, group_b_valB -- etc
FROM {$big_table};
Obviously, you'll have to adapt the query to fill your needs. You just need to grasp the idea behind it : you can insert rows with a SELECT query.
The UPDATE query is different because you have to rely on external data (a website). I don't think there is an easy way to do it in one query, but I may be wrong.

Related

INSERT / UPDATE Mysql Single Form

I have 2 database tables
tbl1 users ---------- tbl2 gamesystems
uid field ------------- gs_uid field
the 2 tables are tied together by the user_id..
now i want tbl2 to only be updated able and fields are not required.. with the exception of the gs_uid when they update there system.
my only issue is i need to insert the user_id into the gs_uid.
function game_system()
{
if(isset($_POST['game_system'])) {
$user_id = $_SESSION['uid'];
$motherboard = escape($_POST['motherboard']);
$processor = escape($_POST['processor']);
$memory = escape($_POST['memory']);
$graphics = escape($_POST['graphics']);
$harddrive = escape($_POST['harddrive']);
$power = escape($_POST['powersupply']);
$cooling = escape($_POST['cooling']);
$towercase = escape($_POST['towercase']);
$sql = "INSERT INTO gamesystem(gs_uid, motherboard, processor, memory, graphics, harddrive, powersupply, cooling, towercase) ";
$sql .= "VALUES('{$user_id}','{$motherboard}','{$processor}','{$memory}','{$graphics}','{$harddrive}','{$power}','{$cooling}','{$towercase}') ";
$result = query($sql);
}
}
If gs_uid is the primary key of table 'gamesystem' , then this field should not accept empty data.
Otherwise, if gs_uid is NOT the key, what's the primary key of this table? In case of UPDATE, you'll need to specify which row you'd like to update, otherwise the system will not know how to do so.
the SQL should looks like below
UPDATE "gamesystem"
SET "gs_uid" = $user_id
WHERE YOUR_PRIMARY_KEY_COLUMN = SPECIFIC VALUE;

PHP/SQL search form: which collation for making it insensitive to white space, order, and punctuation?

I have a search form that pulls data from a SQL database designed in phpmyadmin via PHP. Right now, the colums have utf8_general_ci collations, but I want the search to be not only case-insensitive and accent-insensitive (as it already is), but also insensitive to punctuation, order and white space.
For example, if my search box looks for an author, and my database entry is "Edgar Allan Poe", I want it to find this result even if someone fills the search box with:
- "Poe Edgar Allan"
- "Edgar Poe"
- "Edgar, Allan Poe"
(or another variation)
What collation do I need to choose to achieve this? Can I just change the collation in phpmyadmin, or do I need to add something to my code?
It's not that simple:
What you want to do is cannot be accomplished via collation alone. Collations only influence how the binary representation of text is interpreted, if a particular byte or group of bytes are read an uppercase or a lowercase or accented etc..
It cant reorder words for you, this has to be done either somewhere in the application code or by using more advanced text search indexing.
FULL TEXT indexing:
Most relational database management systems (RDBMS) like MySQL have a FULL TEXT indexing feature.
In MySQL FULL TEXT indexing doesn't help in making "fuzzy" searches it just creates a search index which allows the searches to be performed on the entire length of the text (not limited to 1024 characters like normal indexes are) and make the searches run faster.
CREATE TABLE `table` (
`id` UNSIGNED INT NOT NULL AUTO_INCREMENT,
`title` VARCHAR(255),
`short_description` VARCHAR(1023),
`description` TEXT,
PRIMARY KEY(`id`),
FULLTEXT (`title`, `body`)
) ENGINE=INNODB;
More on MySQL FULLTEXT indexing here.
Some of them implement even more complex and configurable FULL TEXT indexing features such as GiST and GIN.
In PostgreSQL I'm not very experienced but as far as I know GiST and GIN have some special functionality such as allowing you to use soundex, metaphone and custom functions to make "fuzzy" searches and it also works on some of the positional information between word (I think...).
Search servers:
To achieve what you want to achieve you most likely will need either a custom handwritten SQL query to cover word reordering or a search server such as Sphinx Search or Apache Solr.
There is a MySQL module that connects to a Sphinx Server -- it makes the Sphinx Server less complex to utilize because you'll be managing it as if it was just another table in your MySQL server the only difference being the fact that you specify Engine=Sphinx when creating the table.
Search servers allow you to configure priorities based on where the information was found (in the title vs in the description) and they search for variations based on word positions and things like that. If the search words are found in a text then the farther apart they are from each other, the lower the quality of the match (the results are sorted by relevance).
Search servers have logical grammar like Google such as specifying that a word should be excluded from the search or that two words need to be found exactly next to each other.
Search servers allow you to configure indexes that use stem words -- based on the language of the text, the words are read and analyzed for their base/root which allows searching for programmer and receiving results such as programming even though 'programmer' != 'programming'.
They also allow you to configure multiple indexes with different priorities, the highest priority would be the original text index, another would be the stemmed index, a third could be a synonym index the fourth and fifth could be soundex or metaphone indexes.
You can configure aliases, word replacements, common error replacements etc..
Custom built indexing structures:
data_table is the table containing what ever original content you would want to search through.
CREATE TABLE `data_table` (
`id` UNSIGNED INT NOT NULL AUTO_INCREMENT,
`title` VARCHAR(255),
`short_description` VARCHAR(1023),
`description` TEXT,
PRIMARY KEY(`id`),
FULLTEXT (`title`, `body`)
) ENGINE=INNODB;
CREATE TABLE `search_dictionary` (
`id` UNSIGNED INT NOT NULL AUTO_INCREMENT,
`word` VARCHAR(255) NOT NULL,
PRIMARY KEY(`id`),
UNIQUE KEY (`word`)
) ENGINE=INNODB;
CREATE TABLE `search_index` (
`id` UNSIGNED INT NOT NULL AUTO_INCREMENT,
`item_id` UNSIGNED INT NOT NULL,
`word_id` UNSIGNED INT NOT NULL,
`weight` UNSIGNED INT NOT NULL,
PRIMARY KEY(`id`),
UNIQUE KEY(`item_id`, `word_id`)
) ENGINE=INNODB;
Whenever you insert a new record into the data table you also process the following:
/* We need to differentiate how important it is
* if we find a word in the title of an item versus
* finding it in the description.
*/
define('SEARCH_WORD_TITLE_WEIGHT', 100);
define('SEARCH_WORD_SHORT_DESCRIPTION_WEIGHT', 10);
define('SEARCH_WORD_DESCRIPTION_WEIGHT', 1);
/* assuming we have a new item such as the one below */
$dataTableItem = array(
'id' => NULL,
'title' => $title
'short_description' => $short_description
'description' => $description,
);
/* we insert the new item and receive its new ID as a return value */
$dataTableItemId = insert_new_item($dataTableItem);
insert_search_index($dataTableItemId, $title, SEARCH_WORD_TITLE_WEIGHT);
insert_search_index($dataTableItemId, $short_description, SEARCH_WORD_SHORT_DESCRIPTION_WEIGHT);
insert_search_index($dataTableItemId, $description, SEARCH_WORD_DESCRIPTION_WEIGHT);
An example of how you could implement insert_search_index:
/* We need words to be a minimum number of characters
* otherwise they will be ignored by the search index.
*/
define('SEARCH_WORD_MIN_LENGTH', 3);
function insert_search_index ($itemId, $text, $weight = 1) {
/* replace every character which isn't a-z, A-Z, 0-9 or space with space */
$text = preg_replace('/[^a-zA-Z0-9\s]*/', ' ', $text);
/* explode the text into an array of words */
$text = explode(' ', $text);
foreach ($text as $word) {
if (strlen($word) >= SEARCH_WORD_MIN_LENGTH) {
/* counting the number of occurences */
if (isset($words[$word])) {
$words[$word]++
} else {
$words[$word] = 1
}
}
}
/* Insert all new words int dictionary
* we ensure we only insert new words by
* having `search_dictionary`.`word` unique
* and using INSERT IGNORE
*/
$insert_dictionary_query = '
INSERT IGNORE INTO `search_dictionary`
(`word`)
VALUES '
.'('.implode('), (', array_keys($words)).')
';
$pdo->exec($insert_dictionary_query);
/* Select all `word_id`s for the words we
* just added and insert them into the
* search_index along with the $weight
*/
foreach ($words as $word => $repetition) {
$insert_index_query = '
INSERT IGNOR INTO `search_index`
(`item_id`, `word_id`, `weight`)
VALUES
'.$itemId.', (
SELECT `id`
FROM `search_dictionary`
WHERE `word` = '.$word.'
), '.($weight * $repetition).'
';
$pdo->exec($insert_index_query);
if ($pdo->lastInsertId() === null) {
$update_index_query = '
UPDATE `search_index`
SET `weight` = `weight` + '.($weight * $repetition).'
WHERE `item_id` = '.$itemId.'
AND `word_id` = (
SELECT `id`
FROM `search_dictionary`
WHERE `word` = '."'".$word."'".'
);
';
}
}
}
Finally we need to query the search index to search for a particular (set of) words:
/* Assuming we have a search query in $_GET['q'] */
$_GET['q'] = preg_replace('/[^a-zA-Z0-9\s]*/', ' ', $_GET['q']);
$_GET['q'] = explode(' ', $_GET['q']);
$_GET['q'] = array_unique($_GET['q']);
$select_search_index_query = '
SELECT `search_index`.`item_id`,
SUM(`search_index`.`weight`) AS `total_weight`
FROM `search_index`
LEFT JOIN `search_dictionary`
ON `search_index`.`word_id` = `search_dictionary`.`id`
WHERE `search_dictionary`.`word` IN('."'".implode("','", $_GET['q'])."'".')
GROUP BY `search_index`.`item_id`
ORDER BY `total_weight`
';
Here is an example of sorting results using PHP, with some performance/memory usage tests:
// get current time and memory usage
$startTime = microtime(true);
$startMemory = memory_get_usage();
// get all rows
$queryRef = mysql_query('select id, name from product', $dbConnection);
$rows = array();
while ($row = mysql_fetch_array($queryRef, MYSQL_ASSOC)) {
$rows[] = $row;
}
// sort rows
function myCmp($a, $b)
{
$aName = preg_replace('/[^a-z0-9]/', '', strtolower($a['name']));
$bName = preg_replace('/[^a-z0-9]/', '', strtolower($b['name']));
return strcmp($aName, $bName);
}
usort($rows, 'myCmp');
// output time and memory usage statistics
$endTime = microtime(true);
$endMemory = memory_get_usage();
var_dump(array(
'count' => count($rows),
'time' => round($endTime - $startTime, 3) . ' seconds',
'memory' => (($endMemory - $startMemory) / 1024 / 1204) . 'MB'
));
// output result
var_dump($rows);
In my testing, with a small amount of data, that only takes a few milliseconds and a few hundred killobytes of RAM. With a large amount of data it becomes far too slow and uses too much memory.
If it's too slow or memory intensive, you need to look into other more complicated options as #MihaiStancu suggests.
Another option is duplicate data. Perhaps instead of just a "name" column, you could have a "name_for_sorting" column that contains the name already in lowercase and with punctuation/whitespace/etc removed.

IF and ELSE IF statements within a WHILE loop not working properly

I'm trying to create notification system for my community website, am trying to use a while loop to get data, when ever a condition in the if statement is met within the while loop, it should display/print data to the page. For some reason it's only displaying one result, dunno why.
The structure of my database:
CREATE TABLE IF NOT EXISTS `notifications` (
`notification_id` int(11) NOT NULL AUTO_INCREMENT,
`user_id` int(11) NOT NULL,
`to_id` int(11) NOT NULL,
`notification_identifier` enum('1','2','3','4','5','6') NOT NULL,
`notify_id` int(11) NOT NULL,
`opened` enum('0','1') NOT NULL,
`timestamp` datetime NOT NULL,
PRIMARY KEY (`notification_id`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1 AUTO_INCREMENT=4 ;
The notification_identifier tell me what type of notification it is(e.g. profile comment, status update, likes) and the notify_id tells me id of each specific table i need to check with.
My code:
<?
$DisplayNotification ="";
$unread = "0";
$mynotify = mysql_query("SELECT * FROM notifications WHERE to_id='$logOptions_id' AND opened='$unread'") or die (mysql_error());
$notify_Count = mysql_num_rows($mynotify);
if($notify_Count>0){
while($row = mysql_fetch_array($mynotify)){
$notification_id = $row["notification_id"];
$memb_id = $row["user_id"];
$identifier = $row["notification_identifier"];
$notify_id =$row["notify_id"];
$timestamp = $row["timestamp"];
$convertedTime = ($myObject -> convert_datetime($timestamp));
$when_notify = ($myObject -> makeAgo($convertedTime));
if($identifier == 1){// condition 1
$DisplayNotification ='user added you as a friend';
}else if ($identifier == 2) {//condition 2
$DisplayNotification ='user commented on your post';
}
}
}else{// End of $notify
$DisplayNotification ='You have no new notifications.';
}
?>
any help appreciated
Where is $DisplayNotification actually displayed? It's certainly not within the body of your loop.
Each time through the loop you assign $DisplayNotification a new value, which of course replaces the old value. By the time you get done, no matter what's happened, the most recent change is the only one left.
Most likely I suspect you meant to do something like
$DisplayNotification .= "User added you as a friend\n";
The .= will continue adding new text to the same variable throughout the loop.
Or perhaps you could use an array, in which case you'd do
$DisplayNotifications[] = "User added you as a friend";
Then you could display all the items at the end however you'd like.
It looks like you run the while statement fully before actually dumping the variable $DisplayNotification. If that is the case, you're just switching values on the variable during the loop. You either need to store the values to be dumped inside an Array or just dump them inside the loop.

JQuery & PHP Star Rating simplified

EDIT: The plugin in question is located here.
PHP beginner here using a JQuery Star Rating snippet and have gotten it to work perfectly. My problem is that it is currently configured to count and display the average of many ratings (for public applications). I'm trying to simplify the plugin so that it allows one to set a personal rating (as if rating your own songs in iTunes). The user may update their rating, but no partial stars would ever exist. I've broken the plugin many times trying to get it working, but to no avail. The mysql database exists as follows:
CREATE TABLE IF NOT EXISTS `pd_total_vote` (
`id` int(11) NOT NULL auto_increment,
`desc` varchar(50) NOT NULL,
`counter` int(8) NOT NULL default '0',
`value` int(8) NOT NULL default '0',
PRIMARY KEY (`id`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1 AUTO_INCREMENT=3 ;
If I can get it working the way I imagine, I wouldn't require both the counter and value columns, simply a single INT column that holds a value between 1 and 5. Currently counter accumulates the number of votes, while value aggregates the ratings. The stars are then displayed using (value/counter)*20 (as a percentage). The PHP is below (original):
<?php
// connect to database
$dbh=mysql_connect ("localhost", "user", "pass") or die ('Cannot connect to the database');
mysql_select_db ("thenally_pd",$dbh);
if($_GET['do']=='rate'){
rate($_GET['id']);
}else if($_GET['do']=='getrate'){
// get rating
getRating($_GET['id']);
}
// get data from table
function fetchStar(){
$sql = "select * from `pd_total_vote`";
$result=#mysql_query($sql);
while($rs = #mysql_fetch_array($result,MYSQL_ASSOC)){
$arr_data[] = $rs;
}
return $arr_data;
}
// function to retrieve
function getRating($id){
$sql= "select * from `pd_total_vote` where id='".$id."' ";
$result=#mysql_query($sql);
$rs=#mysql_fetch_array($result);
// set width of star
$rating = (#round($rs[value] / $rs[counter],1)) * 20;
echo $rating;
}
// function to set rating
function rate($id){
$text = strip_tags($_GET['rating']);
$update = "update `pd_total_vote` set counter = counter + 1, value = value + ".$_GET['rating']." where id='".$id."' ";
$result = #mysql_query($update);
}
?>
Thanks for a point in the right direction,
Mike
I am unsure as I have no access to the rating system you are using yet just glancing at what you have I guess you could keep the counter set to 1 (if removing it breaks the jQuery Rating System) and have the value updated by the person so when you fetch it they only see their value (make sure value can't go above 5). That way if the value is set to 5 then it will show 5 because it isn't finding other ratings.... (based on my understanding) You will also have to add a user id so you know which persons rating to fetch (since you want it personal). This depends on how dependent the application is a specific database design.

PHP and MySQL SELECT problem

Trying to check if a name is already stored in the database from the login user. The name is a set of dynamic arrays entered by the user threw a set of dynamic form fields added by the user. Can some show me how to check and see if the name is already entered by the login user? I know my code can't be right. Thanks!
MySQL code.
SELECT *
FROM names
WHERE name = '" . $_POST['name'] . "'
AND userID = '$userID'
Here is the MySQL table.
CREATE TABLE names (
id INT UNSIGNED NOT NULL AUTO_INCREMENT,
userID INT NOT NULL,
name VARCHAR(255) NOT NULL,
meaning VARCHAR(255) NOT NULL,
PRIMARY KEY (id)
);
If $_POST['name'] is actually an array of strings, as you say, then try this PHP:
$namesString = '';
foreach ($i=0; $i < count($_POST['name']) $i++)
{
$namesString .= "'" . mysql_real_escape_string($_POST['name'][$i]) . "'";
if(isset($_POST['name'][$i + 1]) $nameString .= ', ';
}
With this query:
SELECT * FROM `names`
WHERE `name` IN ( $namesString )
AND `userID` = '$userID'
The query will return all the rows in which the name is the same as string in $_POST['name'].
First of all, if the userID field is unique, you should add a unique index on it in your table.
Also, watch out for SQL injection attacks!
Using something like this is much more secure:
$sqlQuery = sprintf('SELECT COUNT(id) AS "found" FROM names WHERE userID = "%s"', mysql_real_escape_string($_POST['name'], $conn));
This SQL query will return 1 row with 1 field (named found) which will return you the number of matched rows (0 if none). This is perfect if you only want to check if the userID exists (you don't need to fetch all data for this).
As for the dynamic array, you will have to post more information and I'll update my answer.
Meanwhile here are some usefull PHP functions that can help you do what you want:
For MySQL queries:
mysql_connect
mysql_real_escape_string
mysql_query
mysql_fetch_assoc
For your list of users:
explode
implode
Stated as you say, I'm quite sure the code does exactly what you are asking for. The SELECT should return the records that respond both to the name sent and the current user ID.
If you need some php code, here it is (should be refined):
$result = mysql_query('YOUR SELECT HERE');
if (!$result) {
die('ERROR MESSAGE');
} else {
$row = mysql_fetch_assoc($result));
// $row is an associative array whose keys are the columns of your select.
}
Remember to escape the $_POST.

Categories