MySQL - Using LIKE ? With Multiple Columns Search - mysql

I've had a look around Stackoverflow and can't seem to find what I am looking for.
I have a dynamically updating AJAX search form which shows location data from database.
The issue I am having is with this query here:
$sql = "SELECT location FROM location_data WHERE location LIKE ? LIMIT 10";
Let me explain what is happening first. There are 3 different columns in a database table, one called location, one called CRS and one called tiploc.
I would like to display results like the following:
Select location FROM location_data WHERE location(textbox) is LIKE ?(what the person typed in) OR CRS is LIKE ? or TIPLOC is LIKE ?
Now i've only tried it with CRS so far, and ive done the following query:
$sql = "SELECT location FROM location_data WHERE location OR CRS LIKE ? LIMIT 10";
The above only displays the CRS result (exact match) and doesn't provide any suggestions for location, only shows CRS. Does anyone know how I can amend my query, so that it searches both location and CRS and TIPLOC, LIKE on location, but exact match only on CRS and TIPLOC?
if(isset($_REQUEST['term'])){
// Prepare a select statement
$sql = "SELECT location FROM location_data WHERE location LIKE ? LIMIT 10";
if($stmt = mysqli_prepare($link, $sql)){
// Bind variables to the prepared statement as parameters
mysqli_stmt_bind_param($stmt, "s", $param_term);
// Set parameters
$param_term = $_REQUEST['term'] . '%';
// Attempt to execute the prepared statement
if(mysqli_stmt_execute($stmt)){
$result = mysqli_stmt_get_result($stmt);
// Check number of rows in the result set
if(mysqli_num_rows($result) > 0){
// Fetch result rows as an associative array
while($row = mysqli_fetch_array($result, MYSQLI_ASSOC)){
echo "<p>" . $row["location"] . "</p>";
}
} else{
echo "<p>No matches found</p>";
}
} else{
echo "ERROR: Could not able to execute $sql. " . mysqli_error($link);
}
}
// Close statement
mysqli_stmt_close($stmt);
}
// close connection
mysqli_close($link);
Now heres the on page search, the field I am pulling input from is called "location".
--Code for JS AJAX Search--
<script type="text/javascript">
$(document).ready(function(){
$('.search-box input[type="text"]').on("keyup input", function(){
/* Get input value on change */
$(".result").show();
var inputVal = $(this).val();
var resultDropdown = $(this).siblings(".result");
if(inputVal.length >2){
$.get("backend-search.php", {term: inputVal}).done(function(data){
// Display the returned data in browser
resultDropdown.html(data);
});
} else{
resultDropdown.empty();
}
});
// Set search input value on click of result item
$(document).on("click", ".result p", function(){
$(this).parents(".search-box").find('input[type="text"]').val($(this).text());
$(this).parent(".result").empty();
});
});
$(document).click(function(){
$(".result").hide();
});
</script>

You need to repeat the LIKE expression for each column.
$sql = "SELECT location
FROM location_data
WHERE location LIKE ? OR CRS LIKE ? OR TIPLOC LIKE ?
LIMIT 10";
And since there are now 3 placeholders in the query, you need to fill them all in with the binding:
mysqli_stmt_bind_param($stmt, "sss", $param_term, $param_term, $param_term);

For every individual expression in OR, you have to specify their comparison conditions. Note the location LIKE ? instead of LOCATION OR:
$sql = "SELECT location
FROM location_data
WHERE location LIKE ?
OR CRS LIKE ?
OR TIPLOC LIKE ?
LIMIT 10";
Note: LIMIT clause without ORDER BY is non-deterministic in nature, since MySQL stores an unordered dataset. It basically means that, any 10 rows can be returned by MySQL (if not using ORDER BY).

Related

SQL SELECT query excluding an array of id's for infinite scroll [duplicate]

I have followed help located in this topic: Using infinite scroll w/ a MySQL Database
And have gotten close to getting this working properly. I have a page that is displayed in blocks using jquery masonry, in which the blocks are populated by data from a mysql database. When I scroll to the end of the page I successfully get the loading.gif image but immediately after the image it says "No more posts to show." which is what it should say if that were true. I am only calling in 5 posts initially out of about 10-15, so the rest of the posts should load when I reach the bottom of the page but I get the message that is supposed to come up when there really aren't any more posts.
Here is my javascript:
var loading = false;
$(window).scroll(function(){
if($(window).scrollTop() == $(document).height() - $(window).height()) {
var h = $('.blockContainer').height();
var st = $(window).scrollTop();
var trigger = h - 250;
if((st >= 0.2*h) && (!loading) && (h > 500)){
loading = true;
$('div#ajaxLoader').html('<img src="images/loading.gif" name="HireStarts Loading" title="HireStarts Loading" />');
$('div#ajaxLoader').show();
$.ajax({
url: "blocks.php?lastid=" + $(".masonryBlock:last").attr("id"),
success: function(html){
if(html){
$(".blockContainer").append(html);
$('div#ajaxLoader').hide();
}else{
$('div#ajaxLoader').html('<center><b>No more posts to show.</b></center>');
}
}
});
}
}
});
Here is the php on the page the blocks are actually on. This page initially posts 5 items from the database. The javascript grabs the last posted id and sends that via ajax to the blocks.php script, which then uses the last posted id to grab the rest of the items from the database.
$allPosts = $link->query("/*qc=on*/SELECT * FROM all_posts ORDER BY post_id DESC LIMIT 5");
while($allRows = mysqli_fetch_assoc($allPosts)) {
$postID = $link->real_escape_string(intval($allRows['post_id']));
$isBlog = $link->real_escape_string(intval($allRows['blog']));
$isJob = $link->real_escape_string(intval($allRows['job']));
$isVid = $link->real_escape_string(intval($allRows['video']));
$itemID = $link->real_escape_string(intval($allRows['item_id']));
if($isBlog === '1') {
$query = "SELECT * FROM blogs WHERE blog_id = '".$itemID."' ORDER BY blog_id DESC";
$result = $link->query($query);
while($blogRow = mysqli_fetch_assoc($result)) {
$blogID = $link->real_escape_string($blogRow['blog_id']);
$blogTitle = $link->real_escape_string(html_entity_decode($blogRow['blog_title']));
$blogDate = $blogRow['pub_date'];
$blogPhoto = $link->real_escape_string($blogRow['image']);
$blogAuthor = $link->real_escape_string($blowRow['author']);
$blogContent = $link->real_escape_string($blogRow['content']);
//clean up the text
$blogTitle = stripslashes($blogTitle);
$blogContent = html_entity_decode(stripslashes(truncate($blogContent, 150)));
echo "<div class='masonryBlock' id='".$postID."'>";
echo "<a href='post.php?id=".$blogID."'>";
echo "<div class='imgholder'><img src='uploads/blogs/photos/".$blogPhoto."'></div>";
echo "<strong>".$blogTitle."</strong>";
echo "<p>".$blogContent."</p>";
echo "</a>";
echo "</div>";
}
}
Here is the php from the blocks.php script that the AJAX calls:
//if there is a query in the URL
if(isset($_GET['lastid'])) {
//get the starting ID from the URL
$startID = $link->real_escape_string(intval($_GET['lastid']));
//make the query, querying 25 fields per run
$result = $link->query("SELECT * FROM all_posts ORDER BY post_id DESC LIMIT '".$startID."', 25");
$html = '';
//put the table rows into variables
while($allRows = mysqli_fetch_assoc($result)) {
$postID = $link->real_escape_string(intval($allRows['post_id']));
$isBlog = $link->real_escape_string(intval($allRows['blog']));
$isJob = $link->real_escape_string(intval($allRows['job']));
$isVid = $link->real_escape_string(intval($allRows['video']));
$itemID = $link->real_escape_string(intval($allRows['item_id']));
//if the entry is a blog
if($isBlog === '1') {
$query = "SELECT * FROM blogs WHERE blog_id = '".$itemID."' ORDER BY blog_id DESC";
$result = $link->query($query);
while($blogRow = mysqli_fetch_assoc($result)) {
$blogID = $link->real_escape_string($blogRow['blog_id']);
$blogTitle = $link->real_escape_string(html_entity_decode($blogRow['blog_title']));
$blogDate = $blogRow['pub_date'];
$blogPhoto = $link->real_escape_string($blogRow['image']);
$blogAuthor = $link->real_escape_string($blowRow['author']);
$blogContent = $link->real_escape_string($blogRow['content']);
$blogTitle = stripslashes($blogTitle);
$blogContent = html_entity_decode(stripslashes(truncate($blogContent, 150)));
$html .="<div class='masonryBlock' id='".$postID."'>
<a href='post.php?id=".$blogID."'>
<div class='imgholder'><img src='uploads/blogs/photos/".$blogPhoto."'></div>
<strong>".$blogTitle."</strong>
<p>".$blogContent."</p>
</a></div>";
}
}
echo $html;
}
I have tried using the jquery infinite-scroll plugin, but it seemed much more difficult to do it that way. I don't know what the issue is here. I have added alerts and did testing and the javascript script is fully processing, so it must be with blocks.php right?
EDIT: I have made a temporary fix to this issue by changing the sql query to SELECT * FROM all_posts WHERE post_id < '".$startID."' ORDER BY post_id DESC LIMIT 15
The blocks are now loading via ajax, however they are only loading one block at a time. The ajax is sending a request for every single block and they are fading in one after another, is it possible to make them all fade in at once with jquery masonry?
I seen your code in another answer, and I would recommend using the LIMIT functionality in MySql instead of offsetting the values. Example:
SELECT * FROM all_posts ORDER BY post_id DESC LIMIT '".(((int)$page)*5)."',5
This will just take a page number in the AJAX request and get the offset automatically. It's one consistent query, and works independent of the last results on the page. Send something like page=1 or page=2 in your jQuery code. This can be done a couple different ways.
First, count the number of elements constructed on the page and divide by the number on the page. This will yield a page number.
Second, you can use jQuery and bind the current page number to the body:
$(body).data('page', 1)
Increment it by one each page load.
Doing this is really the better way to go, because it uses one query for all of the operations, and doesn't require a whole lot of information about the data already on the page.
Only thing to note is that this logic requires the first page request to be 0, not 1. This is because 1*5 will evaluate to 5, skipping the first 5 rows. If its 0, it will evaluate to 0*5 and skip the first 0 rows (since 0*5 is 0).
Let me know any questions you have!
Have you tried doing any debugging?
If you are not already using, I would recommend getting the firebug plugin.
Does the ajax call return empty? If it does, try echoing the sql and verify that is the correct statement and that all the variables contain the expected information. A lot of things could fail considering there's a lot of communication happening between client, server and db.
In response to your comment, you are adding the html in this piece of code:
if(html){
$(".blockContainer").append(html);
$('div#ajaxLoader').hide();
}
I would do a console.log(html) and console.log($(".blockContainer").length) before the if statement.

Loop in column name MYSQL

I am using MYSQL.My table contains column name as Revenue2000,Revenue2001,Revenue2002,....,Revenue 2016,Revenue 2017
Traditional way(to select all column manually):
select Revenue2005,
Revenue2006,
Revenue2007,
Revenue2008,
Revenue2009,
Revenue2010
from table_name
Desired Way:
I want to write a Dynamic select statement .There should 2 variables "start" and "end" so that i can make it dynamic.User has the option to specify the starting year and ending year and can view the desired result.
In above case, Start year =2005
End Year=2010
Yes, it's bad database design, and the best answer would be "don't do this at all, just fix your table." Unfortunately, sometimes you're stuck with something someone else made, and can't change it for whatever reason, but you still need to accomplish something (welcome to my life). I would do it like this:
Get the years from user input and convert them to integers in case someone enters something silly/naughty. Don't depend on client-side validation. Prepared statements won't help you here because these will be used as parts of column names.
$start = (int) $_POST['start'];
$end = (int) $_POST['end'];
Do a quick sanity check to make sure that the range makes sense and should work with what's in your database.
if ($start > $end
|| $start < $lowest_year_in_your_db
|| $end > $highest_year_in_your_db) {
// quit with error
}
Then you can generate a list of columns to use in your query. Here's one way with range and array_map, but you could also just build a string with a for loop.
$columns = implode(', ', array_map(function($year) {
return "Revenue$year";
}, range($start, $end)));
$sql = "SELECT $columns FROM table_name";
Theoretically, the worst thing that should be able to happen with this is that you'd get a column that didn't exist, and your query would fail.
But really, if you have any choice about it, don't do this. Normalize your database as people have stated in the comments, or find whoever keeps adding more year columns to the database and make them do it.
As already pointed out the database design is horrible. You should really normalize it, it's worth the effort.
However if that is not possible at the moment the follow code should do exactly what you need:
// Connect to DB
$mysqli = new mysqli("localhost", "USERNAME", "PASSWORD", "DATABASE");
// Get column names
$columns = $mysqli->query('SHOW COLUMNS FROM revenue')->fetch_all();
$columnNames = array_column($columns, 0);
// Extract years from column names
$years = array_map(function($columnName) {
return (int) substr($columnName, -4);
}, $columnNames);
// Get max and min year
$maxYear = max($years);
$minYear = min($years);
// Input year start and end
$start = (int) $_POST['start']; // User-input
$end = (int) $_POST['end']; // User-input
// Avoid wrong inputs
if($start > $end || $start < $minYear || $end > $maxYear) {
die('Error');
}
// Create the SQL-query
$selectColumns = [];
for ($i = $start; $i <= $end; $i++) {
$selectColumns[] = "revenue" . $i;
}
$queryString = "SELECT " . implode(", ", $selectColumns) . " FROM TABLE";
// Run the query
// ...

Laravel: Querying based on Input. If input is empty, get all

I have a calendar (FullCalendar) where the user can filter down results based on a few params (Tutor Secondary Tutor, Lesson, Location). When the user makes a change to the query it hits the following code.
The issue I am having is the 'OR'. What I really want is an IF input is null then get all.
If User { Get all lessons where lead_tutor_id = 1 and secondary_tutors_id = 1 }
If User and Location { Get lessons where the user is as above, but have location_id = 3 }
etc, etc.
So, is there a way I can fall back to get ALL the results IF only one or two filters are set?
$current_events = Calendar::Where(function($query) use ($start_time, $end_time, $tutor, $location, $lesson)
{
$query->whereBetween('date_from', [$start_time, $end_time])->orderBy('date_from')
->whereRaw('lead_tutor_id = ?
OR secondary_tutors_id = ?
OR location_id = ?
OR lesson_id = ?',
[
$tutor, // Input get() for user
$tutor, // Input get() for user
$location, // Input get() for location
$lesson, // Input get() for lesson
]
);
})->with('lessons', 'leadtutor', 'secondarytutor')->get();
I've been playing with Query Scopes, but this seems to fail if passing a NULL value through to it.
Any help is very much appreciated. Thanks in advance.
You can build the query on forehand, store it in a variable and use it once its build.
$query = isset($var) ? $var : '';
$query .= isset($othervar) ? $othervar : '';
whereBetween(*)->orderBy(*)->whereRaw($query)
Only thing you need to keep in mind is to insert the 'OR's in the right place . So have like a check for wether it is the first thing to be inserted or not, if not then put 'OR' in front of it.
Hope that is enough info to help you.
After the advice from Saint Genius, I have got this working:
$built_query = [];
isset($lead_tutor) ? $built_query['lead_tutor'] = 'lead_tutor_id = ' . $lead_tutor . ' ' : null;
isset($secondary_tutor) ? $built_query['secondary_tutor'] = 'secondary_tutors_id = ' . $secondary_tutor . ' ' : null;
isset($location_id) ? $built_query['location'] = 'location_id = ' . $location_id : null;
isset($lesson_id) ? $built_query['lesson'] = 'lesson_id = ' . $lesson_id : null;
// Flatten the array so we can create a query and add the word ADD in between each element.
$built_query = implode(" AND ", $built_query);
// Run the query
$current_events = Calendar::whereBetween('date_from', [$start_time, $end_time])->orderBy('date_from')->whereRaw($built_query)->with('lessons', 'leadtutor', 'secondarytutor')->get();

MySQL and using only some results

I am trying to create a directory and having an issue calling the "listing image" in the results. The issue is that only some listings will have images, otherwise if they do not, I want them to use the default-image I have set up. When I try and add in the 'image' table to my query, it returns ONLY the results that have an image available (leaving out the other listings that do not have an image).
Here is my code:
public function search($neighborhood = null, $biz_filter = null) {
$neighborhood = $this->uri->segment(3);
$biz_filter = $this->uri->segment(4);
// SELECT
$this->db->select('*');
// MAIN TABLE TO GRAB DATA
$this->db->from('biz');
// TABLES TO JOIN
$this->db->join('city', 'city.city_id = biz.biz_cityID');
$this->db->join('zip', 'zip.zip_id = biz.biz_zipID', 'zip.zip_cityID = city.city_id');
$this->db->join('state', 'state.state_id = city.city_stateID');
$this->db->join('neighborhood', 'neighborhood.neighborhood_id = biz.biz_neighborhoodID');
$this->db->join('biz_filter', 'biz_filter.bizfilter_bizID = biz.biz_id');
$this->db->join('biz_category', 'biz_category.bizcategory_id = biz_filter.bizfilter_bizcategoryID');
if ($neighborhood != "-" AND $biz_filter != "-") {
$this->db->where('biz_category.bizcategory_slug', $biz_filter);
$this->db->where('neighborhood.neighborhood_slug', $neighborhood);
} elseif ($neighborhood != "-" AND $biz_filter == "-") {
$this->db->where('neighborhood.neighborhood_slug', $neighborhood);
} elseif ($neighborhood == "-" AND $biz_filter != "-") {
$this->db->where('biz_category.bizcategory_slug', $biz_filter);
} else {
}
// ORDER OF THE RESULTS
$this->db->group_by('biz_name asc');
// RUN QUERY
$query = $this->db->get();
// IF MORE THAN 0 ROWS ELSE DISPLAY 404 ERROR PAGE
return $query;
}
How can I add in the separate table, 'image' that holds the logo images ('image.image_file'). The 'image' table and 'biz' table are connected through the business ID i pass through each table (image.biz_id = biz.biz_id).
Anyone know how to resolve the query to work properly?
Just use
$this->db->join('image', 'image.biz_id = biz.biz_id', 'left');
To LEFT JOIN your image table. When there is no records in the table for the biz_id the image.image_file will have null values. Read here for more information.
You can use a COALESCE function to replace the "null" images with a predefined default value. Just replace your line with $this->db->select('*'); to this one:
// SELECT
$this->db->select("*, COALESCE(image.image_file, 'images/not_found.png') as my_image_file");
When you render the output make sure you use my_image_file column for the image.
On a side note: avoid using '*' in the select. Select only those columns you actually need. Selecting all columns unnecessarily increases the load on the database server resources.

Display selected amount of records from database

I need a page that displays records from a database, sorted by their jobs. So the database holds different kind of persons with different jobs. For example, on the page "teacher" I just want to display all the teachers, not the other persons. I want to have 3 persons on a page and a button "previous" and "next" beneath it. If an user clicks the next-button I want the next 3 records to be shown. When the user reaches the last records, I need the next-button to disappear. Same goes for "previous".
What I have so far:
This piece of code creates a value named startrow.
if (!isset($_GET['startrow']) or !is_numeric($_GET['startrow'])) {
//give the value of the starting row 0 because nothing was found in URL
$startrow = 0;
//otherwise take the value from the URL
} else {
$startrow = (int)$_GET['startrow'];
}
The query I have:
$sql = mysql_query("SELECT * FROM $tbl_name WHERE jobs='teacher' LIMIT $startrow, 3")or
die(mysql_error());
$sql2 = "SELECT COUNT(*) AS TotalJobs FROM $tbl_name WHERE jobs='teacher'";
$result_count = mysql_query($sql2);
$count = mysql_fetch_array($result_count);
I created the $count for the if / else function in the next part (the part that doesn't seem to work the way I want it to):
if ($startrow < $count )
echo 'Next';
$prev = $startrow - 3;
//only print a "Previous" link if a "Next" was clicked
if ($prev >= 0)
echo 'Previous';
So the previous-button seems to work the way it should. The next doesn't. I've created the
if ($startrow < $count)
so basicly, if the value of startrow is smaller than the total number of records, it puts a next-button. But if I test this, it displays the next-button anyhow, no matter the value of startrow.
What am I missing here?
mysql_fetch_array returns an array containing the count, not the value itself. Use $count['TotalJobs'] instead.
Also, you shouldn't use the mysql extension anymore, instead use PDO or MySQLi.
Edit
You can change:
$count = mysql_fetch_array($result_count);
to
$countArr = mysql_fetch_array($result_count);
$count = $countArr['TotalJobs'];