cakephp complex find query example - php

I would like to perform a find('all') query on a table in a CakePHP application with the following conditions:
1- The primary key value is equal 17
AND
2- The value of another column my_column_id in the table is the MAX of all the values of the column my_column_id but less than the value of my_column_id of the datum whose primary key value is equal to 17
AND
3- The value of another column my_column_id in the table is the MIN of all the values of the column my_column_id but greater than the value of my_column_id of the datum whose primary key value is equal to 17
So the above query should give me 3 results.
Is it possible to get all this in one single query? or do I need three separate queries?
I tried something like:
$results = $this->Model->find('all', array(
'conditions' => array(
'MAX(Model.my_column_id) <' => 23,
'Mddel.id' => 17,
'MIN(Model.my_column_id) >' => 23
)
));
23 represents the value of my_column_id of the datum whose primary key is equal to 17
But that gives me the following error: SQL Error: 1111: Invalid use of group function
Any help please.

Often times when I have complex queries I'll just write out the SQL. I can't speak for the speed and I'm not sure if it's frowned upon, but you can try giving this a shot.
$this->Model->query('
SELECT MAX(table.column),
MIN(table.column)
FROM table
WHERE table.id = '$id'
');
Also, if this controller is outside of the model make sure to load the correct model (place it above the query)
$this->loadModel('Model');

You can't use MySQL aggregate functions (MAX, MIN etc) in your where clause.
I think the easiest way will be to explicitly include the GROUP BY and use HAVING, something like this:
$this->Model->find('all', array(
'group' => 'Model.some_field HAVING MAX(Model.some_field) > 23
AND MIN(some_field) < 23'
));
(although that doesn't make sense, I don't think!)*
That's something like what you want.
Toby
Update
[*] By which I mean, I don't think it makes sense to be querying both greater than and less than a value!

You can do it by passing conditions array inside find.
Example
$this->Model->find('all',array
(
'conditions' => array
(
'Model.field' => 17,
'MAX(Model.field) < ' => 17,
'MIN(Model.field) > ' => 17
)
))
OP Comment Response
SQL Error: 1305: FUNCTION NAX does not exist
Solution: Remove all spaces between the function and the parenthesis or set sql_mode="IGNORE_SPACE";
This is causing the error:
SELECT MAX (id) as something FROM Example
This will work correctly:
SELECT MAX(id) as something FROM Example

Related

How do I update multiple records in a table with a single query? (postgresql)

I have a table with the name of a group of animals.
It looks like this:
Suppose you want to update the data for groups.
Now, in the record with group_id = 2, the quantity changes from 2 to 1.
In a record with group_id = 3, different_aviaries change from false to true.
In a record with group_id = 4, the group_name changes to null, and the quantity to 0.
The result should be like this:
How can this be done in a single request?
P.S I greatly simplified the table in the example. In fact, it may be necessary to update 40 records at a time. In addition, the columns in the table from Example 5, but in fact there are 14.
You could make use of the Laravel Batch package. From the readme:
use App\Models\User;
$userInstance = new User;
$value = [
[
'id' => 1,
'status' => 'active',
'nickname' => 'Mohammad'
] ,
[
'id' => 5,
'status' => 'deactive',
'date' => Carbon::now()
] ,
];
$index = 'id';
Batch::update($userInstance, $value, $index);
You could easily adjust it to solve your issue.
Yes it can be done in native SQL. But whether with an ORM or natively it's a bad idea, especially with 40 rows and 14 columns. If this is a one time operation then just write the individual updates. That would be a much safer option because as this larger the probability of making an error vastly increases. If it not a one time operation then there's nothing to predict this variability in advance; so each becomes a one time thing.
But it can be done; Just don't:
update animal_groups
set quantity = case when group_id = 2 then 1
when group_id = 4 then 0
else quantity
end
, different_aviaries = case when group_id = 3 then true
else different_aviaries
end
, group_name = case when group_id = 4 then null
else group_name
end
where group_id in (2,3,4);
Note the quantity update must account for both value changes as the column name can be a LEFT VALUE only once in the statement.

PDO prepared statement seems to ignore HAVING clause

I've included a DB-fiddle, and you can adjust the input parameter accordingly. This returns how I would expect it to, and differs from the results I am seeing in PDO.
I have the following minified table-view and query:
CREATE TABLE `tagged` {
`tag` SMALLINT(5) UNSIGNED NOT NULL
}
Table has an assortment of values, but you can use 1-10 for tags in the DB:
INSERT INTO tagged (tag) VALUES (1), (2), (3), (4), (5), (6), (7), (8), (9), (10)
query:
SELECT tagged.tag,
(#t := :tag),
#t AS temp_var,
(#t IS NULL OR FIND_IN_SET(tagged.tag, #t) > 0) AS is_match
FROM tagged
HAVING is_match = 1
LIMIT 150
This seems well and good when run in a client, command line, jdbc, etc. If I put in an input of '' or NULL, I get all results. Similarly an input of '1' yields only tags of 1, and an input of '1,4' would retrieve all tags with 1 or 4.
The way the query restricts these results is via the is_match = 1 in the HAVING clause. When run with PDO, the parameter seems to bind correctly but it completely ignores the condition in the clause:
Array
(
[0] => stdClass Object
(
[tag] => 3
[(#t := ?)] => 1,4
[temp_var] => 1,4
[is_match] => 0 ## should not have been returned
)
[1] => stdClass Object
(
[tag] => 4
[(#t := ?)] => 1,4
[temp_var] => 1,4
[is_match] => 1
)
PHP code used to run this (simplified):
$conn = /* pdo connection object */;
$stmt = $conn->prepare(DB::queryOf('test')); //uses our above query from a file
$stmt->bindValue(':tag', $args['tag'], PDO::PARAM_STR); //hardcode binding '1,4'
$stmt->execute(); //also tried plain #execute($args)
return $stmt->fetchAll(PDO::FETCH_OBJ);
Is there something I'm missing? I am binding a direct string parameter, and it seems the temporary variable is there and set correctly. Why is PDO returning the results for elements where is_match = 0?
I believe this behavior is dependent on the RDBMS being used.
In the absence of the GROUP BY clause, it seems that in some circumstances, the entire result can be considered as "one group". Because one row in the results satisfies the HAVING condition, all shall pass.
Additional reading:
Use of HAVING without GROUP BY in SQL queries
HAVING without GROUP BY
p.s. I don't think the > 0 is necessary.
I suppose I'd write your query something like this:
SELECT tag,
#t := '1,4' AS temp_var,
1 AS is_match
FROM tagged
WHERE #t IS NULL OR FIND_IN_SET(tag, #t)
LIMIT 150;

Update a custom column value sql

When outside the boxing thinking. I came up with the following solution:
$column = 'product';
$enum = '1';
$product_access = $dbh->prepare("UPDATE products_access SET {$column} = :enum WHERE products_access.id = :id");
$product_accessvar = trim($user['id']);
$product_access->bindParam(':id', $product_accessvar, PDO::PARAM_INT);
$product_access->bindParam(':enum', $enum, PDO::PARAM_INT);
//$product_access->bindParam(':product_enum', $enum);
//foreach($_POST["checkbox2"] as $loc_id)
$product_access->execute();
Thanks for your help, maybe i can help someone with my solution. It works for me now!
Below my question
I have a question about my follow project. I build an sql table where the users can add custom columns with a enum value. But the user can also update the value of this column. I can't set any column name because they all custom made by the user, so there is no column name.
My SQL:
tabel `products_access`
--
CREATE TABLE IF NOT EXISTS `products_access` (
`id` int(30) NOT NULL,
`user_id` int(30) NOT NULL,
`product 2` enum('0','1') NOT NULL COMMENT 'Dit is een product beschrijving van product 2.dgfdg'
)
Array:
Array
(
[id] => 17
[name] => product 2
[number] => 2002
[description] => Dit is een product beschrijving van product 2.
[mount] => 34
[price] => 6778
[deleted] => 0
[user_id] => 17
[product 2] => 1
)
Script:
<label>
<input name="clickedproduct[]" type="checkbox" value="<?php echo $avlue['id']; ?>" <?php echo (($_SERVER['REQUEST_METHOD'] == 'POST') ? ((isset($_POST[$avlue['name']])) ? ' value="'.$avlue[$avlue['name']].'" checked' : ' value="'.$avlue[$avlue['name']].'"') : (($avlue[$avlue['name']] == '1') ? ' value="'.$avlue[$avlue['name']].'"checked' : ' value="'.$avlue[$avlue['name']].'"')); ?>>
<?php echo $avlue['name']; ?>
</label>
PDO:
$product_access = $dbh->prepare('UPDATE products_access() VALUES(:id, :loc)');
$product_access->bindValue(':id', $id);
$product_access->bindParam(':loc', $loc_id);
foreach($_POST["checkbox2"] as $loc_id) $product_access->execute();
Below the array after saving.
Array
(
[username] => joshua
[rank] => Array
(
[0] => 0
)
[koppel] => Array
(
[0] => 1
)
[clickedproduct] => Array
(
[0] => 17
)
)
Can some one explain the solution?
This sort of application ordinarily is implemented using a metadata table, also known as a key / value store.
Each row of this metadata table identifies
the object it describes, with a product_id or similar foreign key.
the name of the data item it holds, e.g. 'price' or 'mount'
the value of the data item it holds, e,g, '6778' or '34'
optionally a code identifying the data type of the item ('money'? 'text'?)
It's easy to add metadata to an object. Insert a row into the metadata table giving the "column name" you want and the value. It's a little trickier to retrieve it. You need a query, for example, like this.
SELECT p.product_id, a.val as price, b.val as mount
FROM product p
LEFT JOIN metadata a ON a.product_id = p.product_id AND a.key='price'
LEFT JOIN metadata b ON b.product_id = p.product_id AND b.key='mount'
WordPress's wp_postmeta table setup is a good and widely used example of this data design pattern. It has a workable API.
It's ordinarily considered bad practice to use data definition language commands (like ALTER TABLE CHANGE colname newcolname INT) in production. For one thing these commands are quite slow and thread-unsafe. For another, when a schema contains all kinds of user-defined columns it's hard to troubleshoot. You're better off using application code, like what I have suggested here, to allow your users to create their own data keys and values.
Create a column named 'custom_column' in 'products_access'.(Add user
defined column name here)
Create another table named 'tbl_custom_column'
Add fields 'user_id', 'custom_column_value' (Add your values here)
Hope that will help.

Waht's the best way to use unique validation according to an attribute in a table

I'm using Laravel 5.1 , I've a model Customer which has many Vehicles.
I set validations for Vehicle model like this :
public static $Rules = array(
'code' => 'required|unique:vehicles',
'registernumber' => 'required|unique:vehicles'
);
Till now, all is fine : I can't insert two vehicles with the same code or registernumber.
What I want to do is :
Can I set a custom validation which allows me to insert unique code or registernumber value just for a given CustumerID ?
Example :
Customer1 :
Vehicle1: code1, registernumber1
Vehicle2: code2, registernumber2
(Here I can't insert for example two codes having 'code1' value with Customer1)
Customer2 :
Vehicle1: code1, registernumber1
Vehicle2: code5, registernumber5
(Here I can't insert for example two registernumbers having 'registernumber5' value with Customer2)
Any idea please ?
As long as the customer id is in that table. The way the unique validation rule works is as follows:
unique:
[table name, optionally with a connection name],
[the column you are checking for uniqueness, optional],
[the id of the row that will be ignored in the check (for example if you are updating a row, you want the row you are updating to not be included), optional],
[the column name that contains the id you are running the check against, optional],
[additional where clauses (where_column, where_value)]
So, if your table schema looks like this:
vehicle_id, code_number, registration_number, customer_id
And you only want to run the unique check for rows of the same customer, you can do this:
$customer_id = 'whatever';
$Rules = array(
'code' => 'required|unique:vehicles,code_number,NULL,vehicle_id,customer_id,'.$customer_id
);
That will run the check only on rows where('customer_id', $customer_id)
Syntax
ALTER TABLE `tablename`
ADD UNIQUE KEY `my_unique_key` (`colname1`, `colname2`);
In your example
ALTER TABLE `yourtable`
ADD UNIQUE KEY `unique_reg` (`customername`, `vehicle`, `code`, `regno`);
Try this but you should handle the db_error otherwise it affects the user experience

Cakephp find returning null values for associated model

I have two models A and B. Relationships are defined as mentioned below.
A hasOne B
B belongsTo A
Everything was working great till a few days back. In last couple of days something has changes and now when I do a find on A (with recursive set to 2), it returns the values of B but all values are returned as NULL including the ID of B.
A (id - 1220, other_fields)
B (id - 11, a_id - 1220, other_fields)
Resulting array looks like this.
array(
'A' => array(
'id' => 1220,
'field1' => 'dsdsa',
...
)
'B' => array(
'id' => null,
'a_id' => null,
...
)
)
I have verified in database that row for B exists corresponding to A. I even tried executing the Host find query (copied from SQL debug dump) and it returns the results correctly so nothing wrong at database level. It's cake (or myself) which is messing up with the data.
It was all working fine but something has gone wrong and I am having hard time trying to figure out what.
Here is the query (replace Host with A and Service with B)
SELECT `Host`.`id`, `Host`.`user_id`, `Host`.`title`, `Host`.`featured`, `Host`.`slug`, `Host`.`profile_type`, `Host`.`social_security_number`, `Host`.`biz_id`, `Host`.`address`, `Host`.`lat`, `Host`.`lon`, `Host`.`description`, `Host`.`type`, `Host`.`size`, `Host`.`unavailability`, `Host`.`kids`, `Host`.`supervision`, `Host`.`emergency_transport`, `Host`.`experience`, `Host`.`first_aid`, `Host`.`oral_medication`, `Host`.`injected_medication`, `Host`.`payment_mode`, `Host`.`paypal_email`, `Host`.`name_on_check`, `Host`.`payment_address`, `Host`.`bank_name`, `Host`.`bank_account_number`, `Host`.`balance`, `Host`.`pending_balance`, `Host`.`approved`, `Host`.`created`, `Host`.`modified`, `Service`.`id`, `Service`.`host_id`, `Service`.`day_care`, `Service`.`day_care_price`, `Service`.`day_night_care`, `Service`.`day_night_care_price`, `Service`.`walking`, `Service`.`walking_price`, `Service`.`walking_max_distance`, `Service`.`checkup_visit`, `Service`.`checkup_visit_price`, `Service`.`checkup_visit_max_distance`, `Service`.`grooming`, `Service`.`grooming_price`, `Service`.`bathing`, `Service`.`bathing_price`, `Service`.`pickndrop`, `Service`.`pickndrop_price`, `Service`.`training`, `Service`.`training_price`, `Service`.`cancellation_policy`, `Service`.`accept_last_min_bookings`, `Service`.`created`, `Service`.`modified` FROM `hosts` AS `Host` LEFT JOIN `services` AS `Service` ON (`Service`.`host_id` = `Host`.`id`) WHERE `Host`.`id` = 2569 ORDER BY `Host`.`created` DESC LIMIT 1

Categories