Doctrine Query: Result creates array with index offset - php

I have a DQL string:
SELECT DISTINCT a,
b,
(
SELECT COUNT(c)
FROM ..\Entity\EntityC c
WHERE c.b = b
),
(
SELECT MAX(c2.date)
FROM ..\Entity\EntityC c2
WHERE c2.b = b
)
FROM ..\Entity\EntityA a
JOIN a.b b
...
I want to retrieve some a's, the count of c's that relate to a.b, and the date of the latest c.
My code DOES generate the results I want, but the resulting arrays have an offset in their indices:
array(size = [rows])
0 => array (size = 3)
0 => Entity(a)
1 => int(COUNT(c))
3 => date(MAX(c2.date))
1 => array (size = 3)
0 => Entity(a)
1 => int(COUNT(c))
3 => date(MAX(c2.date))
...
Why does this offset happen, and is there a way to prevent this?

Maybe it is related to this
"If you fetch multiple entities that are listed in the FROM clause then the hydration will return the rows iterating the different top-level entities." (From http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/dql-doctrine-query-language.html#fetching-multiple-from-entities )
$dql = "SELECT u, g FROM User u, Group g";
array
[0] => Object (User)
[1] => Object (Group)
[2] => Object (User)
[3] => Object (Group)
Could you verify doing a dump on the next row?
Best regards

Related

How to query employee details and relate their performance metrics?

I am fetching the id, first name, and last name of all employees that are approved and not archived. Then I am looping these results and using the ids to query other tables to collect some count data.
I tried the below code, but I am not getting the expected output.
$queryEmp = "
SELECT id, firstname, lastname
FROM tbl_employee as e
WHERE is_archive=0 and is_approved=1
";
$getQuery= $this->db->query($queryEmp);
$result= $getQuery->result();
foreach ($result as $key=> $value) {
//echo "<pre>";
print_r($value);
$day = "MONTH(date_of_created) = DATE(CURRENT_DATE())";
$group = "f_id IN (SELECT MAX(f_id) FROM tbl_fileStatus GROUP BY f_bankid)";
$condiion = "and ba.createdby='" . $value->id . "' and " . $day ." and " . $group;
$query2 = "
select
(SELECT COUNT(c_id)
FROM tbl_lead
WHERE leadstatus='1' AND ".$day.") as confirmCount,
(SELECT COUNT(f_id)
FROM tbl_fileStatus as fs
join tbl_bankdata as ba on ba.bank_id=fs.f_bankid
WHERE fs.f_filestatus=1 " . $condiion . ") as disbursed,
(SELECT COUNT(f_id)
FROM tbl_fileStatus as fs
join tbl_bankdata as ba on ba.bank_id=fs.f_bankid
WHERE fs.f_filestatus=2 ".$condiion.") as filesubmit
";
# code...
$getQuery2= $this->db->query($query2);
$result2[]=$getQuery2->result();
}
echo "<pre>";
print_r(result2);
$result looks like this:
Array (
[0] => stdClass Object (
[id] => 1
[firstname] => xyz
[lastname] => xyz
)
...
)
Second query output:
Array (
[0] => Array (
[0] => stdClass Object (
[fallowCall] => 0
[confirmCount] => 0
[disbursed] => 0
[filesubmit] => 0
)
)
...
)
How can I produce the correct results which relate respective employees with with their performance metrics? Either this structure:
Array (
[0] => stdClass Object (
[id] => 1
[firstname] => xyz
[lastname] => xyz
[somename] => (
[fallowCall] => 0
[confirmCount] => 0
[disbursed] => 0
[filesubmit] => 0
)
)
...
)
Or this structure:
Array (
[0] => stdClass Object (
[id] => 1
[firstname] => xyz
[lastname] => xyz
[fallowCall] => 0
[confirmCount] => 0
[disbursed] => 0
[filesubmit] => 0
)
...
)
I have added the my table structure and some sample data here: https://www.db-fiddle.com/f/8MoWmKPuzTrrC3DQJsiX35/0
some notes here
1) createdby is the id of table tbl_employee
2) lead_id in the bank table is the c_id of the table tbl_lead
3) f_bankid in the tbl_fileStatus is the bank_id of the table tbl_bankdata
There is actually no need to create the additional depth/complexity just to hold the count data. Furthermore, by using a combination of LEFT JOINs to connect the related tables and apply your required conditional rules, you can achieve your desired result by making just one trip to the database. This will without question provide superior efficiency for your application. LEFT JOINs are important to use so that counts can be zero without excluding employees from the result set.
Also, I should point out that your attempted query was mistakenly comparing a MONTH() value against a DATE() value -- that was never going to end well. :) In fact, to ensure that your sql is accurately isolating the current month from the current year, you need to be also checking the YEAR value.
My recommended sql:
SELECT
employees.id,
employees.firstname,
employees.lastname,
COUNT(DISTINCT leads.c_id) AS leadsThisMonth,
SUM(IF(fileStatus.f_filestatus = 1, 1, 0)) AS disbursedThisMonth,
SUM(IF(fileStatus.f_filestatus = 2, 1, 0)) AS filesubmitThisMonth
FROM tbl_employee AS employees
LEFT JOIN tbl_lead AS leads
ON employees.id = leads.createdby
AND leadstatus = 1
AND MONTH(leads.date_of_created) = MONTH(CURRENT_DATE())
AND YEAR(leads.date_of_created) = YEAR(CURRENT_DATE())
LEFT JOIN tbl_bankdata AS bankData
ON employees.id = bankData.createdby
LEFT JOIN tbl_fileStatus AS fileStatus
ON bankData.bank_id = fileStatus.f_bankid
AND MONTH(fileStatus.date_of_created) = MONTH(CURRENT_DATE())
AND YEAR(fileStatus.date_of_created) = YEAR(CURRENT_DATE())
AND fileStatus.f_id = (
SELECT MAX(subFileStatus.f_id)
FROM tbl_fileStatus AS subFileStatus
WHERE subFileStatus.f_bankid = bankData.bank_id
GROUP BY subFileStatus.f_bankid
)
WHERE employees.is_archive = 0
AND employees.is_approved = 1
GROUP BY employees.id, employees.firstname, employees.lastname
The SUM(IF()) expression is a technique used to execute a "conditional count". "Aggregate data" is formed by using GROUP BY and there are specialized "aggregate functions" which must be used to create linear/flat data from these clusters/non-flat collections of data. fileStatus data is effectively piled up upon itself due to the GROUP BY call. If COUNT(fileStatus.f_filestatus) was called, it would count all of the rows in the cluster. Since you wish to differentiate between f_filestatus = 1 and f_filestatus = 2, an IF() statement is used. This is doing the same thing as COUNT() (adding 1 for every qualifying occurrence), but it is different from COUNT() in that it does not count specific rows (within the scope of the cluster) unless the IF() expression is satisfied. Another example.
Here is a db fiddle demo with some adjustments to your supplied sample data: https://www.db-fiddle.com/f/8MoWmKPuzTrrC3DQJsiX35/4 (The result set will only be "good" while the current is June of this year.)
After saving the above string as $sql, you can simply execute it and loop through the array of objects like this:
foreach ($this->db->query($sql)->result() as $object) {
// these are the properties available in each object
// $object->id
// $object->firstname
// $object->lastname
// $object->leadsThisMonth
// $object->disbursedThisMonth
// $object->filesubmitThisMonth
}

Issue with mysql joins

I have two tables
Meetings:
m_id ProjectName
1 Test
2 Test2
Meeting_next:
id fk_m_id Meetingdate status
1 1 9-1-2018 0
1 1 10-1-2018 0
1 1 13-1-2018 1
I want to join this two tables when I left join it I will get duplicate value
Expected output
Array
(
[0] => Array
(
[m_id] => 1
[ProjectName] => test
[meetingDate] =>13-1-2018
)
[1] => Array
(
[m_id] => 2
[ProjectName] => test2
[meetingDate] =>
)
)
I tried -
select * from meetings left join meeting_next on meetings.m_id= meeting_next.fk_m_id where meeting_next.status=1 order by m_id desc
myOutput:
Array
(
[0] => Array
(
[m_id] => 1
[ProjectName] => test
[meetingDate] =>13-1-2018
) )
Bad luck I got only first Project name. I need second too. Please help me. Any help would be appreciated.
Your WHERE condition filters the number of rows to only the row of the first project.
If you want to show both projects, even if there are no meetings with status 1, you need to move the condition to the join condition:
select *
from meetings
left join meeting_next
on meetings.m_id= meeting_next.fk_m_id
and meeting_next.status=1
order by m_id desc
Now you will get all rows from meetings with only the matching entries from meeting_next.

Connecting tables when querying in MySQL

I have 3 tables: tco_articles, tco_module_eostext and tco_articles_modules. My tco_articles has unique id key. One for each article. My tco_module_eostext has unique instance_id that belongs to each article.
My tco_articles_modules contains all article_ids, but have 9 times as much instance_ids that are used in other tables.
So I can have article_id with instance_id that when you query in the tco_module_eostext will return empty.
I'd like to make a query that will return correct body text for the correct article.
So far I have:
global $wpdb;
$posts = array();
$ids = $wpdb->get_results('SELECT DISTINCT instance_id, article_id FROM tco_articles_modules', ARRAY_A);
This returns array with all the instances and ids like:
Array
(
[0] => Array
(
[instance_id] => 928615
[article_id] => 129396
)
[1] => Array
(
[instance_id] => 928616
[article_id] => 129396
)
[2] => Array
(
[instance_id] => 928617
[article_id] => 129396
)
[3] => Array
(
[instance_id] => 928618
[article_id] => 129396
)
You can see that the article_ids are the same but instance_id. When you put
$wpdb->get_results('SELECT body FROM tco_module_eostext WHERE instance_id=928617 ', ARRAY_A);
You may get empty, but for
$wpdb->get_results('SELECT body FROM tco_module_eostext WHERE instance_id=928618 ', ARRAY_A);
You could have some body text.
This is my problem. I need to go through all of them and filter out the not empty ones and assign them correct article. I managed to output the articles
foreach ($ids as $key => $value) {
$instance_ID = $value['instance_id'];
$article_ID = $value['article_id'];
$article_out = $wpdb->get_results('SELECT * FROM tco_articles WHERE id='.$article_ID.' ', ARRAY_A);
$posts[$article_ID] = $article_out[0];
}
Which returns something like:
Array
(
[129396] => Array
(
[id] => 129396
[headline] => Bla bla bla title
[intro] => This is cool article intro
[needs_review] => 0
[published] => 2014-12-16 09:17:00
[unpublished] =>
[hidden] => 0
[no_single_page] => 0
[permalink] => link-perma-link-here
[internal_slug] =>
[type_id] => 3
[thread_id] => 0
[news_id] => 0
[header_game_id] => 0
[puff_hh_id] => 0
[puff_title] =>
[hdrcol_id] => 900
[review_queued] =>
[lock_timeout] => 0
[created] => 2014-12-16 09:17:00
[updated] => 2015-01-16 13:51:30
[created_by] => 84142
[updated_by] => 84142
)
...
Now I'd like to append the body text from the tco_module_eostext table.
Is there a query I can use to do this automatically or to do this one at the time and then append to the $posts array?
The foreach method of querying is kinda slow when you have 180000+ posts.
Any help is appreciated.
If you are sure that there is always only one row in tco_module_eostext against each article_id, you can use JOIN (inner join), which will only show one row for each article_id.
SELECT a.*, t.body
FROM tco_articles a
JOIN tco_articles_modules m ON m.article_id = a.id
JOIN tco_module_eostext t ON m.instance_id = t.instance_id
//WHERE .....
But, this will not show any row of some articles if there is no entry in other two tables for that article_id. But there is still way to solve this. We can use LEFT OUTER JOIN and then make sure we only make the join if there is any row in tco_module_eostext for any instace_id. This will make sure you get at least the article info from tco_articles table when there is no data in other tables.
SELECT a.*, t.body
FROM tco_articles a
LEFT OUTER JOIN tco_articles_modules m ON m.article_id = a.id AND EXISTS ( SELECT NULL FROM tco_module_eostext WHERE instance_id = m.instance_id )
LEFT OUTER JOIN tco_module_eostext t ON m.instance_id = t.instance_id
//WHERE .....
Why not use a query with join?
Not tested!:
SELECT
article.*,
text.body as bodytext
FROM
tco_articles_modules AS modules LEFT OUTER JOIN
tco_module_eostext AS text ON
modules.instance_id = text.instance_id LEFT OUTER JOIN
tco_articles AS article ON
article.id = modules.article_id
it should get all articles with the assigned article_id from tco_articles_modules
Have a look at OUTER Join - you may want to replace this with an INNER JOIN for faster Queries. Also you ma want an WHERE condition for filtering in the query. Watch also out for the right indexing in mysql table - each joining column should be indexed - this will get much more faster results.

Pushing pointers to followers with the metadata (MySQL Query)

I’ve seen the following question on StackOverflow, Intelligent MySQL GROUP BY for Activity Streams posted by Christian Owens 12/12/12.
So I decided to try out the same approach, make two tables similar to those of his. And then I pretty much copied his query which I do understand.
This is what I get out from my sandbox:
Array
(
[0] => Array
(
[id] => 0
[user_id] => 1
[action] => published_post
[object_id] => 776286559146635
[object_type] => post
[stream_date] => 2015-11-24 12:28:09
[rows_in_group] => 1
[in_collection] => 0
)
)
I am curious, since looking at the results in Owens question, I am not able to fully get something, and does he perform additional queries to grab the actual metadata? And if yes, does this mean that one can do it from that single query or does one need to run different optimized sub-queries and then loop through the arrays of data to render the stream itself.
Thanks a lot in advanced.
Array
(
[0] => Array
(
[id] => 0
[user_id] => 1
[fullname] => David Anderson
[action] => hearted
[object_id] => array (
[id] => 3438983
[title] => Grand Theft Auto
[Category] => Games
)
[object_type] => product
[stream_date] => 2015-11-24 12:28:09
[rows_in_group] => 1
[in_collection] => 1
)
)
In "pseudo" code you need something like this
$result = $pdo->query('
SELECT stream.*,
object.*,
COUNT(stream.id) AS rows_in_group,
GROUP_CONCAT(stream.id) AS in_collection
FROM stream
INNER JOIN follows ON stream.user_id = follows.following_user
LEFT JOIN object ON stream.object_id = object.id
WHERE follows.user_id = '0'
GROUP BY stream.user_id,
stream.verb,
stream.object_id,
stream.type,
date(stream.stream_date)
ORDER BY stream.stream_date DESC
');
then parse the result and convert it in php
$data = array(); // this will store the end result
while($row = $result->fetch(PDO::FETCH_ASSOC)) {
// here for each row you get the keys and put it in a sub-array
// first copy the selected `object` data into a sub array
$row['object_data']['id'] = $row['object.id'];
$row['object_data']['title'] = $row['object.title'];
// remove the flat selected keys
unset($row['object.id']);
unset($row['object.title']);
...
$data[] = $row; // move to the desired array
}
you should get
Array
(
[0] => Array
(
[id] => 0
[user_id] => 1
[fullname] => David Anderson
[verb] => hearted
[object_data] => array (
[id] => 3438983
[title] => Grand Theft Auto
[Category] => Games
)
[type] => product
[stream_date] => 2015-11-24 12:28:09
[rows_in_group] => 1
[in_collection] => 1
)
)
It seems that you want a query where you can return the data you're actually able to get plus the user fullname and the data related to the object_id.
I think that the best effort would be to include some subqueries in your query to extract these data:
Fullname: something like (SELECT fullname FROM users WHERE id = stream.user_id) AS fullname... or some modified version using the stream.user_id, as we can't identify in your schema where this fullname comes from;
Object Data: something like (SELECT CONCAT_WS(';', id, title, category_name) FROM objects WHERE id = stream.object_id) AS object_data. Just as the fullname, we can't identify in your schema where these object data comes from, but I'm assuming it's an objects table.
One object may have just one title and may have just one category. In this case, the Object Data subquery works great. I don't think an object can have more than one title, but it's possible to have more than one category. In this case, you should GROUP_CONCAT the category names and take one of the two paths:
Replace the category_name in the CONCAT_WS for the GROUP_CONCAT of all categories names;
Select a new column categories (just a name suggestion) with the subquery which GROUP_CONCAT all categories names;
If your tables were like te first two points of my answer, a query like this may select the data, just needing a proper parse (split) in PHP:
SELECT
MAX(stream.id) as id,
stream.user_id,
(select fullname from users where id = stream.user_id) as fullname,
stream.verb,
stream.object_id,
(select concat_ws(';', id, title, category_name) from objects where id = stream.object_id) as object_data,
stream.type,
date(stream.stream_date) as stream_date,
COUNT(stream.id) AS rows_in_group,
GROUP_CONCAT(stream.id) AS in_collection
FROM stream
INNER JOIN follows ON 1=1
AND stream.user_id = follows.following_user
WHERE 1=1
AND follows.user_id = '0'
GROUP BY
stream.user_id,
stream.verb,
stream.object_id,
stream.type,
date(stream.stream_date)
ORDER BY stream.stream_date DESC;
In ANSI SQL you can't reference columns not listed in your GROUP BY, unless they're in aggregate functions. So, I included the id as an aggregation.

multiple tables left join returns same value

Is it possible to make a query that will return one time the value of the second table and set the otherones at NULL. Im stuck with this.
This is my query
return $this->db->get_results(
"
SELECT id, name, type, check_in_days, check_out_days, all_check_out_days, minimum_stay, maximum_stay, all_accom, GROUP_CONCAT( accom_id ) as accom, GROUP_CONCAT( seasons_id ) as seasons, conditional_type
FROM $this->booking_rules_table
LEFT JOIN $this->booking_rules_accom_table
ON $this->booking_rules_table.id = $this->booking_rules_accom_table.rule_id
LEFT JOIN $this->booking_rules_seasons_table
ON $this->booking_rules_table.id = $this->booking_rules_seasons_table.rule_id
GROUP BY id
"
, ARRAY_A );
This returns a array like this
[2] => Array
(
[id] => 54
[name] =>
[type] => minimum_stay
[check_in_days] => 0,1,2,3,4,5,6
[check_out_days] => 0,1,2,3,4,5,6
[all_check_out_days] => 1
[minimum_stay] => 0
[maximum_stay] => 9999
[all_accom] => 0
[accom] => 7,7,7
[seasons] => 1,3,4
[conditional_type] => compulsory
)
You see that [accom] is returning the 7 3 times because the [seasons] has 3 values.
can i fix this with my query or is there an other solution. I dont want to explode it and build the array again.

Categories