Select one image from product (SQL query builder Yii2) - php

the question is about SQL and Yii2 in particular.
There are three tables:
Product {id, title}
Image {id, url}
Product_Image {product_id, image_id, main}
I'm trying to use the query builder to form a query in such a way that the result is a table like this:
Result {product_id, title, main_image}
Columns product_id, title - from the Product table
Column main_image - should contain an entry from image.url where column main = 1, otherwise return the first found image or NULL
Product::find()
->select([
'product.id AS product_id',
'product.title AS title',
'image.image_url AS main_image',
])
->leftJoin('product_image', 'product_image.product_id = product.id')
->leftJoin('image', 'image.id = product_image.image_id')
->asArray()
->all();
This query returns a lot of the same product_id if the product has multiple images. I need to have only 1 entry for each product and there was either a link to the main image, or if there is no main, then the first entry that came across, or NULL
The ideal look is:
[
0 => [
'product_id' => '1'
'title' => 'Product 1'
'main_image' => null
]
1=>[
'product_id' => '2'
'title' => 'Product 2'
'main_image' => '/images/products/2.jpg'
]
2=>[
'product_id' => '3'
'title' => 'Product 3'
'main_image' => '/images/products/3.jpg'
]
]
I tried adding
andWhere(['product_image.main' => 1])
but in that case all products that don't have a 'main' image disappear.
Please help me, what do I need to add to the SQL request (MySQL)?
UPD:
->joinWith('productImages')
->joinWith('productImages.image');
This approach searches for all existing images and puts them in the "productImages" array. I don't have to search all the images for every product.

Related

CakePHP group and count

I am having trouble figuring this out.
I have the following 3 tables: -
Transactions Table
ID
Date
List item
Products Table
ID
Name
Price
Products_Transactions Table
Transaction_ID
Product_ID
Quantity
So the relationship is as follows - a transaction is made, and then the products_transactions table joins them together since a transaction can have multiple products and a product can have multiple transactions. The join_table also keeps track of the amount sold, so, for instance, a newspaper sells in transaction #1 and with a quantity of 2 (so 2 newspapers sold).
Now, I want to make a MySQL statement that finds all products sold, in a specific date interval, so I get something like this: -
3 x Newspapers
12 x Sodas
15 x Beer
So, it just counts and sums up all the products sold.
I have seriously tried everything - I am working with CakePHP so a solution provided in that would be helpful, but even just the plain SQL to achieve this might help me out.
So far, this is what I have: -
$productTransactionsTable = TableRegistry::get('products_transactions');
$productsTransactions = $productTransactionsTable->find('all');
$productsTransactions->matching('transactions', function ($q) {
return $q->where([
'transaction_date >=' => new \DateTime('-1 week'),
'transaction_date <=' => new \DateTime('now'),
'device_id IN' => $this->deviceIdsInDepartment(2)
]);
});
$productsTransactions->contain(['products']);
$productsTransactions->select([
'count' => $productsTransactions->func()->count('quantity'),
'name' => 'products.name'
]);
$productsTransactions->groupBy('products.id');
But this just gives out 1 single result that counts everything together into 1 row, like this:
/src/Controller/EconomyController.php (line 665)
[
(int) 0 => object(Cake\ORM\Entity) {
'count' => (int) 4504,
'name' => 'D Morgenbrød',
'[new]' => false,
'[accessible]' => [
'*' => true
],
'[dirty]' => [],
'[original]' => [],
'[virtual]' => [],
'[errors]' => [],
'[invalid]' => [],
'[repository]' => 'products_transactions'
}
]
Any help is appreciated! I am seriously stuck here!
Thank you!
I think you are counting the total number of sold quantities and grouping so you are getting above results. I think, you need to try the following approach:
$data = $query
->select([
'name' => 'products.name',
'quantity' => 'products.quantity',
])
->group('products.id');

How to loop through a list of products and categories on PHP

<?
$categoriesID = array("popular","old");
$product => array (
Product 1
'categoryID' => $categoriesID[1],
'Name' => 'Product One',
Product 2
'categoryID' => $categoriesID[2],
'Name' => 'Product Two',
Product 3
'categoryID' => $categoriesID[2],
'Name' => 'Product Two',
Product 4
'categoryID' => $categoriesID[2],
'Name' => 'Product Two',
);
How can I loop through this to reflect that product 1 belongs to category 1, product 2 belongs to category 2, product 3 belongs to category 2 and so on?
I tried the following but no luck..
foreach($product as $key => $pro){
var_dump($categoriesID[$key]);
}
I would really appreciated any suggestions or how what i'm doing wrong.The goal is to insert the relationship into a database table where in order to insert a product a category_id is required.
Your arrays are not written correctly. You got a multi dimensional array here (arrays inside of an array). Read this to understand how they are written and how you can work with them: http://php.net/manual/en/language.types.array.php
If your categories are numeric you should also consider to use numeric values: 1 instead of '1' inside of the $categoriesID array or depending on the database auto casting capability you will get issues inserting strings as decimals.
Here is your given code modified as working example. Ive changed the var_dump output for better readability of the result.
Ive also changed the array indexes you have used since arrays start at 0. If you need the numbers still to start at 1 you could add some nonsense value at the beginning of the array or subtract 1 when accessing the array. Keep in mind that this is an quick & dirty solution to the given problem.
Nevertheless as Patrick Q said you should consider some introduction to PHP.
<?php
$categoriesID = array('1','2');
$product = array (
array(
'categoryID' => $categoriesID[0],
'Name' => 'Product One',
),
array(
'categoryID' => $categoriesID[1],
'Name' => 'Product Two',
),
array(
'categoryID' => $categoriesID[1],
'Name' => 'Product Two',
),
array(
'categoryID' => $categoriesID[1],
'Name' => 'Product Two',
)
);
foreach($product as $key => $value){
echo var_export($value, true) . '<br>';
}
You could further edit Mariusz's answer to do something like this:
foreach($product as $item){
echo $item['Name'].' - '.$item['categoryID'].'<br>';
}
This would give you easy access to both product name and category ID.

Get all rows where the field is not NULL in CakePHP 3

How can I get all the instances of a Table where the fields are not NULL ?
Here is the configuration:
I have a Table 1 where the instances have a relationship "hasmany" with a Table 2. I want to get all the instances of Table 1 linked with a Table 2 instance not NULL.
The CakePHP doc helped me finding the exists() and isNotNull() conditions but I did not achieve.
Here is how I imagined:
$Table1 = TableRegistry::get('Table1')->find('all')->contain([
'Table2' => [
'sort' => ['Table2.created' => 'desc']
]
])->where([
'Table1.id' => $id,
'Table2 IS NOT NULL'
]);
$this->set(compact('Table1'));
But it obviously does not work.
edit : I expect to get all the line of the Table1 which contain existing Not NULL Table2 line(s) linked. The problem is in the 'where' array with the 'Table2 IS NOT NULL', it does not work.
And without this line 'Table2 IS NOT NULL', I get all the Table1 line which contain a Table2 line or not (because some line of Table1 are not linked at all and I don't want to get these lines).
Assuming the tables follow convention and use "id" as the primary key, I suggest the easiest fix would be testing that field for NOT NULL.
I.e., replace this:
'Table2 IS NOT NULL'
with this:
'Table2.id IS NOT NULL'
or:
'Table2.id !=' => null
or:
'Table2.id >' => 0
I've successfuly get the Table1 lines with its existing Table2 line(s) associated.
query = TableRegistry::get('Table1')->find();
$query->select(['Table1.id', 'count' => $query->func()->count('Table2.id')])->matching('Table2')->group(['Table1.id'])->having(['count
>' => 0]);
$table1Ids = [];
foreach ($query as $z)
{
$table1Ids[] = $z->id;
}
$table1= TableRegistry::get('Table1')->find('all')->contain([
'Table2' => [
'sort' => ['Table2.created' => 'desc']
]
])->where([
'id IN' => $table1Ids,
]);

Sort query results by nested association in cakephp 3

The Problem:
I have a cakephp 3.x query object with two nested associations, Organizations.Positions.Skills, that is being set to a view variable, $Organizations. I'm trying to sort the query's resulting top level array by a column in the first nested association. That is, I want to sort $Organizations by a column in Positions, specifically Positions.Ended).
public function index()
{
$this->loadModel('Organizations');
$this->set('Organizations',
$this->Organizations->find('all')
->contain([ //eager loading
'Positions.Skills'
])
);
}
Model Info
Organizations has many Positions
Positions has many Skills
Research I've Done
order option
According to the cookbook find() has an order option: find()->order(['field']); or find('all', ['order'=>'field DESC']);
However, this only applies to fields in the table find() is being called upon. In this case, Organizations. For example, this is how it's typically used.
//This works.
//Sorts Organizations by the name of the organization in DESC.
$this->loadModel('Organizations');
$this->set('Organizations',
$this->Organizations->find('all')
->contain([ //eager loading
'Positions.Skills'
])
->order(['organization DESC'])
);
but, trying to use it for nested associations doesn't work:
$this->set('Organizations',
this->Organizations->find(...)
->contain(...)
->order(['Organizations.Positions.ended DESC'])
);
Error: SQLSTATE[42S22]: Column not found: 1054 Unknown column 'Organizations.Positions.ended' in 'order clause'
and altering it to refer to the field that'll be nested doesn't work either:
//also doesn't work.
$this->set('Organizations',
$this->Organizations->find(...)
->contain(...)
->order([Positions.ended DESC])
);
Error: SQLSTATE[42S22]: Column not found: 1054 Unknown column 'Positions.ended' in 'order clause'
In both cases, the sql error is created when cakephp executes the PDO statement generated by the query.
sort option
Similarly, according to the cookbook, eager loading / associations has the 'sort' option:
$this->loadModel('Organizations');
$this->set('Organizations',
$this->Organizations->find('all')
->contain([ //eager loading
'Positions.Skills',
'Positions' => [
'sort' => ['Positions.ended'=>'DESC']
]
])
);
But, this only sorts the nested association.Specifically, it sorts the associations that are nested. It does not sort the entire resulting set by the nested association (ergo, a multidimensional sort).
For example:
The Organization, Organization C (org id 1), has two positions:
Position 5. Ended 2012
Position 4. Ended 2014
And the Organization, Organization B (org id 2), has two positions:
Position 3 Ended 2013
Position 2 Ended 2015
The above code and data results in the following array being generated when the query is evaluated:
Organizations => [
0 => [
'organization' => 'Organization A',
positions => [
0 => [
'position' => 'Position 1',
'ended' => '2016'
]
]
],
1 => [
'organization' => 'Organization C',
'positions' => [
0 => [
'position' => 'Position 4',
'ended' => '2014'
],
1 => [
'position' => 'Position 5',
'ended' => '2012'
]
]
],
2 => [
'organization' => 'Organization B',
'positions' => [
0 => [
'position' => 'Position 2',
'ended' => '2015'
],
1 => [
'position' => 'Position 3',
'ended' => '2013'
]
]
]
]
other research
Likewise the following stackoverflow questions came up in my research:
http://stackoverflow.com/questions/26859700/cakephp-order-not-working
http://stackoverflow.com/questions/17670986/order-by-doesnt-work-with-custom-model-find-while-paginating
http://stackoverflow.com/questions/18958410/cakephp-paginate-and-sort-2nd-level-association
http://stackoverflow.com/questions/34705257/cakephp-paginate-and-sort-hasmany-association
Furthermore, I do know that PHP has its own sorting functions like sort() and multisort(); but, those can only be called once the query has been evaluated (by foreach). Alternatively, there's calling $organization->toArray() then using multisort; but, this would have to be done in the view, would break the MVC convention of separations of concerns (data and queries are manipulated by the controller and model, not the view!), and would be quite inefficient as it'll be called while the page is loading.
How then, do I sort a cakephp query by its nested associations?
Or, put more simply, how do I order/sort the query to produce the following array upon evaluation:
Organizations => [
0 => [
'organization' => 'Organization A',
'positions' => [
0 => [
'position' => 'Position 1',
'ended' => '2016'
]
]
],
0 => [
'organization' => 'Organization B',
'positions' => [
0 => [
'position' => 'Position 2',
'ended' => '2015'
],
1 => [
'position' => 'Position 3',
'ended' => '2013'
]
]
],
1 => [
'organization => 'Organization C',
'positions' => [
0 => [
'position' => 'Position 4',
'ended' => '2014'
],
1 => [
'position' => 'Position 5',
'ended' => '2012'
]
]
]
]
Background & Context:
I'm building a [portfolio website][7] for myself with cakephp 3.2 to showcase my web dev skills and assist in my quest for a dev career. For my resume page, I'm organizing the massive amount of data with nested accordions to mimic the resume style recruiters would expect to see on an actual resume. As a result, my view does the following:
Looping through the top level view variable (Organizations)
Rendering the organization details
Looping through that organization's positions (still inside 1)
render the position details
loop through the position's relevant skills
render each skill w/ the appropriate link to filter by that skill.
List item
Only hasOne and belongsTo associations are being retrieved via a join on the main query. hasMany associations are being retrieved in a separate queries, hence the errors when you try to refer to a field of Positions.
What you want should be fairly easy to solve, on SQL level, as well as on PHP level.
SQL level
On SQL level you could join in Positions, and order by a computed max(Positions.ended) column, like:
$this->Organizations
->find('all')
->select(['max_ended' => $this->Organizations->query()->func()->max('Positions.ended')])
->select($this->Organizations)
->contain([
'Positions.Skills',
'Positions' => [
'sort' => ['Positions.ended' => 'DESC']
]
])
->leftJoinWith('Positions')
->order([
'max_ended' => 'DESC'
])
->group('Organizations.id');
And that's all, that should give you the results that you want. The query will look something like:
SELECT
MAX(Positions.ended) AS max_ended,
...
FROM
organizations Organizations
LEFT JOIN
positions Positions
ON Organizations.id = (
Positions.organization_id
)
GROUP BY
Organizations.id
ORDER BY
max_ended DESC
PHP level
On PHP level it's also pretty easy to solve to using collections (note that queries are collections - kind of), however it would only make sense if you'd intend to retrieve all rows, or must deal with a set of unordered rows for whatever reason... something like:
$Organizations->map(function ($organization) {
$organization['positions'] =
collection($organization['positions'])->sortBy('ended')->toList();
return $organization;
});
$sorted = $sorted->sortBy(function ($organization) {
return
isset($organization['positions'][0]['ended']) ?
$organization['positions'][0]['ended'] : 0;
});
This could also be implemented in a result formatter, so things happen on controller or model level if you insist.
$query->formatResults(function ($results) {
/* #var $results \Cake\Datasource\ResultSetInterface|\Cake\Collection\CollectionInterface */
$results = $results->map(function ($row) {
$row['positions'] =
collection($row['positions'])->sortBy('ended')->toList();
return $row;
});
return $results->sortBy(function ($row) {
return isset($row['positions'][0]['ended']) ?
$row['positions'][0]['ended'] : 0;
});
});
See
Cookbook > Database Access & ORM > Query Builder > Using SQL Functions
Cookbook > Database Access & ORM > Query Builder > Using leftJoinWith
Cookbook > Database Access & ORM > Query Builder > Queries Are Collection Objects
Cookbook > Database Access & ORM > Query Builder > Adding Calculated Fields
Cookbook > Collections > Sorting
This worked for me:
$this->Organizations->find('all')
->contain(['Positions' => ['sort' => ['Positions.ended' => 'DESC']]])
->contain('Postions.Skills');

MySQL result multiple arrays

i.e : i have 2 tables
Product ( id, name )
Photo ( id, name, photo_id )
And I need to get result in array like this:
array(
'id' => 1,
'name' => product,
'photos' => array(
array('id' => 1, 'name' => 'photo1')
array('id' => 2, 'name' => 'photo2')
)
}
Is it possible in PHP using clear SQL?
I know that is possible to get 2 arrays and connect it but I have many records and I dont want to wase time to quering.
You have to add a foreign_key in your photo table "product_id".
Then create a method getPhotos() in your Product class with will collect all photos for your product.
Is it possible in PHP using clear SQL?
Not in a single SQL call. With a single call, this is the closest you can get:
array(
'id' => 1,
'name' => product,
'photo_id' => 1,
'photo_name' => 'photo1')
),
array(
'id' => 1,
'name' => product,
'photo_id' => 2,
'photo_name' => 'photo2')
)
Your only choice for the format you want is to run queries separately or to combine them into the data structure you want.
As mentioned, this is not possible with SQL. SQL is based on the relational model which is a 1-Normal-Form data model. That means, the result relation is also flat (no nested relations in a relation).
However, there are good frameworks which generate intermediary models in your corresponding target language (e.g. Python, Java, ...) that circumvent the impression of a flat data model. Check for example Django.
https://docs.djangoproject.com/en/1.8/topics/db/models/
Moo

Categories