COUNT and GROUP BY using Zend Framework 2 and tableGateway - zend-framework2

In Zend Framework 2, using tableGateway, I want to run the following SQL query:
SELECT categories.category_name, COUNT(forums.forum_id)
FROM categories LEFT JOIN forums
ON categories.category_id = forums.category_id
GROUP BY categories.category_name;
Problem is that I simply don't know how to do it. I know how to use $select->join() for example, but I can't figure out how to also do a COUNT and GROUP BY.
What I want with my SQL: I have 2 tables; categories and forums. I want to select all the categories from categories and for each category I want the amount of forums.

Someone on another forum gave me the correct answer, and this works for me. Thought I would share it in case anyone else is having a similar question. Here is how I have it now:
use Zend\Db\Sql\Expression;
$resultSet = $this->tableGateway->select(function (Select $select)
{
// Select columns and count the forums.
$select->columns(array(
'category_name',
'forumsCount' => new Expression('COUNT(forums.forum_id)')
));
// Left-join with the forums table.
$select->join('forums', 'categories.category_id = forums.category_id', array(), 'left');
// Group by the category name.
$select->group('categories.category_name');
});
return $resultSet;

Your query looks right. Does it work as expected when you run it directly on the database.
I think you might just need to execute the raw query using an adapter.
$sql = "SELECT categories.category_name, COUNT(forums.forum_id) FROM categories LEFT JOIN forums ON categories.category_id = forums.category_id GROUP BY categories.category_name";
$statement = $this->adapter->query($sql);
return $statement->execute();

Related

Use Join In Delete Zend 2

How can I use join in zend 2 delete query?
For example :
DELETE t1 FROM t1 LEFT JOIN t2 ON t1.id=t2.id WHERE t2.id IS NULL;
I won't write query directly.
$Delete = new Delete();
$Delete->from("example");
$Delete->join // join is not available
it appears you are using Zend\Db\Sql\Delete. There is no method join for this class according to the documentation.
It appears it is not the "zend way" to delete multiple row from multiple table at once.
You could either
Delete the rows with multiple query (one for each table)
Make your own query using the dbAdapter provided by zend
hope this will help you

Using distinct in a join

I'm still a novice at SQL and I need to run a report which JOINs 3 tables. The third table has duplicates of fields I need. So I tried to join with a distinct option but hat didn't work. Can anyone suggest the right code I could use?
My Code looks like this:
SELECT
C.CUSTOMER_CODE
, MS.SALESMAN_NAME
, SUM(C.REVENUE_AMT)
FROM C_REVENUE_ANALYSIS C
JOIN M_CUSTOMER MC ON C.CUSTOMER_CODE = MC.CUSTOMER_CODE
/* This following JOIN is the issue. */
JOIN M_SALESMAN MS ON MC.SALESMAN_CODE = (SELECT SALESMAN_CODE FROM M_SALESMAN WHERE COMP_CODE = '00')
WHERE REVENUE_DATE >= :from_date
AND REVENUE_DATE <= :to_date
GROUP BY C.CUSTOMER_CODE, MS.SALESMAN_NAME
I also tried a different variation to get a DISTINCT.
/* I also tried this variation to get a distinct */
JOIN M_SALESMAN MS ON MC.SALESMAN_CODE =
(SELECT distinct(SALESMAN_CODE) FROM M_SALESMAN)
Please can anyone help? I would truly appreciate it.
Thanks in advance.
select distinct
c.customer_code,
ms.salesman_code,
SUM(c.revenue_amt)
FROM
c_revenue c,
m_customer mc,
m_salesman ms
where
c.customer_code = mc.customer_code
AND mc.salesman_code = ms.salesman_code
AND ms.comp_code = '00'
AND Revenue_Date BETWEEN (from_date AND to_date)
group by
c.customer_code, ms.salesman_name
The above will return you any distinct combination of Customer Code, Salesman Code and SUM of Revenue Amount where the c.CustomerCode matches an mc.customer_code AND that same mc record matches an ms.salesman_code AND that ms record has a comp_code of '00' AND the Revenue_Date is between the from and to variables. Then, the whole result will be grouped by customer code and salesman name; the only thing that will cause duplicates to appear is if the SUM(revenue) is somehow different.
To explain, if you're just doing a straight JOIN, you don't need the JOIN keywords. I find it tends to convolute things; you only need them if you're doing an "odd" join, like an LEFT/RIGHT join. I don't know your data model so the above MIGHT still return duplicates but, if so, let me know.

Laravel eloquent selecting most recent row from joined table

I have two tables, Project and Projectnote
There is a one to many relationship between project and projectnote.
I want to be able to list my projects and select the most recent projectnotes based on the created_at date.
Is this possible to do in Eloquent, I can't figure it out.
Thanks for any help!
Edit: so far I have a basic query (below) that joins the two tables. However, this only selects projects where a note exists and I get multiple rows where there are several notes per project.
$projects = Project::join('projectnotes', 'projectnotes.project_id', '=', 'projects.id')
->select(array('projects.*', 'projectnotes.note as note'))
->get();
You can join the table on the latest value:
->join('t1', function($join) {
$join->on('t1.t2_id', '=', 't2.id')
->on('t1.id', '=', DB::raw("(select max(id) from t1 WHERE t1.t2_id = t2.id)"));
If you also want to join where t1 does not have a value, use a leftJoin. In your case this would look like this:
$projects = Project::leftJoin('projectnotes', function($join) {
$join->on('projectnotes.project_id', '=', 'projects.id')
->on('projectnotes.id', '=', DB::raw("(SELECT max(id) from projectnotes WHERE projectnotes.project_id = projects.id)"));
})
->select(array('projects.*', 'projectnotes.note as note'))
I would suggest you try to order by id in that specific scenario; the below is my suggested solution for you based on your code:
$projects = Project::join('projectnotes', 'projectnotes.project_id', '=', 'projects.id')
->select(array('projects.*', 'projectnotes.note as note'))
->orderBy('projectnotes.id')
->latest('projectnotes.id')
->get()->unique();
The code above works on Laravel 5.6; your can try it out.
In your project model, make the notes relationship function look something like:
public function notes() {
return $this->hasMany('Note');
}
Then you can get the most recent Note of the Project with primary key 1 by chaining orderBy and first:
Project::find(1)->notes()->orderBy('created_at', 'desc')->first();
Also, is your note table called 'Note' or 'Notes'? (Keeping with Laravel style, it should be named singular as I have it in this code)

Difficulties with SQL query and COUNT

thanks in advnace for reading this. I'm not really good with SQL so please pardon any stupid mistake...
Here is the deal, I have four tables (i'm only going to give the basic fields, and dependencies between tables, for the sake of simplicity):
Company: companyId, companyName
User: userId, userName
Project: projectId, projectUserId, projectCompanyId, projectDate
Study: studyProjectId
The dependencies are like so:
A project is for a client (projectUserId) and carried out by a company (projectCompanyId)
There can be many studies for the same project, but each study is for one project (studyProjectId)
Here is the kind of request I'd like to write, but it doesn't work right now:
SELECT
project.projectId,
company.companyName,
user.userName,
COUNT( study.studyId ) AS numberStudies
FROM project, company, user, study
WHERE company.companyId = project.projectCompanyId,
AND user.userId = project.projectUserId,
AND study.studyProjectId = project.projectId
ORDER BY company.companyId, user.userId, project.projectDate;
It returns one record for which numberStudies equals the total number of studies. If I remove the COUNT from the SELECT, then I get the type of result I want, but without the column numberStudies (of course). Hoping you understand what I'm trying to get, what am I doing wrong here?
Thanks again in advance :)
EDIT: If possible, I'd like the request to show records even when numberStudies is 0.
As in the comments, you need a GROUP BY clause when you want to have aggregate results (like it seems you want: "Number of Studies per project, company and user"). So, the first thing to do is add:
GROUP BY project.projectId, company.companyName, user.userName
Notice that the three columns are exactly the three that you have (unaggregated) in the SELECT list.
SELECT
project.projectId,
company.companyName,
user.userName,
COUNT(study.studyId) AS numberStudies
FROM project, company, user, study
WHERE company.companyId = project.projectCompanyId,
AND user.userId = project.projectUserId,
AND study.studyProjectId = project.projectId
GROUP BY project.projectId, company.companyName, user.userName
ORDER BY company.companyId, user.userId, project.projectDate ;
This will show what you want but there are still a few issues:
First, you are using the old (SQL-89) syntax of implicit joins with the conditions in the WHERE clause. This syntax is not deprecated but the new (well, 20 years old SQL-92) syntax with the JOIN keyword has several advantages.
We can add aliases for the tables for readability.
There may be two companies or users with same name so we should group by their IDs, not only their names.
One advantage of explicit JOIN syntax is that it's easy to have results when there are no rows to join (as you want to show when there is no studies for a project). Just LEFT JOIN the study table.
So, the query becomes:
SELECT
p.projectId,
c.companyName,
u.userName,
COUNT(s.studyId) AS numberStudies
FROM
project AS p
JOIN
company AS c ON c.companyId = p.projectCompanyId
JOIN
user AS u ON u.userId = p.projectUserId
LEFT JOIN
study AS s ON s.studyProjectId = p.projectId
GROUP BY
c.companyId,
u.userId,
p.projectId,
c.companyName, u.userName
ORDER BY
c.companyId,
u.userId,
p.projectDate ;
you probably need a LEFT JOIN between study and project

Nested Select with ZF2

Trying to get a nested select using Zend\Db\Sql\Select and can't see anything at all in the documentation or on google.
Wanting to do something like this:
SELECT
table1.*,
(SELECT x,y,z FROM table2 WHERE table2.a = table1.a) as b
FROM table1
Without the nested select, it would look something like this:
$select = new Zend\Db\Sql\Select;
$select
->columns(array(
'*'
))
->from('table1')
ZF1 looked about creating a subSelect item and then adding it as an Expression inside the list of columns but in ZF2 it complains about an Expression needing to be a string.
Edit: The nested-select needs to be as a column as I end up with multiplied rows when using GROUP BY on same column name. This is the correct query I'm trying to get into Zend\Db\Sql\Select:
SELECT
users.id,
(SELECT count(explorations.id) FROM explorations WHERE user_id = users.id) as total_explorations,
count(villages.id)
FROM
users
INNER JOIN
villages
on (villages.user_id = users.id)
GROUP BY
users.id
Ralph Schindler has a repository of different DB patterns that he has specifically implemented in Zend\Db. Here's one for subselects: https://github.com/ralphschindler/Zend_Db-Examples/blob/master/example-20.php
The content is this:
<?php
/** #var $adapter Zend\Db\Adapter\Adapter */
$adapter = include ((file_exists('bootstrap.php')) ? 'bootstrap.php' : 'bootstrap.dist.php');
refresh_data($adapter);
use Zend\Db\Sql;
use Zend\Db\ResultSet\ResultSet;
$sql = new Sql\Sql($adapter);
$subselect = $sql->select();
$subselect->from('artist')
->columns(array('name'))
->join('album', 'artist.id = album.artist_id', array())
->where->greaterThan('release_date', '2005-01-01');
$select = $sql->select();
$select->from('artist')
->order(array('name' => Sql\Select::ORDER_ASCENDING))
->where
->like('name', 'L%')
->AND->in('name', $subselect);
$statement = $sql->prepareStatementForSqlObject($select);
$result = $statement->execute();
$rows = array_values(iterator_to_array($result));
assert_example_works(
count($rows) == 2
&& $rows[0]['name'] == 'Lady Gaga'
&& $rows[1]['name'] == 'Linkin Park'
);
Basically, you can use one select as the value of the predicate of another select.
I would suggest you restructure you SQL query. I'm not sure which database you are using, but if you are using MySQL, the COUNT function can use the DISTINCT keyword. This way you don't count the duplicated ids. I've adjusted your SQL query to what I would use, this way you eliminate the need for inner select.
SELECT
users.id,
COUNT(DISTINCT explorations.id) AS total_explorations,
COUNT(DISTINCT villages.id) AS total_villages
FROM users
INNER JOIN villages ON villages.user_id = users.id
INNER JOIN explorations ON explorations.user_id = users.id
GROUP BY users.id
I haven't run this query, but I'm sure it should work and give you the result you want. Hopefully I'm not misunderstanding your situation. Below is the equivalent Zend Framework 2 select.
$select = $sql->select('users');
$select->columns(array('id'));
$select->join('villages',
'villages.user_id = users.id',
array(
'total_villages' => new Expression("COUNT(DISTINCT villages.id)")
)
);
$select->join('explorations',
'explorations.user_id = users.id',
array(
'total_explorations' => new Expression("COUNT(DISTINCT explorations.id)")
)
);
What you are describing is defined as a JOIN. There are some different join scenarios and i will not cover the differences of them, but the most commons would be INNER JOIN or LEFT JOIN.
And this is indeed to be found inside the ZF2-Documentation of Zend\Db\Sql#Join
The Query would look like this:
Select
t1.*,
t2.field1,
t2.field2,
t2.field3
FROM
tablename1 t1,
tablename2 t2
WHERE
t1.field = t2.field
Looking at the Documentation of ZF2-Documentation of Zend\Db\Sql#Join, i think the Select would look like this:
$select = new \Zend\Db\Sql\Select();
$select->columns(array(
'id',
'title',
// List ALL Columns from TABLE 1 - * is bad/slow!
), true)->from(array(
't1' => 'tablename1'
))->join(
'tablename2',
'id = t1.id',
array(
'username',
'email',
// List ALL Columns from TABLE 2 u want to select
),
$select::JOIN_INNER
)
Another i think: If you don't use the columns() you'd SELECT * but for your own sake, start writing good queries ;) Have more control over your code!
Can't promise that this code works, since i don't use Zend\Db on my own, but using the Documentation on the right point should get you running nonetheless.
I Hope I am getting your problem correctly...
Still not upgraded myself to ZF2 but this is one of the way you can create a nested Select statement in ZF1 if you are using MVC architecture(try it in ZF2 also).
$table1 = new table1();
$table1->select()->from('table1',array('*',
'b' => '(SELECT x,y,z FROM table2 WHERE table2.a = table1.a)',
));
Update:
Got back to this after your comment and realized that the code I've written would not work as you will not be able select multiple columns from another table into a single column(i.e x,y,z in b).
But yes it would work in case you have to perform some agg. function on the other table which gives out a single column. e.g.
$table1 = new table1();
$table1->select()->from('table1',array('*',
'b' => '(count (*) FROM table2 WHERE table2.a = table1.a)',
));
So this would work.
This way you can get some of the columns with some function performed on them.
And the rest of the columns from the other table(table2) you can get using a join.

Resources