CakePHP : Get Table with many conditions - php

I have a very specific problem. Even with the great CakePHP doc, I still don't know how to fix my pb.
I'm currently web developping using the CakePHP framework. Here is my situation :
I have a Table "TableA" which contains parameters "name", "type"(1 to 6) and "state"(OK and NOT OK) . What I want is getting all the Table lines which are type 5 OR 6 and which have not a same name line with "state" OK.
There are different lines of the table which have the same "name". I'm interesting to the lines from the same name where there is no OK state.
For example, there are :
name : example1 state : NOT OK
name : example1 state : NOT OK
name : example1 state : NOT OK
And there is no example1 with the state OK and this is this kind of line I want to get.
I would like to do this with the cakePHP syntax, with conditions in the TableRegistry::get function.
Thanks for helping. Waiting for your return.
PS:
What I achieved now is not the best solution :
$tablea_NOTOK = TableRegistry::get("TableA")->find('all', array(
'conditions' => array(
'OR' => array(
array('TableA.type' => 5),
array('TableA.type' => 6),
),
'Etudes.state' => 'NOT OK'
)
));
$this->set(compact('tablea_NOTOK'));
$tablea_OK = TableRegistry::get("TableA")->find('all', array(
'conditions' => array(
'OR' => array(
array('TableA.type' => 5),
array('TableA.type' => 6),
),
'Etudes.state' => 'OK'
)
));
$this->set(compact('tablea_OK'));
And then in my view, i compared each line of the tablea_OK with the tablea_NOTOK. But there is a lot of data so the code is not perfect and slow

You may consider creating a view table in your database which holds the combination of data needed. Since the data will all be from a single table, you wouldn't need to loop through the data and compare it.
I don't know all your table relationships, but I made a simple table with these fields and data:
id name type state
1 Harry 5 OK
2 Harry 6 NOT OKAY
3 Harry 6 NOT OKAY
4 John 5 NOT OKAY
Then I wrote a query which would group by name and count the state values:
SELECT `name`, `type`, `state`,
(SELECT COUNT(state) FROM TableA as TableA1 WHERE `state` = 'OK' AND TableA.name = TableA1.name) as okay_count,
(SELECT COUNT(state) FROM TableA as TableA2 WHERE `state` = 'NOT OKAY' AND TableA.name = TableA2.name) as not_okay_count
FROM TableA
GROUP BY name;
The results look like this:
name type state okay_count not_okay_count
Harry 5 OK 1 2
John 5 NOT OKAY 0 1
You can adjust the query as needed and create your database view table and then call that in CakePHP.
$my_view_table = TableRegistry::get("MyViewTable")->find('all');
You can learn more about MySQL view tables here

Related

MYSQL + PHP : It is possible to union results from one table with data provided by an array?

Scenario:
We have a table where we store our data but also we got some data from an external API. It is possible, to append this external data to our query results so to be able to apply WHERE and ORDER BY conditon.
Example:
$a = array(array('RegDate' => '02-10-2018',
'JobTitle' => 'Web Designer'
),
array('RegDate' => '03-10-2018',
'JobTitle' => 'Account Manager'
),
array('RegDate' => '01-10-2018',
'JobTitle' => 'Web Designer'
),
...
);
$SQL = SELECT RegDate, JobTitle, CandidateName UNION ALL SELECT $a[]['RegDate'] ,$a[]['JobTitle'] WHERE JobTitle LIKE '%Account%' ORDER BY RegDate DESC
You would probably, deleguate your logic to php instead of MySQL here. Retrieve data from Database then append external data to it:
1 - execute query, get data from DB.
2 - Append external data to data previously returned by database
3 - Sort resulting array

cant get the group of an int field from a query in cakephp3

I have a table with a field called 'year'. It has many repetitions in the column so I want to find the distinct group. I have 4 different 'years' in about 20 rows and I dont get these values from the query. Instead what is returned are 4 numbers which are not the years (5,14,4,70). The same code worked fine when I used this with suburb field in another table where there were multiple values of this field. I dont get why this isnt working.
//in view
echo $this->Form->input('year', ['label' => 'Year','options' => $allyears]);
//controller
$allyears = $this->TimesheetDates->find('list')
->select(['TimesheetDates.id', 'TimesheetDates.year'])
->group(['TimesheetDates.year'])->autoFields(true)
->order(['TimesheetDates.year'=> 'ASC'])
->hydrate(false);
$this->set('allyears',$allyears);
//another controller and this code worked fine
$suburb = $this->Students->find('list')->where(['Students.address_suburb !=' => '','Students.student_inactive' => 0])
->select(['Students.id','Students.address_suburb'])
->group(['Students.address_suburb'])->autoFields(true)
->order(['Students.address_suburb' => 'ASC'])
->hydrate(false);
take a look at the documentation about how find('list') works
$allyears = $this->TimesheetDates->find('list', [
'keyField' => 'id',
'valueField' => 'year']
)
->group(['year'])
->order(['year'=> 'ASC']);
Note that it has no meaning selecting the id of the TimesheetDates Table as you are grouping by year and the id is choosen randomly between all the records that share the same year

Fetching row data as column names and set as boolean if exists or not

I have access to a database similar to this:
users:
id | name
====|========
1 | Tom
2 | Dick
3 | Harry
4 | Sally
exlusions:
user_id | exclusion
========|==============
1 | French only
3 | No magazine
4 | English only
1 | No magazine
Is it possible to query the database to get a result like this?
$users = [
[
'id' => 1,
'name' => 'Tom',
'english_only' => false, // unset or null would be okay
'french_only' => true,
'no_magazine' => true,
],
// . . .
];
I've been playing around with things like GROUP_CONCAT, PIVOT, and other examples and can't figure out how or if any of it applies.
SQL Fiddle -- thought I could modify this to match my scenario. I didn't get all that far, and as you can see I have no idea what I am doing...
You can use IF in your select to make your 3 columns out of the 1 based on their values.
SELECT id, name,
IF(exclusion='English only',true,false) as english_only
IF(exclusion='French only',true,false) as french_only
IF(exclusion='No magazine',true,false) as no_magazine
FROM users, exclusions
WHERE users.id=exclusions.user_id
I started with #RightClick's answer, but had to tweak it a bit for my SQL server.
SELECT User.id, User.name,
CASE WHEN Ex.exclusion = 'English Only' THEN 1 ELSE 0 END as english_only,
CASE WHEN Ex.exclusion = 'French Only' THEN 1 ELSE 0 END as french_only,
CASE WHEN Ex.exclusion = 'No magazine' THEN 1 ELSE 0 END as no_magazine
FROM users as User
LEFT JOIN exclusions Ex on User.id = Ex.user_id;
So much simpler than what I thought it was going to be after googling and searching SO all day...

Checking users friendship

Continuing this question,
in my web app, I want to allow users to add friends, like facebook, in my previous question, I finally decided to have the database structure as #yiding said:
I would de-normalize the relation such that it's symmetric. That is,
if 1 and 2 are friends, i'd have two rows (1,2) and (2,1).
The disadvantage is that it's twice the size, and you have to do 2
writes when forming and breaking friendships. The advantage is all
your read queries are simpler. This is probably a good trade-off
because most of the time you are reading instead of writing.
This has the added advantage that if you eventually outgrow one
database and decide to do user-sharding, you don't have to traverse
every other db shard to find out who a person's friends are.
So, now if user 1 adds user 2, and user 5 adds 2, something like this will go into the db:
ROW_ID USER_ID FRIEND_ID STATUS
1 1 2 0
2 2 1 0
3 5 2 0
4 2 5 0
As you see, we insert the row of the "REQUEST SENDER" first, so now imagine that user 5 is logged in, and we want to show him the friendship requests, here is my query:
$check_requests = mysql_query("SELECT * FROM friends_tbl WHERE FRIEND_ID = '5'");
the above query, will fetch ROW_ID = 4, this means with the above query shows us that user 2 has added 5, but he has NOT, actually the user 5 added user 2, so here we should not show any friendship requests for user 5, instead we need to show it for user 2.
How I'm supposed to check this correctly?
This is an edited answer.
Your SQL query should look like this:
SELECT USER_ID, FRIEND_ID FROM friends_tbl WHERE FRIEND_ID = '5' OR USER_ID = '5'
Then you have to parse your result in this way. Assuming you have got a php array like this:
$result = array(
0 => array(
'USER_ID' => 5,
'FRIEND_ID' => 2
),
1 => array(
'USER_ID' => 2,
'FRIEND_ID' => 5
)
2 => array(
'USER_ID' => 5,
'FRIEND_ID' => 8
),
3 => array(
'USER_ID' => 8,
'FRIEND_ID' => 5
)
)
You just have to get the even rows:
$result_final = array();
for($i = 0; $i < count($result); $i++) {
if($i % 2 == 0) $result_final[] = $result[$i];
}
Then you will have an array like this:
$result = array(
0 => array(
'USER_ID' => 5,
'FRIEND_ID' => 2
),
1 => array(
'USER_ID' => 5,
'FRIEND_ID' => 8
)
)
Alternative method: Make your SQL look like this:
SELECT FRIEND_ID FROM friends_tbl WHERE USER_ID = '5'
That's all.
Friendship query notifies should be placed in something like message inbox. Relation you described is meant to hold, well, friendship relations, not the fact of the event happening itself. You should consider create relation to hold notifies and fill it properly alongside with two inserts on friends_tbl
You'll need to hold a temporary table (or fixed - for data mining) which has all the requests made from one user to another, for example:
table: friendRequest
inviterId inviteeId status tstamp
2 5 0 NOW()
5 8 0 NOW()
assuming that 0 is unapproved.
Than you'll query for all pending requests
SELECT * FROM friendRequest WHERE invitee_id = :currentLoggedUserId AND status = 0
Once a user approved a user, you'll create a transaction, describing this newly formed relation and updating the friendRequests table
You could also query this way assymetric relations, where a user has many followers, by looking for un-mutual friendships.

CakePHP edit multiple records at once

I have a HABTM relationship between two tables: items and locations, using the table items_locations to join them.
items_locations also stores a bit more information. Here's the schema
items_locations(id, location_id, item_id, quantity)
I'm trying to build a page which shows all the items in one location and lets the user, through a datagrid style interface, edit multiple fields at once:
Location: Factory XYZ
___________________________
|___Item____|___Quantity___|
| Widget | 3 |
| Sprocket | 1 |
| Doohickey | 15 |
----------------------------
To help with this, I have a controller called InventoryController which has:
var $uses = array('Item', 'Location'); // should I add 'ItemsLocation' ?
How do I build a multidimensional form to edit this data?
Edit:
I'm trying to get my data to look like how Deceze described it below but I'm having problems again...
// inventory_controller.php
function edit($locationId) {
$this->data = $this->Item->ItemsLocation->find(
'all',
array(
"conditions" => array("location_id" => $locationId)
)
);
when I do that, $this->data comes out like this:
Array (
[0] => Array (
[ItemsLocation] => Array (
[id] => 16
[location_id] => 1
[item_id] => 1
[quantity] => 5
)
)
[1] => Array (
[ItemsLocation] => Array (/* .. etc .. */)
)
)
If you're not going to edit data in the Item model, it probably makes most sense to work only on the join model. As such, your form to edit the quantity of each item would look like this:
echo $form->create('ItemsLocation');
// foreach Item at Location:
echo $form->input('ItemsLocation.0.id'); // automatically hidden
echo $form->input('ItemsLocation.0.quantity');
Increase the counter (.0., .1., ...) for each record. What you should be receiving in your controllers $this->data should look like this:
array(
'ItemsLocation' => array(
0 => array(
'id' => 1,
'quantity' => 42
),
1 => array(
...
You can then simply save this like any other model record: $this->Item->ItemsLocation->saveAll($this->data). Adding an Item to a Location is not much different, you just leave off the id and let the user select the item_id.
array(
'location_id' => 42, // prepopulated by hidden field
'item_id' => 1 // user selected
'quantity' => 242
)
If you want to edit the data of the Item model and save it with a corresponding ItemsLocation record at the same time, dive into the Saving Related Model Data (HABTM) chapter. Be careful of this:
By default when saving a HasAndBelongsToMany relationship, Cake will delete all rows on the join table before saving new ones. For example if you have a Club that has 10 Children associated. You then update the Club with 2 children. The Club will only have 2 Children, not 12.
And:
3.7.6.5 hasAndBelongsToMany (HABTM)
unique: If true (default value) cake will first delete existing relationship records in the foreign keys table before inserting new ones, when updating a record. So existing associations need to be passed again when updating.
Re: Comments/Edit
I don't know off the top of my head if the FormHelper is intelligent enough to autofill Model.0.field fields from a [0][Model][field] structured array. If not, you could easily manipulate the results yourself:
foreach ($this->data as &$data) {
$data = $data['ItemsLocation'];
}
$this->data = array('ItemsLocation' => $this->data);
That would give you the right structure, but it's not very nice admittedly. If anybody has a more Cakey way to do it, I'm all ears. :)

Categories