Build PostgreSQL SELECT dynamically with different number of parameters - sql

I'd like to write my PostgreSQL (9.3) SELECT dynamically based on the user's fields filled out in the query form:
$cond = '';
if ($_REQUEST['sel_when_a'] != '')
$cond .= ' AND field_lastmod >= $2';
if ($_REQUEST['sel_when_b'] != '')
$cond .= ' AND field_lastmod <= $3';
$sel = ExecSQLP('SELECT field_value, field_lastmod
FROM my_values
WHERE field_value ILIKE \'%\' || $1 || \'%\'
. $cond .
' ORDER BY node_id;'
, array( $_REQUEST['sel_find'], $_REQUEST['sel_when_a'], $_REQUEST['sel_when_b'] ));
(ExecSQLP is my function to execute an SQL statement with the arguments, based on pg_query_params.)
This is simple and elegant, but I get an error message: Query failed: ERROR: bind message supplies 3 parameters, but prepared statement "" requires 1 if sel_when_a ("after") and sel_when_b ("before") are empty.
I think this shouldn't be an error in PostgreSQL as it doesn't do any bad if an unused parameter is supplied. Can I suppress this error? Othervise how to build the statement with different number of parameters? I have a lot of them, not only the 3 above. It'd be terrible to prepare for the several cases with renumbered parameters if PostgreSQL doesn't allow this.
The documentation: http://www.postgresql.org/docs/9.3/interactive/sql-expressions.html#SQL-EXPRESSIONS-PARAMETERS-POSITIONAL is not very useful for this case.
Thanks for your tips. SQL injection vulnerability is not accepted!
(Perhaps similar to How to handle dynamic number of parameters in querystring when building REST api?, but the relation is not always "equals to a string".)

I'd build the parameter array while building query. Like this:
$cond = '';
$params = array( $_REQUEST['sel_find'] );
if ($_REQUEST['sel_when_a'] != '') {
$params[] = $_REQUEST['sel_when_a'];
$cond .= ' AND field_lastmod >= $'.count($params);
}
if ($_REQUEST['sel_when_b'] != '') {
$params[] = $_REQUEST['sel_when_b'];
$cond .= ' AND field_lastmod <= $'.count($params);
}
$sel = ExecSQLP('SELECT field_value, field_lastmod
FROM my_values
WHERE field_value ILIKE \'%\' || $1 || \'%\''
. $cond .
' ORDER BY node_id;'
, $params ));
It's not very sensible to ignore wrong number or parameters - in 99% of cases it would be a programming error.
If you would've used PDO for database connection you could use named parameters like this:
$cond = '';
$params = array( ':sel_find' => $_REQUEST['sel_find'] );
if ($_REQUEST['sel_when_a'] != '') {
$cond .= ' AND field_lastmod >= :sel_when_a';
$params[':sel_when_a'] = $_REQUEST['sel_when_a'];
}
if ($_REQUEST['sel_when_b'] != '') {
$cond .= ' AND field_lastmod <= :sel_when_b';
$params[':sel_when_b'] = $_REQUEST['sel_when_b'];
}
$sel = $dbh->prepare('SELECT field_value, field_lastmod
FROM my_values
WHERE field_value ILIKE \'%\' || :sel_find || \'%\''
. $cond .
' ORDER BY node_id;')->execute($params);

Related

posts_orderby not displaying the posts

I would like to customize my wordpress search page
First, i used the "posts_where" to modify the clause
function search_posts_where ($where) {
global $wp_query, $wpdb;
// Searching and not in admin
if (!is_admin() && $wp_query->is_search && isset($wp_query->query_vars['s'])) {
// Tables names
$post_title = "{$wpdb->prefix}posts.post_title";
$post_excerpt = "{$wpdb->prefix}posts.post_excerpt";
$post_content = "{$wpdb->prefix}posts.post_content";
$post_type = "{$wpdb->prefix}posts.post_type";
$post_status = "{$wpdb->prefix}posts.post_status";
$post_author = "{$wpdb->prefix}posts.post_author";
$post_ID = "{$wpdb->prefix}posts.ID";
$post_date = "{$wpdb->prefix}posts.post_date";
// Get the 's' parameters
$wp_query->query_vars['s'] ? $search_text = $wp_query->query_vars['s'] : $search_text = 'IS NULL';
// Write the where clause
$where = " AND ((($post_title LIKE '%$search_text%')";
$where .= " OR ($post_excerpt LIKE '%$search_text%')";
$where .= " OR ($post_content LIKE '%$search_text%')))";
$where .= " AND $post_type IN ('parcours')";
$where .= " AND $post_status = 'publish'";
$where .= " GROUP BY $post_ID";
}
return $where;
}
add_filter('posts_where', 'search_posts_where', 10, 2);
It works fine. All posts belonging to my custom post type 'parcours' are shown, depending on what I entered for the 's' query.
Second, i used the "posts_join" to add the meta table (not used yet !)
function search_posts_join ($join) {
global $wp_query, $wpdb;
// Searching and not in admin
if (!is_admin() && $wp_query->is_search && isset($wp_query->query_vars['s'])) {
// Tables names
$post_meta = "{$wpdb->prefix}postmeta";
$post_ID = "{$wpdb->prefix}posts.ID";
$post_meta_ID = "{$wpdb->prefix}postmeta.post_id";
// Join clause
$join .= "LEFT JOIN $post_meta ON ($post_ID = $post_meta_ID)";
}
return $join;
}
add_filter('posts_join', 'search_posts_join', 10, 2);
Still works perfectly !
Now the problem, i would like to order my posts in ascending direction (default is descending). So, i added the "posts_orderby" hook.
function search_posts_orderby ($orderby) {
global $wp_query, $wpdb;
// Searching and not in admin
if (!is_admin() && $wp_query->is_search) {
// Tables names
$post_title = "{$wpdb->prefix}posts.post_title";
$post_date = "{$wpdb->prefix}posts.post_date";
$post_ID = "{$wpdb->prefix}posts.ID";
// Order by clause
$orderby .= " ORDER BY $post_title ASC,";
$orderby .= " $post_date DESC";
}
return $orderby;
}
add_filter('posts_orderby', 'search_posts_orderby', 10, 2);
And here is the problem. All posts disapeared. Removing the "orderby" and they come back.
Looking at the SQL query, i have
"SELECT SQL_CALC_FOUND_ROWS wp_128444_posts.* FROM wp_128444_posts LEFT JOIN wp_128444_postmeta ON (wp_128444_posts.ID = wp_128444_postmeta.post_id) WHERE 1=1 AND (((wp_128444_posts.post_title LIKE '%tour%') OR (wp_128444_posts.post_excerpt LIKE '%tour%') OR (wp_128444_posts.post_content LIKE '%tour%'))) AND wp_128444_posts.post_type IN ('parcours') AND wp_128444_posts.post_status = 'publish' GROUP BY wp_128444_posts.ID ORDER BY wp_128444_posts.post_title LIKE '{5a35f6e9144541f93e08829126b2cb633436cebf95d774062fff749a12e6a465}tour{5a35f6e9144541f93e08829126b2cb633436cebf95d774062fff749a12e6a465}' DESC, wp_128444_posts.post_date DESC ORDER BY wp_128444_posts.post_title ASC, wp_128444_posts.post_date DESC LIMIT 0, 6"
I don't know why WP is adding the default ORDER BY, that i don't want.
Is is possible to remove it ?
I tried to replace my hook with "pre_get_posts" hook
function order_posts_by_title( $query ) {
if ( $query->is_search() && $query->is_main_query() ) {
$query->set( 'orderby', 'title' );
$query->set( 'order', 'ASC' );
}
}
add_action( 'pre_get_posts', 'order_posts_by_title' );
With this hook, it works !! Sounds very strange to me
Any explanation ?
Thanks

LINQ Query to search for users with partial name

I have this SQL query:
SELECT
*
FROM
employeeTable
WHERE
(
concat(first_name, ' ', last_name) like concat('%', replace(#MatchString, ' ', '%'), '%')
or
concat(last_name, ' ', first_name) like concat('%', replace(#MatchString, ' ', '%'), '%')
)
It works perfectly by searching both name and last name with partial values., by searching users with partial strings. So for example:
"ric jon" will find Rick Jones, Richard Jonesy, and Jonathan Prichter.
I have the following Linq Query, using Entity Framework:
from employee in context.Employee
where employee.first_name.Contains(matchString)
|| employee.last_name.Contains(matchString)
select employee
But the string "ric jon" does not find anything.
I can't seem to make that linq query work the same as the SQL query.
That SQL wouldn't in fact find those records you sampled with the given "ric jon". What you are asking for is a special filter which IMHO requires a function that is not natively supported by the backend (you didn't specify your backend but anyway at least I don't know a backend that would support this natively). Something like this would work (note that you are getting the Employee records to local, this is not done at backend level):
Func<Employee, string[], bool> match = (emp, _parts) => {
return
((emp.FirstName.IndexOf(_parts[0],StringComparison.CurrentCultureIgnoreCase ) != -1) &&
(_parts.Length == 1 || emp.LastName.IndexOf(_parts[1], StringComparison.CurrentCultureIgnoreCase) != -1)) ||
((_parts.Length == 1 || emp.FirstName.IndexOf(_parts[1], StringComparison.CurrentCultureIgnoreCase) != -1) &&
(emp.LastName.IndexOf(_parts[0], StringComparison.CurrentCultureIgnoreCase) != -1));
};
string search = "ric jon";
var result = context.Employee.AsEnumerable()
.Where(n => match(n, search.Split()));
Update:
You can use this one which would be supported by many backends:
string search = "ric jon";
string[] parts = search.ToLowerInvariant().Split();
string p1 = parts.Length < 1 ? "" :parts[0];
string p2 = parts.Length < 2 ? "" :parts[1];
var result = context.Employee.Where(n =>
((n.FirstName.ToLowerInvariant().Contains(p1) &&
n.LastName.ToLowerInvariant().Contains(p2))) ||
((n.FirstName.ToLowerInvariant().Contains(p2) &&
n.LastName.ToLowerInvariant().Contains(p1))));

How can I convert this to use PDO?

I would like to use PDO for selecting (searching) a database.
The search 'form' has MULTIPLE fields that can be used.. 1 or many can be filled in to help refine the search. (or there can be many o them left blank/empty)
here is what I have been using (locally):
//localhost details
$db_username="root"; //database user name
$db_password="";//database password
$db_database="test"; //database name
$db_host="localhost";
mysql_connect($db_host,$db_username,$db_password);
#mysql_select_db($db_database) or die("Unable to connect to database.");
if(isset($_POST['submit'])) {
// define the list of fields
$fields = array('first', 'trialdate', 'wcity', 'wstate', 'plantif');
$conditions = array();
//loop through the defined fields
foreach($fields as $field){
// if the field is set and not empty
if(isset($_POST[$field]) && $_POST[$field] != '') {
// create a new condition while escaping the value inputed by the user (SQL Injection)
$conditions[] = "`$field` LIKE '%" . mysql_real_escape_string($_POST[$field]) . "%'";
}
}
//build the query
$query = "SELECT * FROM myTable ";
// if there are conditions defined
if(count($conditions) > 0) {
// append the conditions
$query .= "WHERE " . implode (' OR ', $conditions); // you can change to 'OR', but I suggest to apply the filters cumulative
}
$result = mysql_query($query);
if(isset($_POST['submit'])) {
while($row = mysql_fetch_array($result)) {
echo $row['first'] . "<br />"; //individual value
//build panels that displays everything from row..etc
}
}
}
this has been working fine... but I'd like convert to using the PDO approach.
I gave it a few tries...but am missing something here..
heres what I've tried so far..
//localhost details
$db_username="root"; //database user name
$db_password="";//database password
$db_database="test"; //database name
$db_host="localhost";
//PDO DB connection
$conn = new PDO('mysql:host='.$db_host.'dbname='.$db_database.'charset=utf8', $db_username, $db_password);
if(isset($_POST['submit'])) {
$stmt = $conn->prepare('SELECT * FROM myTable WHERE first LIKE :first OR trialdate LIKE :trialdate OR wcity LIKE :wcity OR wstate LIKE :wstate OR plantif LIKE :plantif');
//build query placeholders (*note: use bindValue for $_POST values)
$stmt->bindValue(':first', '%' . $_POST['first'] . '%');
$stmt->bindValue(':trialdate', '%' . $_POST['trialdate'] . '%');
$stmt->bindValue(':wcity', '%' . $_POST['wcity'] . '%');
$stmt->bindValue(':wstate', '%' . $_POST['wstate'] . '%');
$stmt->bindValue(':plantif', '%' . $_POST['plantif'] . '%');
$stmt->execute();
foreach ($stmt as $row) {
// do something with $row
echo $row['first'] . "<br />"; //individual value
}
}
I could use help on getting the PDO example working with a displayed result/row/value?

perl called with 2 bind variables when 0 are needed

i have this code:
my (#matches, #vars);
if ($date_to) {
push(#matches, q{ m.mentioned_at <= ? });
push(#vars, $date_to);
}
if ($person) {
push(#matches, q{ t.name like ? });
push(#vars, $person);
}
if ($show) {
push(#matches, q{ t.name like ? });
push(#vars, $show);
}
if (#vars){
my $where = join (' and ', #matches);
$where = qq{where $where} if #matches
$res = $db->do({ page => $.p, per_page => $.s }, q{
select m.id, m.body, m.link,
count(distinct(m.id)) as num_items
from mentions m
$where
group by m.id, m.body, m.link,
order by m.created_at desc
}, #vars );
}
print STDERR Dumper ($where);
$VAR1 = 'where m.mentioned_at <= ? and t.name like ? ';`
print STDERR Dumper (#vars);
$VAR1 = '2012/06/01';
$VAR2 = 'Adby Phil';`
Why do i have this ERROR:
called with 2 bind variables when 0 are needed
Can't use an undefined value as an ARRAY reference
It seems like it does not see the $where clause.
I think you got the order of your parameters to do() switched:
$rv = $dbh->do($statement, \%attr, #bind_values);
Looks like you have \%attr first, then $statement.
Also, be aware that do() doesn't return a statement handle, so you can't fetch the data. You may want a more traditional prepare and execute sequence.

MYSQL: Limit Word Length for MySql Insert

every search query is saved in my database, but I want to Limit the Chracterlength for one single word: odisafuoiwerjsdkle --> length too much --> dont write in the database
my actually code is:
$search = $_GET['q'];
if (!($sql = mysql_query ('' . 'SELECT * FROM `history` WHERE `Query`=\'' . $search . '\''))) {
exit ('<b>SQL ERROR:</b> 102, Cannot write history.');
;
}
while ($row = mysql_fetch_array ($sql)) {
$ID = '' . $row['ID'];
}
if ($ID == '')
{
mysql_query ('' . 'INSERT INTO history (Query) values (\'' . $search . '\')');
}
if (!($sql = mysql_query ('SELECT * FROM `history` ORDER BY `ID` ASC LIMIT 1')))
{
exit ('<b>SQL ERROR:</b> 102, Cannot write history.');
;
}
while ($row = mysql_fetch_array ($sql)) {
$first_id = '' . $row['ID'];
}
if (!($sql = mysql_query ('SELECT * FROM `history`')))
{
exit ('<b>SQL ERROR:</b> 102, Cannot write history.');
;
}
One option would be using a trigger in the table. But, if you are expecting a lot of traffic on your search engine it might not scale very well. So, using client side (PHP in your case) constraints might be a better choice.