How to get the following array from database? - php

Ok to make it more clear:
I am Using doctrine
I have a table Brands and Products
Brand
id
name
Product
id
name
brand_id
I have a lot of brands and Products of those brands in the database.
I would like to retrieve List of brands(+ count of its products) Grouped by Brand.name's first latter.
ex:
array(
n => array(
0 => array('Nike', 4 ),
1 => array('North Pole', 18)
.....
)
.....
)
So my question was can this be done with one query in a efficient way.
I really don't wan't to run separate queries for each brand.name's first latter.
Doctrines "Hierarchical Data" cross my mind but I believe it for different thing?.
thanks

If you are going to use this form of result more than once, it might be worthwhile to make the formatting into a Hydrator, as described here.
In your case, you can create a query that select 3 columns
first letter of brand.name
brand.name
count(product.id)
Then hydrate the result
$results = $q->execute(array(), 'group_by_first_column');

You cannot take it from database in that way, but you can fetch data as objects or arrays and then transform it to described form. Use foreach loops.

When using Doctrine you can also use raw SQL querys and hydrate arrays instead of objects. So my Solution would be to use a native SQL Query:
SELECT
brand.name,
count(product.id)
FROM
brand
JOIN
product ON
brand.id=product.brand_id
GROUP BY
brand.id ORDER BY brand.name;
And then iterate in PHP over the result to build the desired array. Because the Result is ordered by Brand Name this is quite easy. If you wasn't to keep database abstraction I think it should also be possible to express this query in DQL, just hydrate an array instead of objects.

Related

Getting count fields of contained models using WHERE conditions

I have three models, Companies, events and assistances, where the assistances table stores the event_id and the company_id. I'd like to get a query in which the total assistances of the company to certain kind of events are stored. Nevertheless, as all these counts are linked to the same table, I don't really know how to build this query effectively. I have the ids of the assistances to each kind of event stored in some arrays, and then I do the following:
$query = $this->Companies->find('all')->where($conditions)->order(['name' => 'ASC']);
$query
->select(['total_assistances' => $query->func()->count('DISTINCT(Assistances.id)')])
->leftJoinWith('Assistances')
->group(['Companies.id'])
->autoFields(true);
Nevertheless, I don't know how to get the rest of the Assistance count, as I would need to count not all the distinct assistance Ids but only those taht fit to certain conditions, something like ->select(['assistances_conferences' => $query->func()->count('DISTINCT(Assistances.id)')])->where($conferencesConditions) (but obviously the previous line does not work. Is there any way of counting different kind of assistances in the query itself? (I need to do it this way because I then plan to use pagination and sort the table taking those fields into consideration).
The *JoinWith() methods accept a second argument, a callback that receives a query builder used for affecting the select list, as well as the conditions for the join.
->leftJoinWith('Assistances', function (\Cake\ORM\Query $query) {
return $query->where([
'Assistances.event_id IN' => [1, 2]
]);
})
This would generate a join statement like this, which would only include (and therefore count) the Assistances with an event_id of 1 or 2:
LEFT JOIN
assistances Assistances ON
Assistances.company_id = Companies.id AND
Assistances.event_id IN (1, 2)
The query builder passed to the callback really only supports selecting fields and adding conditions, more complex statements would need to be defined on the main query, or you'd possibly have to switch to using subqueries.
See also
Cookbook > Database Access & ORM > Query Builder > Filtering by Associated Data

Paginate and search associated query with dynamic AJAX loading in CakePHP 3

I am working on an application in CakePHP 3.7.
We have 3 database tables with the following hierarchy. These tables are associated correctly according to the Table classes:
regulations
groups
filters
(The associations are shown in a previous question: CakePHP 3 - association is not defined - even though it appears to be)
I can get all of the data from all three tables as follows:
$regulations = TableRegistry::getTableLocator()->get('Regulations');
$data = $regulations->find('all', ['contain' => ['Groups' => ['Filters']]]);
$data = $data->toArray();
The filters table contains >1300 records. I'm therefore trying to build a feature which loads the data progressively via AJAX calls as the user scrolls down the page similar to what's described here: https://makitweb.com/load-content-on-page-scroll-with-jquery-and-ajax/
The problem is that I need to be able to count the total number of rows returned. However, the data for this exists in 3 tables.
If I do debug($data->count()); it will output 8 because there are 8 rows in the regulations table. Ideally I need a count of the rows it's returning in the filters table (~1300 rows) which is where most of the data exists in terms of the initial load.
The problem is further complicated because this feature allows a user to perform a search. It might be the case that a given search term exists in all 3 tables, 1 - 2 of the tables, or not at all. I don't know whether the correct way to do this is to try and count the rows returned in each table, or the rows overall?
I've read How to paginate associated records? and Filtering By Associated Data in the Cake docs.
Additional information
The issue seems to come down to how to write a query using the ORM syntax Cake provides. The following (plain MySQL) query will actually do what I want. Assuming the user has searched for "Asbestos":
SELECT
regulations.label AS regulations_label,
groups.label AS groups_label,
filters.label AS filters_label
FROM
groups
JOIN regulations
ON groups.regulation_id = regulations.id
JOIN filters
ON filters.group_id = groups.id
WHERE regulations.label LIKE '%Asbestos%'
OR groups.label LIKE '%Asbestos%'
OR filters.label LIKE '%Asbestos%'
ORDER BY
regulations.id ASC,
groups_label ASC,
filters_label ASC
LIMIT 0,50
Let's say there are 203 rows returned. The LIMIT condition means I am getting the first 50. Some js on the frontend (which isn't really relevant in terms of how it works) will make an ajax call as the user scrolls to the bottom of the page to re-run this query with a different limit (e.g. LIMIT 51, 100 for the next set of results).
The problem seems to be two fold:
If I write the query using Cake's ORM syntax the output is a nested array. Conversely if I write it in plain SQL it's returning a table which has just 3 columns with the labels that I need to display. This structure is much easier to work with in terms of outputting the required data on the page.
The second - and perhaps more important issue - is that I can't see how you could write the LIMIT condition in the ORM syntax to make this work due to the nested structure described in 1. If I added $data->limit(50) for example, it only imposes this limit on the regulations table. So essentially the search works for any associated data on the first 50 rows in regulations. As opposed to the LIMIT condition I've written in MySQL which would take into consideration the entire result set which includes the columns from all 3 tables.
To further elaborate point 2, assume the tables contain the following numbers of rows:
regulations: 150
groups: 1000
filters: 5000
If I use $data->limit(50) it would only apply to 50 rows in the regulations table. I need to apply the LIMIT the result set after searching all rows in all 3 tables for a given term.
Creating that query using the query builder is pretty simple, if you want joins for non-1:1 associations, then use the *JoinWith() methods instead of contain(), in this case innerJoinWith(), something like:
$query = $GroupsTable
->find()
->select([
'regulations_label' => 'Regulations.label',
'groups_label' => 'Groups.label',
'filters_label' => 'Filters.label',
])
->innerJoinWith('Regulations')
->innerJoinWith('Filters')
->where([
'OR' => [
'Regulations.label LIKE' => '%Asbestos%',
'Groups.label LIKE' => '%Asbestos%',
'Filters.label LIKE' => '%Asbestos%',
],
])
->order([
'Regulations.id' => 'ASC',
'Groups.label' => 'ASC',
'Filters.label' => 'ASC',
]);
See also
Cookbook > Database Access & ORM > Query Builder > Using innerJoinWith
Cookbook > Database Access & ORM > Query Builder > Adding Joins

how to add sub query in yii when subquery return array

I an working on an API, in which I have 2 tables 1 for library and 2nd for issued books. We can issue 5 books to any user. I am working on an API in which I need to check if any user already assigned a book then he same book will not issue to him. I used a sub query for it. but it returns an array. So I can't get the data according my need. I need that if We issue 3 book to him than the books will show in other color. my code is:
$data1=(new \yii\db\Query())->select('book_id')->distinct()->from('issuedBooks')
->where('user_id=:user_id', [':user_id'=>310])->all();
$workout= (new \yii\db\Query())->select('*')->from('library')
->leftJoin(['issuedBooks'=>$data1],'issuedBooks.book_id=library.book_id')
->all();
$data1 return an array so my second query not working properly.
Preface: I know nothing of Yii Query Builder. But I do know SQL. And usually if you want to pass parameterized queries from an array you would list out the items of array with OR or IN() using the WHERE clause not JOIN.
Using above link, consider removing the joins and using a where clause in second query, passing the array, $data1 as a parameter:
->where(array('in', 'book_id', $data1))

how to fetch specific and unique data from database different tables in php

i want to fetch data from database three tables all are linked together.
I'm facing problem, i have tried many methods to it like merge, unique and separate list.
following is the code.
<?php
$conn=new mysqli("localhost","root","","db");
$rows=$conn->query("select Account,Amount, event_name,event_description from donations
LEFT JOIN accounts
ON accounts.ID_Account=donations.ID_Account
LEFT JOIN events
ON events.event_id=donations.event_id ");
$rows1=$conn->query("SELECT Account from accounts");
while((list($Account, $Amount, $event_name,$event_description)=$rows->fetch_row()) and (list($Account)=$rows1->fetch_row()))
{
echo "<tr><td> $Account</td><td> $Amount </td><td>$event_name</td><td>$event_description</td></tr>";
}
?>
Now The problem is, it shows duplicate data from accounts and also I want it to be like if there is no donation $Amount for the account $Account it should not show donation in front of Account, but it is showing. is there any way i can make it working the way i want?
Many Thanks.
You are looking for some data hydration methods. Now you will get A * B * C = X rows with your resulting data because you are doing multiple joins and your resulting array from mysql will always have only one dimension and therefore a lot of duplicated data.
What you want is array (or object) with more than one dimension. I assume you want a resulting array with something like:
$result = array(
'account1' => array('event1', 'event2'),
'account1' => array('event3', 'event4', 'event5')
);
There are multiple solutions to this:
Write your own data hydration method to create a new array with values from your result
Do three single queries instead of one query with JOIN, then merge the results (can even be faster!)
Use doctrine (a php database abstraction layer) which can do exactly what you want without too much trouble.
If you start to code new app I would recommend 3.) to you:
http://doctrine-orm.readthedocs.org/en/latest/tutorials/getting-started.html

Doctrine 2 find pages having attributes

I need to fill query builder with filter conditions of page attributes. I got Enities "Page", "Attribute" and "Value"
Page: id, name, etc
Attribute: id, name, etc
Value: page_id, date, string, numeric
I need to get all pages having attribute values selected in form.
So i get Query Builder:
$qb->select('p')
->from(Page p)
->leftJoin(p.values);
Can I use "having" or "where" clauses to do like?
$qb->add('having', 'a.id = :attr1_id AND a.value = :attr1_val')
$qb->add('having', 'a.id = :attr2_id AND a.value = :attr2_val')
UPDATE: No i can't. in this way it can get page with attr1_id = attr2_value AND attr2_id = attr1_value and all conditions will be true, but result is wrong.
or I should add custom join for each value?
UPDATE: here's working exapmle:
//attr1.intval=:a1 (or LIKE, BETWEEN, etc compare)
$comp_expression=$aliace.'.'.$value_field.'=:a'.$this->getId();
//INNER JOIN attribute attr1 ON attr1.type=5 AND attr1.intval=:a1
$qb->innerJoin($value_class, $aliace , 'WITH', $aliace.'.type='.$this->getId().' AND '.$comp_expression);
//where attr1.intval IS NOT NULL
$qb->andWhere($aliace.'.'.$value_field.' IS NOT NULL');
$qb->setParameter('a'.$this->getId(),$value);
UPDATE: So the only way to do key+value filtring is to add join for each filter condition? I got 27 attributes now, so need to build query with 27 joins?
Is there another way to this better? Sorry if this is duplicate, cant find keywords to find same questions.
UPDATE: Maybe i shuld get out of sql query limits and create mysql procedure for it?
UPDATE: Can i use somthing like this in doctrine? MySQL optimization on filtering key-value pairs as records
Model
The model you are using here is called Entity-Attribute-Value (EAV). If you weren't aware of that I suggest you read about the pro's and con's first.
One of the con's is that searching in a EAV model is difficult and very inefficient.
AND queries
It seems you want to query for Entities that have a specific Value for a specific Attribute and another specific Value for a specific Attribute. These types of queries are not very efficient in the EAV model.
Let's assume your entities are set up as:
Entity: id, attributes (one-to-many), values (one-to-many)
Attribute: id, name, entity (many-to-one), values (one-to-many)
Value: id, content, entity (many-to-one), attribute (many-to-one)
Let's assume you want to query for all Entities that have an Attribute color with the value blue. A Doctrine query would look like this:
SELECT e FROM Entity e
JOIN e.values v JOIN v.attribute a
WHERE a.name = 'color' AND v.content = 'blue'
(Of course you should never use values in the DQL like this, but bind them as parameters.)
Now let's assume you want to query for all Entities that have an Attribute color with the value blue and an Attribute shape with the value square. The query becomes:
SELECT e FROM Entity e
JOIN e.values v1 JOIN v.attribute a1
JOIN e.values v2 JOIN v.attribute a2
WHERE a1.name = 'color' AND v1.content = 'blue'
AND a2.name = 'shape' AND v2.content = 'square'
Now if you want to query for 3 Attribute/Value pairs, you'll need 3 of those JOIN sets. If you want to query for 4 Attribute/Value pairs, you'll need 4 of those JOIN sets. Etc.
OR queries
Let's assume you want to query for all Entities that have an Attribute color with the value blue or an Attribute shape with the value square. The query becomes:
SELECT e FROM Entity e
JOIN e.values v JOIN v.attribute a
WHERE (a.name = 'color' AND v.content = 'blue')
OR (a.name = 'shape' AND v.content = 'square')
You can see this is more efficient, you won't need those additional JOIN sets for every additional Attribute/Value pair.
Mixing AND OR
I'm going to be short here: this will become a query nightmare. I strongly advise you not to do this.
Alternative
If your application is going to rely heavily on these types of queries (especially AND and mixing AND OR), I suggest you consider a different type of storage engine. Relational Databases are not really suited for this kind of thing.
You'll probably be better of using a Document Oriented Database, something like Elasticsearch, MongoDB, CouchDB, etc.
Hybrid alternative
You can also "copy" the EAV part to a Document Oriented Database and use it only for search functionality. I suggest you set up events for when changes are made to an Entity (create, update, delete). Then create listeners that persist those changes into the Document store.
This way your application can work with the Relational Database for normal operations, and the Document store for searching. In this case I advise Elasticsearch, which is extremely suitable for this kind of thing.
There is no such difference between the two methods, the two methods do exactly the same thing.
By the central point of question, I got the doubt is about the construction of query using like.
The first example you provide is workfull.
The second example you propose is not right.
On the second example, you need to define All the where clauses on andWhere method.
$qb->innerJoin('a1 ON p.id=a1.page_id');
$qb->andWhere('a1.attribute_id=:attr1_id ');
$qb->andWhere('a1.value=:attr1_val');
$qb->andWhere('a1 is not null');
All are you doing is creating a sql by a third api, Doctrine ORM is one of the most successful orm tool for php, knowing how to use it will let you to experience the ORM power, by doctrine implementation all you need to know are there:
Doctrine Create Query Builder -> The same are you using.
Doctrine Query Builder
Doctrine Create Query -> Simplest form
Query Language

Categories