0

I look HERE and HERE before ask you but this solution doesn't work.
THIS is my table that I'm working: LIVE DEMO

I can filter records only using AND function and this is very limited

If you type never dream table returns me null results. But I want to see all rows with never OR dream word when I typing!

I want to change this PHP (ssp.class.php) script but I don't know how code must be edited. Replace AND with OR doesn't work...

class SSP {
/**
 * Create the data output array for the DataTables rows
 *
 *  @param  array $columns Column information array
 *  @param  array $data    Data from the SQL get
 *  @return array          Formatted data in a row based format
 */
static function data_output ( $columns, $data )
{
    $out = array();

    for ( $i=0, $ien=count($data) ; $i<$ien ; $i++ ) {
        $row = array();

        for ( $j=0, $jen=count($columns) ; $j<$jen ; $j++ ) {
            $column = $columns[$j];

            // Is there a formatter?
            if ( isset( $column['formatter'] ) ) {
                $row[ $column['dt'] ] = $column['formatter']( $data[$i][ $column['db'] ], $data[$i] );
            }
            else {
                $row[ $column['dt'] ] = $data[$i][ $columns[$j]['db'] ];
            }
        }

        $out[] = $row;
    }

    return $out;
}


/**
 * Database connection
 *
 * Obtain an PHP PDO connection from a connection details array
 *
 *  @param  array $conn SQL connection details. The array should have
 *    the following properties
 *     * host - host name
 *     * db   - database name
 *     * user - user name
 *     * pass - user password
 *  @return resource PDO connection
 */
static function db ( $conn )
{
    if ( is_array( $conn ) ) {
        return self::sql_connect( $conn );
    }

    return $conn;
}


/**
 * Paging
 *
 * Construct the LIMIT clause for server-side processing SQL query
 *
 *  @param  array $request Data sent to server by DataTables
 *  @param  array $columns Column information array
 *  @return string SQL limit clause
 */
static function limit ( $request, $columns )
{
    $limit = '';

    if ( isset($request['start']) && $request['length'] != -1 ) {
        $limit = "LIMIT ".intval($request['start']).", ".intval($request['length']);
    }

    return $limit;
}


/**
 * Ordering
 *
 * Construct the ORDER BY clause for server-side processing SQL query
 *
 *  @param  array $request Data sent to server by DataTables
 *  @param  array $columns Column information array
 *  @return string SQL order by clause
 */
static function order ( $request, $columns )
{
    $order = '';

    if ( isset($request['order']) && count($request['order']) ) {
        $orderBy = array();
        $dtColumns = self::pluck( $columns, 'dt' );

        for ( $i=0, $ien=count($request['order']) ; $i<$ien ; $i++ ) {
            // Convert the column index into the column data property
            $columnIdx = intval($request['order'][$i]['column']);
            $requestColumn = $request['columns'][$columnIdx];

            $columnIdx = array_search( $requestColumn['data'], $dtColumns );
            $column = $columns[ $columnIdx ];

            if ( $requestColumn['orderable'] == 'true' ) {
                $dir = $request['order'][$i]['dir'] === 'asc' ?
                    'ASC' :
                    'DESC';

                $orderBy[] = '`'.$column['db'].'` '.$dir;
            }
        }

        $order = 'ORDER BY '.implode(', ', $orderBy);
    }

    return $order;
}


/**
 * Searching / Filtering
 *
 * Construct the WHERE clause for server-side processing SQL query.
 *
 * NOTE this does not match the built-in DataTables filtering which does it
 * word by word on any field. It's possible to do here performance on large
 * databases would be very poor
 *
 *  @param  array $request Data sent to server by DataTables
 *  @param  array $columns Column information array
 *  @param  array $bindings Array of values for PDO bindings, used in the
 *    sql_exec() function
 *  @return string SQL where clause
 */

static function filter ( $request, $columns, &$bindings )
{
    $globalSearch = array();
    $columnSearch = array();
    $dtColumns = self::pluck( $columns, 'dt' );

    if ( isset($request['search']) && $request['search']['value'] != '' ) {
        $str = $request['search']['value'];

        for ( $i=0, $ien=count($request['columns']) ; $i<$ien ; $i++ ) {
            $requestColumn = $request['columns'][$i];
            $columnIdx = array_search( $requestColumn['data'], $dtColumns );
            $column = $columns[ $columnIdx ];

            if ( $requestColumn['searchable'] == 'true' ) {
                $binding = self::bind( $bindings, '%'.$str.'%', PDO::PARAM_STR );
                $globalSearch[] = "`".$column['db']."` LIKE ".$binding;
            }
        }
    }

    // Individual column filtering
    for ( $i=0, $ien=count($request['columns']) ; $i<$ien ; $i++ ) {
        $requestColumn = $request['columns'][$i];
        $columnIdx = array_search( $requestColumn['data'], $dtColumns );
        $column = $columns[ $columnIdx ];

        $str = $requestColumn['search']['value'];

        if ( $requestColumn['searchable'] == 'true' &&
         $str != '' ) {
            $binding = self::bind( $bindings, '%'.$str.'%', PDO::PARAM_STR );
            $columnSearch[] = "`".$column['db']."` LIKE ".$binding;
        }
    }

    // Combine the filters into a single string
    $where = '';

    if ( count( $globalSearch ) ) {
        $where = '('.implode(' OR ', $globalSearch).')';
    }

    if ( count( $columnSearch ) ) {
        $where = $where === '' ?
            implode(' AND ', $columnSearch) :
            $where .' AND '. implode(' AND ', $columnSearch);
    }

    if ( $where !== '' ) {
        $where = 'WHERE '.$where;
    }

    return $where;
}


/**
 * Perform the SQL queries needed for an server-side processing requested,
 * utilising the helper functions of this class, limit(), order() and
 * filter() among others. The returned array is ready to be encoded as JSON
 * in response to an SSP request, or can be modified if needed before
 * sending back to the client.
 *
 *  @param  array $request Data sent to server by DataTables
 *  @param  array|PDO $conn PDO connection resource or connection parameters array
 *  @param  string $table SQL table to query
 *  @param  string $primaryKey Primary key of the table
 *  @param  array $columns Column information array
 *  @return array          Server-side processing response array
 */
static function simple ( $request, $conn, $table, $primaryKey, $columns )
{
    $bindings = array();
    $db = self::db( $conn );

    // Build the SQL query string from the request
    $limit = self::limit( $request, $columns );
    $order = self::order( $request, $columns );
    $where = self::filter( $request, $columns, $bindings );

    // Main query to actually get the data
    $data = self::sql_exec( $db, $bindings,
        "SELECT SQL_CALC_FOUND_ROWS `".implode("`, `", self::pluck($columns, 'db'))."`
         FROM `$table`
         $where
         $order
         $limit"
    );

    // Data set length after filtering
    $resFilterLength = self::sql_exec( $db,
        "SELECT FOUND_ROWS()"
    );
    $recordsFiltered = $resFilterLength[0][0];

    // Total data set length
    $resTotalLength = self::sql_exec( $db,
        "SELECT COUNT(`{$primaryKey}`)
         FROM   `$table`"
    );
    $recordsTotal = $resTotalLength[0][0];


    /*
     * Output
     */
    return array(
        "draw"            => intval( $request['draw'] ),
        "recordsTotal"    => intval( $recordsTotal ),
        "recordsFiltered" => intval( $recordsFiltered ),
        "data"            => self::data_output( $columns, $data )
    );
}


/**
 * The difference between this method and the `simple` one, is that you can
 * apply additional `where` conditions to the SQL queries. These can be in
 * one of two forms:
 *
 * * 'Result condition' - This is applied to the result set, but not the
 *   overall paging information query - i.e. it will not effect the number
 *   of records that a user sees they can have access to. This should be
 *   used when you want apply a filtering condition that the user has sent.
 * * 'All condition' - This is applied to all queries that are made and
 *   reduces the number of records that the user can access. This should be
 *   used in conditions where you don't want the user to ever have access to
 *   particular records (for example, restricting by a login id).
 *
 *  @param  array $request Data sent to server by DataTables
 *  @param  array|PDO $conn PDO connection resource or connection parameters array
 *  @param  string $table SQL table to query
 *  @param  string $primaryKey Primary key of the table
 *  @param  array $columns Column information array
 *  @param  string $whereResult WHERE condition to apply to the result set
 *  @param  string $whereAll WHERE condition to apply to all queries
 *  @return array          Server-side processing response array
 */
static function complex ( $request, $conn, $table, $primaryKey, $columns, $whereResult=null, $whereAll=null )
{
    $bindings = array();
    $db = self::db( $conn );
    $localWhereResult = array();
    $localWhereAll = array();
    $whereAllSql = '';

    // Build the SQL query string from the request
    $limit = self::limit( $request, $columns );
    $order = self::order( $request, $columns );
    $where = self::filter( $request, $columns, $bindings );

    $whereResult = self::_flatten( $whereResult );
    $whereAll = self::_flatten( $whereAll );

    if ( $whereResult ) {
        $where = $where ?
            $where .' AND '.$whereResult :
            'WHERE '.$whereResult;
    }

    if ( $whereAll ) {
        $where = $where ?
            $where .' AND '.$whereAll :
            'WHERE '.$whereAll;

        $whereAllSql = 'WHERE '.$whereAll;
    }

    // Main query to actually get the data
    $data = self::sql_exec( $db, $bindings,
        "SELECT SQL_CALC_FOUND_ROWS `".implode("`, `", self::pluck($columns, 'db'))."`
         FROM `$table`
         $where
         $order
         $limit"
    );

    // Data set length after filtering
    $resFilterLength = self::sql_exec( $db,
        "SELECT FOUND_ROWS()"
    );
    $recordsFiltered = $resFilterLength[0][0];

    // Total data set length
    $resTotalLength = self::sql_exec( $db, $bindings,
        "SELECT COUNT(`{$primaryKey}`)
         FROM   `$table` ".
        $whereAllSql
    );
    $recordsTotal = $resTotalLength[0][0];

    /*
     * Output
     */
    return array(
        "draw"            => intval( $request['draw'] ),
        "recordsTotal"    => intval( $recordsTotal ),
        "recordsFiltered" => intval( $recordsFiltered ),
        "data"            => self::data_output( $columns, $data )
    );
}


/**
 * Connect to the database
 *
 * @param  array $sql_details SQL server connection details array, with the
 *   properties:
 *     * host - host name
 *     * db   - database name
 *     * user - user name
 *     * pass - user password
 * @return resource Database connection handle
 */
static function sql_connect ( $sql_details )
{
    try {
        $db = @new PDO(
            "mysql:host={$sql_details['host']};dbname={$sql_details['db']}",
            $sql_details['user'],
            $sql_details['pass'],
            array( PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION )
        );
    }
    catch (PDOException $e) {
        self::fatal(
            "An error occurred while connecting to the database. ".
            "The error reported by the server was: ".$e->getMessage()
        );
    }

    return $db;
}


/**
 * Execute an SQL query on the database
 *
 * @param  resource $db  Database handler
 * @param  array    $bindings Array of PDO binding values from bind() to be
 *   used for safely escaping strings. Note that this can be given as the
 *   SQL query string if no bindings are required.
 * @param  string   $sql SQL query to execute.
 * @return array         Result from the query (all rows)
 */
static function sql_exec ( $db, $bindings, $sql=null )
{
    // Argument shifting
    if ( $sql === null ) {
        $sql = $bindings;
    }

    $stmt = $db->prepare( $sql );
    //echo $sql;

    // Bind parameters
    if ( is_array( $bindings ) ) {
        for ( $i=0, $ien=count($bindings) ; $i<$ien ; $i++ ) {
            $binding = $bindings[$i];
            $stmt->bindValue( $binding['key'], $binding['val'], $binding['type'] );
        }
    }

    // Execute
    try {
        $stmt->execute();
    }
    catch (PDOException $e) {
        self::fatal( "An SQL error occurred: ".$e->getMessage() );
    }

    // Return all
    return $stmt->fetchAll();
}


/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
 * Internal methods
 */

/**
 * Throw a fatal error.
 *
 * This writes out an error message in a JSON string which DataTables will
 * see and show to the user in the browser.
 *
 * @param  string $msg Message to send to the client
 */
static function fatal ( $msg )
{
    echo json_encode( array( 
        "error" => $msg
    ) );

    exit(0);
}

/**
 * Create a PDO binding key which can be used for escaping variables safely
 * when executing a query with sql_exec()
 *
 * @param  array &$a    Array of bindings
 * @param  *      $val  Value to bind
 * @param  int    $type PDO field type
 * @return string       Bound key to be used in the SQL where this parameter
 *   would be used.
 */
static function bind ( &$a, $val, $type )
{
    $key = ':binding_'.count( $a );

    $a[] = array(
        'key' => $key,
        'val' => $val,
        'type' => $type
    );

    return $key;
}


/**
 * Pull a particular property from each assoc. array in a numeric array, 
 * returning and array of the property values from each item.
 *
 *  @param  array  $a    Array to get data from
 *  @param  string $prop Property to read
 *  @return array        Array of property values
 */
static function pluck ( $a, $prop )
{
    $out = array();

    for ( $i=0, $len=count($a) ; $i<$len ; $i++ ) {
        $out[] = $a[$i][$prop];
    }

    return $out;
}


/**
 * Return a string from an array or a string
 *
 * @param  array|string $a Array to join
 * @param  string $join Glue for the concatenation
 * @return string Joined string
 */
static function _flatten ( $a, $join = ' AND ' )
{
    if ( ! $a ) {
        return '';
    }
    else if ( $a && is_array($a) ) {
        return implode( $join, $a );
    }
    return $a;
}

}

---------------- UPDATED

I try to change original code

static function filter ( $request, $columns, &$bindings )
    {
            $globalSearch = array();
            $columnSearch = array();
            $dtColumns = self::pluck( $columns, 'dt' );

            if ( isset($request['search']) && $request['search']['value'] != '' ) {
                    $str = $request['search']['value'];

                    for ( $i=0, $ien=count($request['columns']) ; $i<$ien ; $i++ ) {
                            $requestColumn = $request['columns'][$i];
                            $columnIdx = array_search( $requestColumn['data'], $dtColumns );
                            $column = $columns[ $columnIdx ];

                            if ( $requestColumn['searchable'] == 'true' ) {
                                    $binding = self::bind( $bindings, '%'.$str.'%', PDO::PARAM_STR );
                                    $globalSearch[] = "`".$column['db']."` LIKE ".$binding;
                            }
                    }
            }

with Andriy edited code

static function filter ( $request, $columns, &$bindings )
    {
            $globalSearch = array();
            $columnSearch = array();
            $dtColumns = self::pluck( $columns, 'dt' );

            if ( isset($request['search']) && $request['search']['value'] != '' ) {
                    $str = $request['search']['value'];

                    for ( $i=0, $ien=count($request['columns']) ; $i<$ien ; $i++ ) {
                            $requestColumn = $request['columns'][$i];
                            $columnIdx = array_search( $requestColumn['data'], $dtColumns );
                            $column = $columns[ $columnIdx ];

                            if ( $requestColumn['searchable'] == 'true' )
                            $strArray = explode(' ',$str);
                            foreach ($strArray as $str)
                            {
                                    $binding = self::bind( $bindings, '%'.$str.'%', PDO::PARAM_STR );
                                    $globalSearch[] = "`".$column['db']."` LIKE ".$binding;
                            }
                    }
            }
user3520363
  • 173
  • 11

1 Answers1

2

in function filter()

$str is the variable you want to search.

So, you need to:

  1. Use separate search engine, like solr, elastic search, sphinx.

  2. Use mysql FULL TEXT search.

  3. Change:

    $binding = self::bind( $bindings, '%'.$str.'%', PDO::PARAM_STR ); $columnSearch[] = "".$column['db']." LIKE ".$binding;

to

$strArray = explode(' ',$str);
foreach ($strArray as $str) {
 $binding = self::bind( $bindings, '%'.$str.'%', PDO::PARAM_STR );
 $columnSearch[] = "`".$column['db']."` LIKE ".$binding;
}
Andriy
  • 867
  • 8
  • 13
  • I recreate table using MyISAM engine. Then I add fulltext seach using this query `ALTER TABLE ajax ADD FULLTEXT( engine, browser, platform, version, grade );` I try to changes Global Filter with your code correction but now if I if I type *sweet music* table returns me only *music* records (21 values) and not 36 records (sweet+music). Look if I edit code correctly: [link](http://pastebin.com/CAzh5uZg) – user3520363 Apr 16 '15 at 09:31
  • You should use FULL TEXT INDEX or change the code, not both at the same time. – Andriy Apr 16 '15 at 09:54
  • `ALTER TABLE ajax ADD FULLTEXT index(browser);` where browser is column name. I set this example for all 5 columns and I removed your edited code from ssp.class.php but nothing change. Now AND condition is returned.. – user3520363 Apr 16 '15 at 10:54
  • provide a query you use with FULL TEXT index. You changed LIKE operator, to MATCH/AGAINST operator? – Andriy Apr 16 '15 at 14:00
  • sorry for delay. In MySql I use this query successfully: `SELECT * FROM ajax WHERE MATCH(browser,engine) AGAINST('music sweet' in boolean mode);` *so I can see all rows with music and all rows with music word*. But problem is this: how can I implement this fulltext mode in my php script? Because I have text input like input method for my jquery table. There is a solution for implement into ssp.class.php – user3520363 Apr 16 '15 at 18:46
  • In function filter change $binding = self::bind( $bindings, '%'.$str.'%', PDO::PARAM_STR ); $globalSearch[] = "`".$column['db']."` LIKE ".$binding; to smth like $binding = self::bind( $bindings, $str, PDO::PARAM_STR ); $globalSearch[] = "match(`".$column['db']."`) against (".$binding.")"; – Andriy Apr 17 '15 at 06:30
  • good! now it works. But how is possible set fulltext index to 3 terms and not 4? – user3520363 Apr 17 '15 at 08:50
  • you mean match 3 words, and do not match one word? Add separate parameter to the query like: "AND column NOT LIKE '%value%'; – Andriy Apr 17 '15 at 09:07
  • No, sorry for my bad engish. It mean a a word of three letters. Full Index can't display me on table words like `fly`, `H3G` – user3520363 Apr 17 '15 at 09:19
  • maybe connected to stop words https://dev.mysql.com/doc/refman/5.5/en/fulltext-stopwords.html or server settings [mysqld] ft_min_word_len=3 (https://dev.mysql.com/doc/refman/5.5/en/fulltext-fine-tuning.html) – Andriy Apr 17 '15 at 10:02
  • Ok, thanks for info. There is another filter in my demo. You have solved for datatables filters but 3rd filter it can't work like datatables filter: is possible implement `fullindex` for entire sentences, with a variable number of words, in a row? Look my dilemma: [LINK](http://i.imgur.com/RhbNOdx.png) This filter is an external filter and not part of default ssp.class.php. In my demo your code solution `"match(".$column['db'].") against (".$binding.")";` splits EVERY word and returns a OR condition but I don't want FULLINDEX with splitting for this filter. Thanks for help. – user3520363 Apr 17 '15 at 12:13
  • you can play with weight coefficient, like in this solution: http://stackoverflow.com/questions/6259647/mysql-match-against-order-by-relevance-and-column but for matching group of words, is not the best solution to use FULL TEXT index. SO probably you need two separate functions, one with LIKE operator, as it was before (for sentence match), and one for each word match. You can even combine queries, using MySQL UNION operator. – Andriy Apr 17 '15 at 13:27
  • Thanks for tips. I ask you this because I want to replicate this *client solution* of filters: [CLIENT VERSION](http://datatables.altervista.org/yadcf_client/127.0.0.1/yadcf/dimensioni%20corrette%20importante%20anche%20css%20yadcf%20filter%20-%20css.html) Look a moment what I try to do for server side.Global filter works like an extended AND that match records on all columns. Individual column filter is a AND condition while yadcf filter works like a OR solution. Is possible to replicate or something in php solution I will miss? – user3520363 Apr 17 '15 at 14:43
  • Andry, can you help me with this similar question, please? Only you help me: http://stackoverflow.com/questions/29728752/how-set-and-condition-to-all-columns-php – user3520363 Apr 19 '15 at 16:38