Search for a value in where clause from Serialized Array - php

The title could be confusing, but I think my doubt is clear.
I'll explain. I'm doing this query:
$sql = 'SELECT * FROM calendar WHERE day = "'.$day.'" AND month = "'.$month.'" AND year = "'.$year.'" AND realizada = 0 AND colaborador = "What to put here?!"';
But the field "colaborador" is a serialized array.
One example, when I print_rthe value of my array after unserialize it's something like this:
Array ( [0] => l3gion [1] => Someone [2] => teste )
Imagine that I want to search for "l3gion" in the previous Query, how can I do this?
Thank you.
l3gion

If you need to query individual elements from your array, don't store the serialized array. Store each element on an individual row in a child table, and associate it with the primary key value of your calendar table:
CREATE TABLE colaboradoras (
calendar_id INT NOT NULL,
colaborador VARCHAR(20) NOT NULL,
FOREIGN KEY (calendar_id) REFERENCES calendar(calendar_id)
);
INSERT INTO colaboradoras VALUES
(1234, 'l3gion'),
(1234, 'Someone'),
(1234, 'teste');
$sql = "SELECT * FROM calendar AS c JOIN colaboradoras AS o
ON c.calendar_id = o.calendar_id
WHERE c.day = $day AND c.month = $month AND c.year = $year
AND c.realizada = 0 AND o.colaborador = 'l3gion'";
This is the normalized approach.
If you must store the serialized array, you might check out How FriendFeed Uses MySQL to index "schemaless" data. But that also involves creating new tables for the indexes.
If you really can't create any new tables or indexes, you can try to use LIKE or REGEXP but both of these solutions will be very inefficient and error-prone.
SELECT ... WHERE ... AND colaborador REGEXP '[[:<:]]s:6:\"l3gion\";'
You're screwed.

The short answer: Don't serialize data into a database field. That's what normalization is for...
The long answer is it's going to be VERY difficult to do. You could do a search for colaborador LIKE '%"13gion"%'... You could write a regex to deal with it as well (And use MySQL's REGEXP expression)...
But the best solution is to not store serialized data in the database...

colaborador = "What to put here?!"
This code will deal with serialized and raw data, as well as array and plain string values.
if (is_string($colaborador)) {
// prevent errors from showing
ob_start();
$data = unserialize($colaborador);
// $data is now false or output buffer is non-empty if $colaborador was
// not serialized
$data = ob_get_length() > 0 || false === $data ? $colaborador : $data;
// lose the possible error
ob_end_clean();
} else {
$data = $colaborador;
}
// use the first value as colaborador if an array
$sql = 'SELECT * FROM calendar WHERE day = "'.$day.'" AND month = "'.$month.'"
AND year = "'.$year.'" AND realizada = 0 AND colaborador = \'' .
(is_array($data) ? (empty($data) ? '' : $data[0]) : $data) . '\'';`
Or if you want to search all of them:
$sql = 'SELECT * FROM calendar WHERE day = "'.$day.'" AND month = "'.$month.'"
AND year = "'.$year.'" AND realizada = 0 AND colaborador = IN (\'' .
(is_array($data) ? (empty($data) ? '' : implode("', '", $data[0])) : $data) . '\')';`

Related

Get last number from unique ID from database SQL - PHP CodeIgniter

scroll down for the answer
I have Registration table like this:
table
Now I'm hopping to get the last digit, for example:
SG20160412001 >> 1
SG20160412056 >> 56
SG20160412121 >> 121
The purpose is for generating new Reg_ID, for example:
If the returned data is = 1 then the new Reg_ID will be SG20160412002
If the returned data is = 56 then the new Reg_ID will be SG20160412057
If the returned data is = 0 then the new reg_ID will be SG20160412001
Here's my current code:
public function generate_no_reg($nmNegara)
{
$dtNow = date('Y-m-d');
$query = 'SELECT COUNT(Reg)ID) FROM Registratrion WHERE Reg_Date="$dtNow"';
$lastid = $this->db->query($query);
$id = $lastid->num_rows()+1;
$regCount = format_no_registrasi($id);
$kode = $this->get_negara($nmNegara).$regCount;
return $kode;
}
And here's my current code for generating the last 3 digit:
function format_no_registrasi($no)
{
$leadingzeros = '000';
$no_reg = date('Y') . date('m') . substr($leadingzeros, 0, (-strlen($no))) . $no+1;
return $no_reg;
}
The problem I'm having from this code is:
When i delete a row, the Primary Key will be doubled, example:
SG20160412001 << I delete this one
SG20160412002
Now the data count is returning 1, and when I generate new one it will be: SG20160412002
Any help is very much appreciated. Sorry for the problem.
scroll down for the answer
This query cuts 3 last signs from Reg_ID and returns max value.
select max(cast(substr(Reg_ID, -3) as UNSIGNED))
from FROM Registratrion
WHERE Reg_Date="$dtNow"
So id numeration supports ids till 999
Max' solution is much cleaner.
Don't use the count, use the highest number.
Create the leading characters of the Reg_ID
$leadingCharacters = 'SG' + date('Ymd');
Then select the hightes Reg_ID that starts with this string:
select Reg_ID from Registration where Reg_ID like '$leadingCharacters%' order by Reg_ID desc limit 0,1
Then extract the last three digits from the result, convert it to int and increment it. This will be your next ID.
$number = (int) substr($result,-3);
$number++;
NOTICE: Typed the code from memory, so there might be a little error in it. But the concept should work though
Have you considered splitting out your reg_id into three columns?
prefix CHAR(2)
reg_date DATE
postfix SMALLINT UNSIGNED
You can use a surrogate or composite PRIMARY if you need and concat the columns on select:
SELECT CONCAT(prefix, reg_date, LPAD(post_fix,3,'0')) reg_id
FROM registration
Keeping separate data in separate fields saves a lot of headache with operations like this.
My answer is helped by Markus and Max
This is the function that i used for leading characters, since the 'SG' is from database:
public function get_negara($nmNegara)
{
$this->db->select('Kode_Negara');
$this->db->from('tb_negara_tujuan');
$this->db->where('Nama_Negara',$nmNegara);
$query = $this->db->get();
$result = $query->row();
return $result->Kode_Negara . date('Ymd');
}
// this will returning 'SG20160413'
This is the helper function used to convert 1 to 001:
function format_no_registrasi($no)
{
$leadingzeros = '000';
$no_reg = substr($leadingzeros, 0, (-strlen($no))) . $no;
return $no_reg;
}
This is the function used to generate the full Reg_ID:
public function generate_no_reg($nmNegara)
{
$leadingChar = $this->get_negara($nmNegara);
$this->db->select_max('No_Registrasi');
$this->db->like('No_Registrasi',$leadingChar);
$query = $this->db->get('tb_registrasi');
$result = $query->row();
$noreg = (int) substr($result->No_Registrasi,-3);
$noreg++;
return $kode = $leadingChar.format_no_registrasi($noreg);
}

convert mysql result to json with correct types

I know how to get a mysql-row and convert it to json:
$row = mysqli_fetch_assoc(mysqli_query($db, "SELECT * FROM table WHERE id=1"));
echo json_encode($row); // it's an ajax-call
but:
the db-row has different types like int, float, string.
by converting it using json_encode() all results are strings.
Is there a better way to correct the types than this:
$row['floatvalue1'] = 0+$row['floatvalue1'];
$row['floatvalue2'] = 0+$row['floatvalue2'];
$row['intvalue1'] = 0+$row['intvalue1'];
I would like to loop through the keys and add 0 because:
first coding rule: DRY - dont repeat yourself
but i can't because:
row has also other types than numbers (string, date)
there are many columns
design is in dev, so columns-names often changes
Thanks in advance and excuse my bad english :-)
EDIT (to answer the comment-question from Casimir et Hippolyte):
I call this php-code using ajax to get dynamically sql-values. in my javascript-code i use the results like this:
result['intvalue1'] += 100;
lets say the json-result of intval1 is 50, the calculated result is:
"50100", not 150
The code below is just a proof of concept. It needs encapsulation in a function/method and some polishing before using it in production (f.e. call mysqli_fetch_field() in a loop and store the objects it returns before processing any row, not once for every row).
It uses the function mysqli_fetch_field() to get information about each column of the result set and converts to numbers those columns that have numeric types. The values of MYSQLI_TYPE_* constants can be found in the documentation page of Mysqli predefined constants.
// Get the data
$result = mysqli_query($db, "SELECT * FROM table WHERE id=1");
$row = mysqli_fetch_assoc($result);
// Fix the types
$fixed = array();
foreach ($row as $key => $value) {
$info = mysqli_fetch_field($result);
if (in_array($info->type, array(
MYSQLI_TYPE_TINY, MYSQLI_TYPE_SHORT, MYSQLI_TYPE_INT24,
MYSQLI_TYPE_LONG, MYSQLI_TYPE_LONGLONG,
MYSQLI_TYPE_DECIMAL,
MYSQLI_TYPE_FLOAT, MYSQLI_TYPE_DOUBLE
))) {
$fixed[$key] = 0 + $value;
} else {
$fixed[$key] = $value;
}
}
// Compare the results
echo('all strings: '.json_encode($row)."\n");
echo('fixed types: '.json_encode($fixed)."\n");
something like
$row['floatvalue1'] = reset( sscanf ( $row['floatvalue1'] , "%f" ));
$row['floatvalue2'] = reset( sscanf ( $row['floatvalue2'] , "%f" ));
$row['intvalue1'] = reset( sscanf ( $row['intvalue1'] , "%d" ));
json_encode($row);
If you're simply trying to make sure that your values are operable with respect to their type, you need to first cast their type correctly.
Unless you need them server-side, I would just pass-on the json directly to the front-end and do the work there.
In Javascript, you could make an attempt at casting the numbers like so:
function tryNumber(string){
return !isNaN( parseInt(string) ) ? parseInt(string) : string;
}
function tryDate(string){
return !isNaN( new Date(string).getTime() ) ? new Date(string) : string;
}
tryNumber('foo'); // "hello"
tryNumber('24'); // 24
tryDate('bar'); // "bar"
tryDate('December 17, 1995'); // "Sun Dec 17 1995 00:00:00 GMT+0000 (GMT)"
These two lines attempt to cast the values as a Date/Number. If they can't be cast, they will remain String's.
A MySQLi OO version based on #axiac's answer, that produces a JSON array ($jsnAll) containing all records. In this code snippet, the method FixSQLType is called to fix a row. Note, it should be wrapped in a try{}catch{} block and "objMySQLi" has already been instantiated:
$lcAllRows = array();
// Make an SQL SELECT statement
$SQL = "SELECT * FROM $lcName WHERE $lcWhere";
// Run the query
$this->sqlResult = $this->objMySQLi->query($SQL);
// Fetch the result
while( $row = $this->sqlResult->fetch_assoc()){
$lcCount = count($lcAllRows) ;
// Call to fix, row
$fixedRow = $this->FixSQLType($row);
$lcAllRows[$lcCount]= $fixedRow;
}
$jsnAll = json_encode($lcAllRows);
The FixSQLType method. This is almost identical to #axiac's answer, except for the call to $this->sqlResult->fetch_field_direct($i). "fetch_field" seemed to get itself lost, using "fetch_field_direct" overcame that problem.
private function FixSQLType($pRow){
// FROM https://stackoverflow.com/a/28261996/7571029
// Fix the types
$fixed = array();
$i = 0;
foreach ($pRow as $key => $value) {
$info = $this->sqlResult->fetch_field_direct($i);
$i++;
if (in_array($info->type, array(
MYSQLI_TYPE_TINY, MYSQLI_TYPE_SHORT, MYSQLI_TYPE_INT24,
MYSQLI_TYPE_LONG, MYSQLI_TYPE_LONGLONG,
MYSQLI_TYPE_DECIMAL,
MYSQLI_TYPE_FLOAT, MYSQLI_TYPE_DOUBLE
))) {
$fixed[$key] = 0 + $value;
} else {
$fixed[$key] = $value;
}
}
return $fixed;
}

Need some logic with PHP to loop through some mysql db results

I have the following results returned via a mysql query:
tag_id article_id (asc) name tag_slug
56 69487 Exploration exploration
10 69488 Events events
32 69488 Military military
28 69489 Arts arts
3 69489 Religion churches
36 69490 Forestry forestry
8 69490 Industry industry
40 69490 Man-Made man-made
42 69490 Politics politics
I need to loop through the results and create a string that would include the tag_slugs associated with each set of article id's. The name and tag_id columns aren't necessary for this part of the code.
For example ...
69487 would have a string that is: '<exploration>'
69488 would have a string that is: '<events><military>'
69489 would have a string that is: '<arts><churches>'
69490 would have a string that is: '<forestry><industry><man-made><politics>'
...and a column named tags would be updated in the db with these strings and the respective article_id.
My attempt below works, kind of, but always leaves off the last db update. I'm sure there has to be a better more logical way but right now I cannot.
$previous_article_id = 0;
while( $row = $result->fetch_assoc() )
{
if ( $row['article_id '] != $previous_article_id && $previous_article_id != 0 )
{
$sql = "
UPDATE
".ARTICLE_TABLE."
SET
tags = '".$tags."'
WHERE
id = ".$previous_article_id."
";
$db->query($sql) OR sql_error($db->error.$sql);
$tags = '';
}
if ( $row['article_id '] == $previous_article_id || $previous_article_id == 0 )
{
$tags .= '<'.$row['tag_slug'].'>';
}
$previous_article_id = $row['article_id '];
}
Yes, I know it should be PDO and the codes a bit crazy, but I'm working on a friends brothers website so not much authority to change it.
First of all, my compliments for how you have explained your problem and what you have tried. Your code is also well formatted. I often see some crap posts here. So my compliments.
I should change the whole stuff to the following:
$article_tags = array();
while( $row = $result->fetch_assoc() )
{
$article_tags[$row['article_id']][] = $row['tag_slug'];
}
foreach( $article_tags as $article_id => $tag_array )
{
$tags = '';
foreach( $tag_array as $tag )
{
$tags .= '<' . $tag . '>';
}
$sql = "
UPDATE
".ARTICLE_TABLE."
SET
tags = '".$tags."'
WHERE
id = ".$article_id."
";
$db->query($sql) OR sql_error($db->error.$sql);
}
Maybe the cleaner way to do that with PHP is to create an associative array having your article_ids as keys and the string containing the related tags as values.
Eg.
$myArray[$row['article_id ']].='<'.$row['tag_slug'].'>';
Then, at the end you just iterate with a foreach loop over it and insert the data in your database.
You can more easily, in my opinion,fetch this results from the database by using GROUP_CONCAT function
SELECT article_id, CONCAT('<',GROUP_CONCAT(tag_slug separator '><'),'>')
from table GROUP BY article_id
ORDER BY article_id DESC
In case you have duplicates on your tag_slug you can use DISTINCT operator inside GROUP_CONCAT function

SQL Query not completing correctly - not sure why

Alright,
I've got a multiple select dropdown on a page called week-select, its selections get passed via ajax to my php page.
I can get the data just fine, but when the query runs it doesn't complete appropriately.
I've got this:
//Deal with Week Array
$weekFilter = $_GET['week']; /*This is fine, if it's 1 week the query works great (weeks are numbered 12-15), but if it is 2 weeks the result is formatted like this 12-13 or 13-14-15 or whichever weeks are selected*/
$weekFilter = str_replace("-",",",$weekFilter); /*This works to make it a comma separated list*/
.../*I deal with other variables here, they work fine*/
if ($weekFilter) {
$sql[] = " WK IN ( ? ) ";
$sqlarr[] = $weekFilter;
}
$query = "SELECT * FROM $tableName";
if (!empty($sql)) {
$query .= ' WHERE ' . implode(' AND ', $sql);
}
$stmt = $DBH->prepare($query);
$stmt->execute($sqlarr);
$finalarray = array();
$count = $stmt->rowCount();
$finalarray['count'] = $count;
if ($count > 0) { //Check to make sure there are results
while ($result = $stmt->fetchAll()) { //If there are results - go through each one and add it to the json
$finalarray['rowdata'] = $result;
} //end While
}else if ($count == 0) { //if there are no results - set the json object to null
$emptyResult = array();
$emptyResult = "null";
$finalarray['rowdata'] = $emptyResult;
} //end if no results
If I just select one week it works great and displays the appropriate data.
If I select multiple options (say weeks 12, 14 and 15) it runs the query but only displays week 12.
When I manually input the query in SQL, how I imagine this query is getting entered - it runs and displays the appropriate data. So if I put SELECT * FROM mytablename WHERE WK IN ( 12, 14, 15 ) it gets exactly what I want.
I can't figure out why my query isn't executing properly here.
Any ideas?
**EDIT: I make the array from the multiple selections a string using javascript on the front end before it is passed to the backend.
Your resulting query with values probably looks like this with a single value in IN:
… WK IN ("12,14,15") …
Either use one placeholder for each atomic value:
if ($weekFilter) {
$values = explode(",", $weekFilter);
$sql[] = " WK IN ( " . implode(",", array_fill(0, count($values), "?")) . " ) ";
$sqlarr = array_merge($sqlarr, $values);
}
Or use FIND_IN_SET instead of IN:
$sql[] = " FIND_IN_SET(WK, ?) ";
I don't think you can bind an array to a singular ? placeholder. Usually you have to put in as many ? values as there are elements in your array.
If your HTML is correct and your week select has name="week[]", then you will get an array back with $_GET['week'];, otherwise without the [] it will only give you 1 value. Then, you're doing a string replace, but it's not a string. Instead, try this:
$weekFilter = implode(',', $_GET['week']);

Specifying conditions on database results to array

In my database I have a field called "modules" - info data looks like this: 1, 4, 1, 3
I want to list/output all data via PHP with the numbers 1 - all other numbers have to be ignored.
I want to check the output result via an array NOT via mySQL
Any suggestion how I can do that?
$list_modules = array();
$res_m = $db->Execute("SELECT modules FROM users u WHERE user_id = '".$user->id."'");
while ( $m = $res_m->GetNext() ) {
$list_modules = array($m['modules']);
}
print_r($list_modules); //Output below
Example (Output):
Array
(
[0] => 1, 4, 1, 3
)
You can do that in you MySQL with a WHERE clause. Depending on the column name in the database:
SELECT column_name FROM table WHERE column_name = 1;
Note: In your question it looks like you tried to list a range:
with the numbers 1 - all other numbers have to be ignored.
If you meant to put a range (e.g. 1 - 4) then your WHERE clause would be:
WHERE column_name BETWEEN 1 AND 4
You should test for that in your MySQL query:
SELECT * FROM `TABLE` WHERE `modules` = 1;
Or, alternatively, if that's not possible..
Loop with foreach and test for 1?
$array = array(1,4,1,3);
foreach ($array as $element) {
if ($element == 1) { echo 1; }
}
This should do it... there is really no other way seeing at the col is Varchar, you also need to eliminate strings like 15, 21, etc. so %1% will not work.
SELECT modules FROM users WHERE user_id = ".$user->id." AND modules LIKE % 1,%
Give it a shot and let me know if it works.
You stated in your comment that the modules field contains comma-separated values, is that right? I reckon that the modules field is VARCHAR, CHAR, or any other string. If so, you could use a query like:
SELECT * FROM `tableName` WHERE `modules` LIKE '%1,%';
There may be other solutions, probably more optimal, but this one should perform well, I think.
Meh found the solution I could use.
$list_modules = array();
$query = "SELECT modules FROM users WHERE user_id = ".$user->id."";
$res_m = $db->Execute($query);
while ( $m = $res_m->GetNext() ) {
$list_modules = array('id' => $m['modules']);
}
$modules = explode(",",$list_modules['id']);
foreach ($modules as $key => $value) {
if($value == 1){
// list data
}
}
}

Categories