Selecting distinct values from key/value pairs in a seperate table - mysql

I have 2 database tables
JOBS(JOB_ID, JOB_TIME, JOB_NAME,...), JOB_PARAMETERS(JOB_ID,NAME,VALUE)
where JOB_PARAMETERS is essentially a map containing job parameter key value pairs.
Every job may have a unique parameter key/value pairs.
I am looking to pragmatically build a query that will return distinct job id's that contain key/value combinations. Where the values are actually a list of values, comparison operators.
For example:
JOB_PARAMETERS: NAME = 'OUTPUT_FILENAME', VALUE LIKE "ALEX%", "JAX%"
NAME = 'PRIORITY' , VALUE > 7
The above example would automatically filter out all jobs that don't have the OUTPUT_FILENAME and PRIORITY key. Returning All jobs that meet both conditions.
I also need to be able to support pagination and order by.
I was planning on using Perl with DBIx::Class, But I can do it in pure Perl/SQL as well.
I am open to changing the database schema, but every job can have different key/value pairs, so I cant just make them columns in the jobs table.
Thanks in advance.

When using DBIx::Class you can generate a DBIC schema by using Schema::Loader.
After connecting to the database you get a $schema object you can use to get a ResultSet filtered to return the Result objects you want:
my $rs_job_parameters = $schema->resultset('Job_Parameters')->search({
-or => [
{
'name' => 'OUTPUT_FILENAME',
'value' => [{ like => 'ALEX%'}, { like => 'JAX%' }].
},
{
'name' => 'PRIORITY',
'value' => [{ '>' => 7}].
}
]},
{
columns => [qw( job_id )],
group_by => [qw( job_id )], # alternative you can use distinct => 1 to group_by all selected columns
having => \[ 'COUNT(*) = ?', [ 2 ] ],
}
);
my #job_ids = $rs_job_parameters->get_column('job_id')->all;

One can do it in SQL, by grouping JOB_PARAMETERS by JOB_ID and filtering the groups accordingly. For example, if there is a uniqueness constraint over (JOB_ID, NAME), one can query as follows:
SELECT JOB_ID
FROM JOB_PARAMETERS
WHERE (NAME='OUTPUT_FILENAME' AND (VALUE LIKE 'ALEX%' OR VALUE LIKE 'JAX%'))
OR (NAME='PRIORITY' AND VALUE > 7)
GROUP BY JOB_ID
HAVING COUNT(*) = 2
Absent such a uniqueness constraint, COUNT(*) would have to be replaced e.g. with COUNT(DISTINCT NAME).

Related

CakePHP 4: How to add count of association results to where conditions

I'm trying to add the count results of an association to the where conditions, for example:
$findQuery = $this->Products->find('all');
$findQuery->leftJoinWith('Synonyms', function($q) {
return $q->where(['Synonyms.title LIKE' => '%TEST%']);
});
$findQuery->select([
'amount_of_matching_synonyms' => $findQuery->func()->count('Synonyms.id')
]);
$findQuery->where([
'OR' => [
'Products.description LIKE' => '%TEST%',
'amount_of_matching_synonyms >' => 0
]
]);
What happens now is that I'm getting 1 result returned with the 'amount_of_matching_synonyms' field. But this appears to have a cumulated result of all the records it should return.
Please help me out!
You should first figure out how to do these things in plain SQL, it will then be much easier to translate things to the query builder.
Counting related data requires joining in the realted data and creating groups on which aggregate functions can be used, and you're missing the latter. Furthermore you cannot use aggregates in the WHERE clause, as grouping happens after the WHERE clause is applied, you would have to use the HAVING clause instead.
The basic SQL to filter on such counting would look something like this:
SELECT
COUNT(synonyms.id) amount_of_matching_synonyms
FROM
products
LEFT JOIN
synonyms ON synonyms.id = synonyms.product_id
GROUP BY
products.id
HAVING
amount_of_matching_synonyms > 0
Translating this into the query builder would be fairly simple, you'd just need group() and having(), something like this:
$findQuery = $this->Products
->find()
->select([
'Products.description',
'amount_of_matching_synonyms' => $findQuery->func()->count('Synonyms.id')
])
->leftJoinWith('Synonyms', function(\Cake\ORM\Query $q) {
return $q->where(['Synonyms.title LIKE' => '%TEST%']);
})
->group('Products.id')
->having([
'OR' => [
'Products.description LIKE' => '%TEST%',
'amount_of_matching_synonyms >' => 0
],
]);
Note that you need to select the description, otherwise the condition in the having clause would fail.
The resulting SQL would look something like this:
SELECT
products.description,
COUNT(synonyms.id) amount_of_matching_synonyms
FROM
products
LEFT JOIN
synonyms ON
synonyms.product_id = products.id
AND
synonyms.title LIKE '%TEST%'
GROUP BY
products.id
HAVING
products.description LIKE '%TEST%'
OR
amount_of_matching_synonyms > 0
See also
MySQL 5.7 Reference Manual / Functions and Operators / Aggregate Functions
Cookbook > Database Access & ORM > Query Builder > Aggregates - Group and Having
Cookbook > Database Access & ORM > Query Builder > Subqueries

Add multiple count fields of contained model to query in cakephp 3

I want to have in my model, instead of the complete set of entries of the models contained by another one, just the quantity of them. I could do this by adding the "size" field to the resultset, but I'd like to do this in the query, as I want to paginate and order the results dinamically. I am using this code, but for some reason, if the first count is different from zero, both count fields have the same value, which is the value of the second count field.
$query = $this->Users->find('all')
->contain(['Likes','Favs']);
$query
->select(['like_count' => $query->func()->count('Likes.id')])
->leftJoinWith('Likes')
->group(['Users.id'])
->autoFields(true);
$query
->select(['fav_count' => $query->func()->count('Favs.id')])
->leftJoinWith('Favs')
->group(['Users.id'])
->autoFields(true);
$this->paginate['sortWhitelist'] = [
'name',
'email',
'last_login',
'fav_count',
'like_count',
];
I would like to know why this happens and if there is any other way to do what I attempt, which would be to have ('name', email, 'last_login', quantity of entries in Likes with the user's id, quantity of entries in Favs with the user's id). I have tried using join() to do the left join, but I haven't been able to obtain the result I want.
If you have multiple joins, then you need to count with DISTINCT, eg:
COUNT(DISTINCT Likes.id)
This is because your result will contain Likes * Favs number of rows, as for every joined like, all favs will be joined, ie for 2 likes and 10 favs you'd end up with 20 rows in total. A regular COUNT() would include every single one of those rows.
Also note that you don't need to repeat all that grouping, etc stuff, and you can use a callable for select() to avoid breaking up the builder.
$query = $this->Users
->find('all')
->select(function (\Cake\ORM\Query $query) {
return [
'like_count' => $query->func()->count(
$query->func()->distinct(['Likes.id' => 'identifier'])
),
'fav_count' => $query->func()->count(
$query->func()->distinct(['Favs.id' => 'identifier'])
),
];
})
->autoFields(true)
->contain(['Likes', 'Favs'])
->leftJoinWith('Likes')
->leftJoinWith('Favs')
->group(['Users.id']);
Using the functions builder to generate the DISTINCT is a workaround, as there is no API yet that would allow to specifically generate a keyword. The result will be something like DISTINCT(Likes.id), but it will work fine, the parentheses will be interpreted as part of the expression after the keyword, not as part of a function call.

Can we compare value in Multi Dimensional Json encoded values in MySQL

I am using WordPress platform, and using WP_Query to call my data although can use custom MYSQL script if if can work.
I just want to add a where condition which can filter JSON value as below explained:
so in postmeta table i have a following columns
meta_key: _stock_reserve_outlet_extended
meta_value: a:2:{s:8:"outlet_2";a:2:{s:5:"stock";s:2:"20";s:5:"rider";s:2:"-1";}s:8:"outlet_1";a:2:{s:5:"stock";i:-4;s:5:"rider";i:0;}}
which is actually an array extracted as below by get_post_meta() function:
Array (
[outlet_2] => Array (
[stock] => 45
[rider] => 0
)
[outlet_1] => Array (
[stock] => -4
[rider] => 0)
)
)
i want to filter rows during calling rows from script by comparing following:
stock > 0 in outlet_2
I tried to search filter json values filtration by json_search but that does not compare by key.
To give WP_Query a try, i use following but nothing effect shows
$args['meta_query'][] = array(
'key' => '_stock_outlet_extended',
'value' => array(
'key' => 'stock',
'value' => '18'
),
'compare' => '>',
);
Then i tried following script but that not work aswell:
SELECT meta_value
FROM `frbl1ozme_postmeta`
WHERE `post_id` = 3699 AND `meta_key` = '_stock_outlet_extended' AND meta_value ->'$.stock' > 1;
Do anyone know how to tackle this sort of criteria?
what i observer is if you are saving any array by using update_post_meta() function of wordpress then it will serialize it and you are unable to filter rows by script then.
So after understood this fact i made the changes in structure and along with the stock i am saving status according to the value condition of stock, so i can filter rows easily by MYSQL.
So in one line answer, one can not achieve filter serialize rows via mysql queries till date.
Thank you all for the Help, specially #RiggsFolly

Yii2 : LeftJoin query with Where clause & orderBy clause

I have write query to get distinct stateID whose status is active, from tbl_summer table which is primary key of table tbl_states.
I want the listing of distinct state names in alphabetical order.
Actually i got this from following query but alphabetical order is not getting...
So what is the solution...?
Here is my query :
$query = Tbl_summer::find()
->select('tbl_summer.StateID, tbl_states.state_name')
->distinct('tbl_summer.StateID')
->from('tbl_summer')
->leftJoin('tbl_states', ['tbl_states.ID' => 'tbl_summer.StateID'])
->where(['tbl_summer.IsActive' => '1'])
->orderBy(['tbl_states.state_name' => SORT_ASC]);
Does this work?
$query = Tbl_summer::find()
->select('tbl_summer.StateID, tbl_states.state_name')
->from('tbl_summer')
->leftJoin('tbl_states', ['tbl_states.ID' => 'tbl_summer.StateID'])
->where(['tbl_summer.IsActive' => '1'])
->groupBy('tbl_summer.StateID, tbl_states.state_name')
->orderBy(['tbl_states.state_name' => SORT_ASC]);
I think the second field in groupBy is not needed if there is only one name for one id.

CakePHP & MySQL - Using find('all') with GROUP for one field and MAX for another field

I have a MySQL table with 3 columns (thread_id, message_id, message). Along the lines of the solution found under the "Example using GROUP BY" in this link, I want my query to GROUP BY thread_id, but return the line of of the highest message_id (instead of default lowest) for each thread_id. I then want a nicely formatted array with lines/items just like you get for less complex find operations in CakePHP along the lines of $array[index]['Model']['field']. Using the following CakePHP syntax:
$this->Model->find('all', array(
'fields' => array('MAX(Model.message_id) as message_id', 'Model.thread_id', 'Model.message'),
'group => 'Model.thread_id'
));
Now, unfortunately I am not getting that nicely formatted array. Instead I get an array which looks something like:
Array ( [0] => Array ( [0] => Array ( [message_id] => wanted/correct_message_id ) [Model] => Array ( [message] => Message from lowest/unwanted message_id line. [thread_id] => Key from lowest/unwanted message_id line))
Why does the message_id not get hooked onto the [Model] part of the array and why does CakePHP fetch the lowest message_id line and put the message and thread_id into the [Model] part of the array without the message_id column?
I want all thre columns in the [Model] part of the array and I want that line to be the highest message_id for that thread_id per my initial description. Hope this question makes sense.
Virtual fields are really useful for this kind of thing.
class MyModel extends AppModel {
public $virtualFields = array(
'max_message_id' => 'MAX(MyModel.message_id)'
);
}
You can now use max_message_id as if it were a normal field in your table, so you can add it to your find operations.