WordPress query, meta_query, compare BETWEEN, no results - mysql

I have code for my filter. It worked well until I add new product in my database. I found the problem, but dont know what to do with that.
I have parameters "alc_min" and "alc_max" in my filter. I get these from crawling all products. After I send this filter, I fire this code:
$meta_query = array();
$b = "alc_min";
$c = "alc_max";
if (isset ( $data [$b] ) && isset ( $data [$c] )) {
$compare = "BETWEEN";
$a = array (
'key' => "alc",
'value' => array (
$data [$b],
$data [$c]
),
'compare' => $compare
);
array_push ( $meta_query, $a );
}
$items = new WP_Query ( array (
'post_type' => $type,
'posts_per_page' => $posts_per_page,
'order' => $order,
'meta_key' => $orderkey,
'orderby' => $orderby,
'post_status' => 'publish',
'meta_query' => $meta_query,
'paged' => $paged
) );
Until now, it worked well. No I add new product with "alc" <10 and I found, that if I have "alc_min" and "alc_max" <10 or >10, it is ok. But if "alc_min" is <10 and "alc_max" >10 I get no results at all.
Does anyone any idea what to check or fix?

After the clarification, I've suspected that the reason why selecting "alc_min" = 7 and "alc_max" = 13 doesn't yield any result is because of the column datatype. Consider this example:
CREATE TABLE table1 (
alc VARCHAR(50));
INSERT INTO table1 VALUES
('7'),
('9'),
('11'),
('13');
The table above is created with alc column datatype as VARCHAR instead of INTEGER (or numeric datatype). I've tested that running either one of the query below:
SELECT * FROM table1 WHERE alc BETWEEN '7' AND '9';
SELECT * FROM table1 WHERE alc BETWEEN '11' AND '13';
will return the expected result. However, with this query:
SELECT * FROM table1 WHERE alc BETWEEN '7' AND '13';
yields no result. This is because the values are treated as string instead of numbers and when that happens, 1 is always smaller than 7. See below what happen you run select query with order by on the data set above:
SELECT * FROM table1 ORDER BY alc;
+-----+
| alc |
+-----+
| 11 |
| 13 |
| 7 |
| 9 |
+-----+
As you can see, since the data is treated as string (according to the column datatype), then you could imagine this in alphabetical form as the following:
+-----+--------------+
| alc | alphabetical |
+-----+--------------+
| 11 | AA |
| 13 | AC |
| 7 | G |
| 9 | I |
+-----+--------------+
So, the condition of BETWEEN '7' AND '13' becomes BETWEEN 'G' AND 'AC'; which doesn't really make sense. And if you change to BETWEEN '11' AND '9' you'll get the correct result but that made the query even more confusing and not making sense at all.
Now, I've discovered that there are at least 3 workaround/solution for this:
One of the oldest way I can think of is by adding +0 to the column in the query. I didn't find any official docs about this but I assume that doing this will change the data value to numeric in the query:
SELECT * FROM table1
WHERE alc+0 BETWEEN '7' AND '13';
This is probably the same as above is just that I'm not sure if this is version specific or not. It turns out that in my testing, if you didn't wrap the searched value in quotes, you'll get the result as if the data is numeric:
SELECT * FROM table1
WHERE alc BETWEEN 7 AND 13;
This require a change of column datatype but afterwards any of the query with or without quotes on the searched value should work:
ALTER TABLE table1 CHANGE alc alc INT;
I hope that this is true and the issue is really about column datatype. As far as I know, this is the closest thing to what your situation is that I had experience with.
Here's a fiddle for reference

Related

SQL query needed for a complex structure

I have a tricky SQL query that needs to be built to get the highest priority rule based on customer session and geo IP data.
I attached the following tables: rule, rule_attribute, rule_attribute_value.
rule - table where all rules are stored
Click here to see a screenshot of the 'rule' table
rule_attribute - table where all rule attributes are stored
Click here to see a screenshot of the 'rule_attribute' table
rule_attribute_value - table where all rule attribute values are stored
Click here to see a screenshot of the 'rule_attribute_value' table
When the customer logs in, I have access to all those attributes (customer_id, customer_group_id, country_id, subdivision_one_id, subdivision_two_id). Only customer_id and customer_group_id will always have values. The others are optional, but there is a dependency between them. We can't have subdivisions without selecting first a country. We can have a second subdivision without selecting a country and then the first subdivision.
What I would like to get is the highest priority rule that matches the session data in the most optimized way. I have a solution that involves some coding, but I want to see if it's possible directly through SQL.
Here are some examples of session data arrays:
Array
(
[customer_id] => 2
[customer_group_id] => 1
[current_store_id] => 0
[country_id] => 15
[subdivision_one_id] => 224
[subdivision_two_id] =>
)
Array
(
[customer_id] => 2
[customer_group_id] => 1
[current_store_id] => 0
[country_id] => 15
[subdivision_one_id] =>
[subdivision_two_id] =>
)
Array
(
[customer_id] => 3
[customer_group_id] => 2
[current_store_id] => 0
[country_id] =>
[subdivision_one_id] =>
[subdivision_two_id] =>
)
Without a better understanding of the rules and data this is the best I can come up with. It is based on your first array example -
SELECT `r`.*
FROM `rule_attribute_value` `rav`
INNER JOIN `rule` `r`
ON `rav`.`rule_id` = `r`.`rule_id`
INNER JOIN `rule_attribute` `ra`
ON `rav`.`attribute_id` = `ra`.`attribute_id`
WHERE
(`rav`.`store_id` = 0 AND `ra`.`attribute_code` = 'customer' AND `rav`.`value` = 2) OR
(`rav`.`store_id` = 0 AND `ra`.`attribute_code` = 'customer_group' AND `rav`.`value` = 1) OR
(`rav`.`store_id` = 0 AND `ra`.`attribute_code` = 'country' AND `rav`.`value` = 15) OR
(`rav`.`store_id` = 0 AND `ra`.`attribute_code` = 'subdivision_one' AND `rav`.`value` = 224)
GROUP BY `r`.`rule_id`
HAVING COUNT(DISTINCT `rav`.`attribute_id`) = 4 /* 4 IS THE NUMBER OF ATTRIBUTES BEING QUERIED */
ORDER BY `r`.`position` ASC
LIMIT 1;

Perl DBI: uneven-number of bind variables with OR Statement (called with x bind variables when y are needed)

Definition of task:
Fetch data from two different columns using OR.
Problem:
While its working with the plain (MySQL) query, Perl DBI throws an exception due to the uneven-number of bind variables.
Let's assume the following DB schema:
customer vpn_primary_ip vpn_secondary_ip
1000 1.1.1.1 2.2.2.2
1001 3.3.3.3 NULL
1002 4.4.4.4 5.5.5.5
1003 NULL 6.6.6.6
Side note:
As the column where the ip address is stored is not predictable, I combine the search for the columns vpn_primary_ip AND vpn_secondary_ip using the OR operator. The plain SQL query is as follows:
SELECT
customer,
vpn_primary_ip,
vpn_secondary_ip,
FROM
table
WHERE
vpn_primary_ip IN ( '1.1.1.1', '4.4.4.4', '5.5.5.5', '6.6.6.6' )
OR
vpn_secondary_ip IN ( '1.1.1.1', '4.4.4.4', '5.5.5.5', '6.6.6.6' );
The query above gives the following (appropriate) result:
+----------+-----------------+------------------+
| customer | vpn_primary_ip | vpn_secondary_ip |
+----------+-----------------+------------------+
| 1000 | 1.1.1.1 | 2.2.2.2 |
| 1002 | 4.4.4.4 | 5.5.5.5 |
| 1003 | NULL | 6.6.6.6 |
+----------+-----------------+------------------+
The same SQL query with Perl DBI:
my #ip_addresses = ('1.1.1.1', '4.4.4.4', '5.5.5.5', '6.6.6.6');
my $sth = $dbh->prepare (
"SELECT
customer,
vpn_primary_ip,
vpn_secondary_ip,
FROM
table
WHERE
vpn_primary_ip IN ( #{[join',', ('?') x #ip_addresses]} )
OR
vpn_secondary_ip IN ( #{[join',', ('?') x #ip_addresses]} )"
);
$sth->execute(#ip_addresses);
Throws the following exception:
DBD::mysql::st execute failed: called with 4 bind variables when 8 are needed at get_vpn_customers line 211, <DATA> line 1.
DBD::mysql::st execute failed: called with 4 bind variables when 8 are needed at get_vpn_customers line 211, <DATA> line 1.
The only idea to make it work, is to pass #ip_addresses to the execute method twice:
$sth->execute(#ip_addresses, #ip_addresses);
Question:
Is this the proper approach or is there another, let's say, best or better practice?
$sth->execute(#ip_addresses, #ip_addresses);
This is the correct approach. All DBI knows is that you have passed it an SQL query containing eight bind points. It, therefore, needs eight matching values passed to the execute() method.
There is no way for Perl, DBI or MySQL to know that the bind values are repeated.
Other possible solution is to massage SQL query to workable state before $sth->execute()
use strict;
use warnings;
use feature 'say';
my #ip_addresses = ('1.1.1.1', '4.4.4.4', '5.5.5.5', '6.6.6.6');
my $query = "
SELECT
customer,
vpn_primary_ip,
vpn_secondary_ip,
FROM
table
WHERE
vpn_primary_ip IN ( #{[join',', ('?') x #ip_addresses]} )
OR
vpn_secondary_ip IN ( #{[join',', ('?') x #ip_addresses]} )
";
say $query;
my $ip_addresses;
my $flag = 0;
for (#ip_addresses) {
$ip_addresses .= ', ' if $flag;
$ip_addresses .= "'$_'";
$flag = 1;
}
$query = "
SELECT
customer,
vpn_primary_ip,
vpn_secondary_ip,
FROM
table
WHERE
vpn_primary_ip IN ( $ip_addresses )
OR
vpn_secondary_ip IN ( $ip_addresses )
";
say $query;
Output
SELECT
customer,
vpn_primary_ip,
vpn_secondary_ip,
FROM
table
WHERE
vpn_primary_ip IN ( ?,?,?,? )
OR
vpn_secondary_ip IN ( ?,?,?,? )
SELECT
customer,
vpn_primary_ip,
vpn_secondary_ip,
FROM
table
WHERE
vpn_primary_ip IN ( '1.1.1.1', '4.4.4.4', '5.5.5.5', '6.6.6.6' )
OR
vpn_secondary_ip IN ( '1.1.1.1', '4.4.4.4', '5.5.5.5', '6.6.6.6' )

search for text within string in laravel or mysql

I use my SQL as backend for my project, and I need to get all record from database where some part of inputted string is available in database string like:
table = seller
id company_name seller_name
1 companyname1 seller1
2 companyname2 seller2
3 companyname3 seller3
4 companyname4 seller4
Given string is 1105 companyname1 is outstanding
So i need to get id = 1 for a given string if it is possible with laravel or MySQL then please help me.
You can construct a query using like:
where $YourString like concat('%', companyname, '%')
Note: There are situations where one company name might be sufficiently like another ("company1" and "company10"). If this is an issue, regular expressions might help.
First, you have to convert that string to an array.
$str = "1105 companyname1 is outstanding";
$str = explode(' ' , $str);
$results = Seller::whereIn('company_name' , $str)->get();
Note:
As you are converting random strings to an array, there will be a mistake where the user input some more spaces.
Let say the user input a string like $str = "1105 companyname1 is outstanding"; this will create some more elements. Like
array(
0 => '1104',
1 => '',
2 => 'companyname1',
3 => 'is',
4 => 'outstanding'
)
So, to avoid that, I have to recommend you to split out some more spaces. You can do by
$str = preg_split('/\s+/', $string, -1, PREG_SPLIT_NO_EMPTY);

SELECT count(*) with where produces weird values

It seems when I am trying to make a query to get the users transaction sum, it does not return the proper value until I remove the filter on code, what is even more interesting is the filter on the bar code following works perfectly fine, it seems maybe there is an inconsistency between both the subqueries in the WHERE clause?
Explanation:
With the below query, when I remove the and code != "foo" AND code !="foobar" from the query, it returns the correct value, but I also tried changing it to code = "foo" or code = "foobar" to check if any of the results had these codes, and it returns null when I do this.
SELECT SUM(t.amount)
FROM transactions t
WHERE `t`.`deleted_at` IS NULL
AND `t`.`user_id` = 80
AND `t`.`user_id` IS NOT NULL
AND `manually_deleted_at` IS NULL
AND
(SELECT count(*)
FROM `transaction_subcategories` s
WHERE `t`.`transaction_subcategory_id` = `s`.`id`
AND `code` != "foo"
AND `code` != "foobar"
AND
(SELECT count(*)
FROM `transaction_categories` c
INNER JOIN `transaction_categories_transaction_subcategories` sc ON `c`.`id` = `sc`.`transaction_category_id`
WHERE `sc`.`transaction_subcategory_id` = `s`.`id`
AND `code` = "bar") >= 1) >= 1
AND `posted_date` BETWEEN "2016-04-01 00:00:00.000000" AND "2017-03-31 23:59:59.000000"
AND `parent_id` = 0;
While I do realize this is a mysql query issue, the laravel ORM code is a bit cleaner:
$income_transactions = \Auth::user ()->transactions ()
->notManuallyDeleted()
->whereHas('transactionSubcategory', function ($query) {
$query
->where('code', '!=', 'foo')
->where('code', '!=', 'foobar')
->whereHas('transactionCategories', function ($query2){
$query2->where('code', '=', 'bar');
});
})
->whereBetween ( 'posted_date', [$from,$to])
->where('parent_id', '=', 0)
->get ();
Update
Not sure if this helps, but I did a query to see which subcategories it is returning with the WHERE clause and it returns only ones where the code is NULL
mysql> SELECT * FROM transaction_subcategories WHERE id in ('1125', '630', '1395') AND code is null;
+------+-----------------------------------+------+---------+
| id | name | code | user_id |
+------+-----------------------------------+------+---------+
| 630 | foo | NULL | 80 |
| 1125 | foo | NULL | 80 |
| 1395 | foo | NULL | 80 |
+------+-----------------------------------+------+---------+
Update 2 Turns out it is the WHERE doing it, it seems that NULL values won't be compared against a string, in other words if I do a WHERE on a column that has nulls, the null value rows will disappear
Answer Turns out adding OR code is null in a group in the WHERE clause was all I needed. Closed :)

Update a field if another field has a certain value

I am trying to update a MySQL database but only if a field has the value Approved.
If status is Approved then Date approved should update with the date.
Below is the code I am currently using but cannot get it to work. How to get it to work?
UPDATE my_table
SET `FieldValue`= IF(FieldName='status' AND FieldValue='Approved','".date('m/d/Y')."','')
WHERE `SubmissionId`=".$SubmissionId."
AND FieldName='Date Approved'
Sample Data
+--------+--------------+---------------+--------------+
| FormId | SubmissionId | FieldName | FieldValue |
+--------+--------------+---------------+--------------+
| 6 | 778 | status | Not Approved |
| 6 | 778 | Date Approved | |
+--------+--------------+---------------+--------------+
Use a CASE statement like below:
UPDATE my_table
SET `FieldValue` = CASE WHEN FieldName = 'status'
AND FieldValue='Approved' THEN date('m/d/Y') ELSE `FieldValue` END
WHERE `SubmissionId` = $SubmissionId;
But your query won't make sense; your FieldValue column looks like a string type column and you are trying store a date type data.
Something like this?
$db = JFactory::getDbo();
$query = $db->getQuery(true);
// Fields to update.
$fields = array(
$db->quoteName('FieldValue') . ' = ' . $date->toSql('m/d/Y'))
);
// Conditions for which records should be updated.
$conditions = array(
$db->quoteName('SubmissionId') . ' = SubmissionId',
$db->quoteName('FieldValue') . ' = ' . $db->quote('Approved')
);
$query->update($db->quoteName('#__my_table'))->set($fields)->where($conditions);
$db->setQuery($query);
$result = $db->execute();
Superficially, you should be using the raw SQL like this:
UPDATE my_table
SET FieldValue = date('m/d/Y')
WHERE SubmissionId = 778
AND FieldName = 'Date Approved'
-- AND FieldValue IS NULL -- Optional
-- AND FormId = 6 -- Optional
AND EXISTS (SELECT * FROM my_table
WHERE FieldName = 'status'
AND FieldValue = 'Approved'
AND SubmissionId = 778
-- AND FormId = 6 -- Optional
)
You might need to tart things up a little to get values embedded into the string that forms the SQL statement.
You don't mention FormID in your query; in case of doubt, you should constrain the UPDATE with the correct FormID value, twice, like you constrain the SubmissionID value twice (as shown in the comments). You might decide you only want to update the 'Date Approved' field name when it is NULL (or perhaps blank).
I note that one of the problem with this EAV design is that you lose the type-checking that a normal design gives you. You could store a date (as intended), or a pure number, or pure text, or anything else in the FieldValue column for the 'Date Approved' FieldName and there's nothing to stop that abuse happening. If you had an orthodox typed column, you could ensure that non-dates were never stored in the 'Date Approved' column.
Your UPDATE is tied to a single submission ID; so is mine. It should be possible to enhance things so that all the uninitialized 'Date Approved' columns that are approved and have not previously had the 'Date Approved' value set do in fact have it set.