how to avoid filesort on union all query - mysql

Please i have this query which uses filesort if combined(union all), but if executed separately works fine without file sort. Here i dont want to execute it separately but want to implement the union all option. Please someone should help.
(SELECT STRAIGHT_JOIN *,
topic_post_time,
topic_title,
topic_id AS tid,
p.userid AS profile_id,
Concat(first_name, ' ', last_name) AS poster_name,
Concat(first_name, '.', last_name) AS profile_name,
forum_id,
topic_last_post_time,
sch_name_abbrev,
picture_small_url,
profile_pix_upload_path,
profile_pix_upload_path,
LEFT(post_text, 100) AS post_text
FROM _forum_topics FORCE INDEX(topic_poster)
INNER JOIN _profile p
ON ( p.userid = _forum_topics.topic_poster )
INNER JOIN _users
ON p.userid = _users.userid
INNER JOIN _class
ON _users.classid = _class.classid
INNER JOIN _unit
ON _class.unitid = _unit.unitid
INNER JOIN _department
ON _unit.deptid = _department.deptid
INNER JOIN _faculty
ON _department.facid = _faculty.facid
INNER JOIN _university
ON ( _faculty.schid = _university.schid )
WHERE _forum_topics.sub_forum_id IN( 133, 134, 135, 136,
137, 138 )
ORDER BY topic_last_post_time DESC
LIMIT 0, 20)
UNION ALL
(SELECT STRAIGHT_JOIN *,
topic_post_time,
topic_title,
topic_id AS tid,
p.userid AS profile_id,
Concat(first_name, ' ', last_name) AS poster_name,
Concat(first_name, '.', last_name) AS profile_name,
forum_id,
topic_last_post_time,
sch_name_abbrev,
picture_small_url,
profile_pix_upload_path,
profile_pix_upload_path,
LEFT(post_text, 100) AS post_text
FROM _sch_forum_topics s FORCE INDEX(topic_poster)
INNER JOIN _profile p
ON ( p.userid = s.topic_poster )
INNER JOIN _users
ON p.userid = _users.userid
INNER JOIN _class
ON _users.classid = _class.classid
INNER JOIN _unit
ON _class.unitid = _unit.unitid
INNER JOIN _department
ON _unit.deptid = _department.deptid
INNER JOIN _faculty
ON _department.facid = _faculty.facid
INNER JOIN _university
ON _faculty.schid = _university.schid
ORDER BY topic_last_post_time DESC
LIMIT 0, 20)
LIMIT
0, 20

It seems to me that the only difference is the WHERE clause. Just use an OR.
Also, ORDER BY is not accomplished in this context, not because MySQL is buggy, but because it does not have a logical meaning.
There is also an old MySQL bug which makes all UNIONs use temporary tables:
http://bugs.mysql.com/bug.php?id=50674
and ordering a temporary table generated by a query means filesort

#Federico
"There is also an old MySQL bug which makes all UNIONs use temporary tables: http://bugs.mysql.com/bug.php?id=50674 and ordering a temporary table generated by a query means filesort"
I've made an anwser because an comment doenst allow code formatting
C++ Source code off (file sql/sql_union.cc) MySQL 5.5.32 union looks like it always creates an temporary table..
See the code comments and function create_tmp_table
/*
Create a temporary table to store the result of select_union.
SYNOPSIS
select_union::create_result_table()
thd thread handle
column_types a list of items used to define columns of the
temporary table
is_union_distinct if set, the temporary table will eliminate
duplicates on insert
options create options
DESCRIPTION
Create a temporary table that is used to store the result of a UNION,
derived table, or a materialized cursor.
RETURN VALUE
0 The table has been created successfully.
1 create_tmp_table failed.
*/
bool
select_union::create_result_table(THD *thd_arg, List<Item> *column_types,
bool is_union_distinct, ulonglong options,
const char *alias)
{
DBUG_ASSERT(table == 0);
tmp_table_param.init();
tmp_table_param.field_count= column_types->elements;
if (! (table= create_tmp_table(thd_arg, &tmp_table_param, *column_types,
(ORDER*) 0, is_union_distinct, 1,
options, HA_POS_ERROR, alias)))

Related

MySQL Find Most Recent/Largest Record Per Group by order by

MySQL Find Most Recent/Largest Record Per Group by order by, and how can i minimize / shorten this query and every time it returns the first row value of the group, whereas i like to select the last row value of a group, and sort the values based on ja.id ? I know this is a bad query can anyone suggest or provide me solution to shorten this query . I have used all the necessary Column Indexes in all the tables. How to shorten the query without the use of union all .both the queries in union all are same expect in the where statement.
SELECT
a.previous_status,
a.rejected_status,
a.rejection_reason_text,
a.rejection_reason,
a.rjaId,
a.refer_applied_status,
a.title,
a.playerId,
a.gameId,
a.gamePostDate,
a.game_referal_amount,
a.country,
a.country_name,
a.state,
a.location,
a.state_abb,
a.game_type,
a.appliedId,
a.appliedStatus,
a.admin_review,
a.is_req_referal_check,
a.memberId,
a.appliedEmail,
a.game_id,
a.referred_id,
a.memStateAbb,
a.memState,
a.memZipcode,
a.memCity,
a.memCountryNme,
a.memCountry,
a.appliedMemberName,
a.first_name,
a.primary_contact,
a.last_name,
a.addressbookImage,
a.userImage,
a.last_login,
a.user_experience_year,
a.user_experience_month,
a.current_designation,
a.current_player,
a.appliedDate,
a.addressbook_id,
a.joiningdate,
a.gameStatus,
a.gameReferalAmountType,
a.gameFreezeStatus,
a.gameFreezeMsg,
a.app_assign_back_to_rp_count,
a.applied_source,
a.max_id,
a.gamesApplied,
a.gamesAppliedId,
SUM(a.totalgameApplied) AS totalgameApplied,
a.application_assign_to_rp_status,
a.rpAppliedSource,
a.applied_on
FROM
(
(
SELECT
ja.previous_status,
ja.rejected_status,
ja.rejection_reason_text,
ja.rejection_reason,
rja.id AS rjaId,
rja. STATUS AS refer_applied_status,
jp.title,
jp.user_user_id AS playerId,
jp.id AS gameId,
jp.posted_on AS gamePostDate,
jp.game_referal_amount,
jp.country,
jp.country_name,
jp.state,
jp.location,
jp.state_abb,
jp.game_type,
ja.id AS appliedId,
IFNULL(ja. STATUS, '') AS appliedStatus,
IFNULL(ja.admin_review, '') AS admin_review,
ja.is_req_referal_check,
usr.id AS memberId,
rja.email AS appliedEmail,
rja.game_id,
rja.referred_id,
mem.state_abb AS memStateAbb,
mem.state AS memState,
mem.zipcode AS memZipcode,
mem.city AS memCity,
mem.country_name AS memCountryNme,
mem.country_code AS memCountry,
usc. NAME AS appliedMemberName,
usc.first_name,
IFNULL(
mem.primary_contact,
usc.phone_number
) AS primary_contact,
usc.last_name,
usc.profileimage_path AS addressbookImage,
usr.profile_image AS userImage,
usr.last_login,
mem.user_experience_year,
mem.user_experience_month,
mem.current_designation,
mem.current_player,
rja.create_date AS appliedDate,
rja.addressbook_id,
IFNULL(ja.joining_date, '') AS joiningdate,
jp. STATUS AS gameStatus,
jp.games_referal_amount_type AS gameReferalAmountType,
jp.game_freeze_status AS gameFreezeStatus,
jp.game_freeze_message AS gameFreezeMsg,
ja.app_assign_back_to_rp_count,
ja.applied_source,
MAX(rja.id) AS max_id,
GROUP_CONCAT(
jp.title
ORDER BY
rja.create_date DESC
) AS gamesApplied,
GROUP_CONCAT(DISTINCT(jp.id)) AS gamesAppliedId,
COUNT(DISTINCT(jp.id)) totalgameApplied,
ja.application_assign_to_rp_status,
1 AS rpAppliedSource,
rja.create_date AS applied_on
FROM
(`refer_gameapplied` AS rja)
JOIN `games_post` AS jp ON `jp`.`id` = `rja`.`game_id`
JOIN `user_socialconnections` AS usc ON `rja`.`addressbook_id` = `usc`.`id`
LEFT JOIN `user_user` AS usr ON `usr`.`email` = `rja`.`email`
LEFT JOIN `user_member` AS mem ON `mem`.`user_id` = `usr`.`id`
LEFT JOIN `game_applied` AS ja ON `ja`.`id` = `rja`.`applied_id`
WHERE
`rja`.`referby_id` = '2389'
GROUP BY
`rja`.`email`
)
UNION ALL
(
SELECT
ja.previous_status,
ja.rejected_status,
ja.rejection_reason_text,
ja.rejection_reason,
jr.id AS rjaId,
jrtm. STATUS AS refer_applied_status,
jp.title,
jp.user_user_id AS playerId,
jp.id AS gameId,
jp.posted_on AS gamePostDate,
jp.game_referal_amount,
jp.country,
jp.country_name,
jp.state,
jp.location,
jp.state_abb,
jp.game_type,
ja.id AS appliedId,
IFNULL(ja. STATUS, '') AS appliedStatus,
IFNULL(ja.admin_review, '') AS admin_review,
ja.is_req_referal_check,
usr.id AS memberId,
jrtm.referto_email AS refappliedEmail,
jr.game_id,
jrtm.id,
mem.state_abb AS memStateAbb,
mem.state AS memState,
mem.zipcode AS memZipcode,
mem.city AS memCity,
mem.country_name AS memCountryNme,
mem.country_code AS memCountry,
usc. NAME AS appliedMemberName,
usc.first_name,
IFNULL(
mem.primary_contact,
usc.phone_number
) AS primary_contact,
usc.last_name,
usc.profileimage_path AS addressbookImage,
usr.profile_image AS userImage,
usr.last_login,
mem.user_experience_year,
mem.user_experience_month,
mem.current_designation,
mem.current_player,
jrtm.refer_on AS appliedDate,
jrtm.referto_addressbookid,
IFNULL(ja.joining_date, '') AS joiningdate,
jp. STATUS AS gameStatus,
jp.games_referal_amount_type AS gameReferalAmountType,
jp.game_freeze_status AS gameFreezeStatus,
jp.game_freeze_message AS gameFreezeMsg,
ja.app_assign_back_to_rp_count,
ja.applied_source,
MAX(jrtm.id) AS max_id,
GROUP_CONCAT(
jp.title
ORDER BY
jr.refer_on DESC
) AS gamesApplied,
GROUP_CONCAT(DISTINCT(jp.id)) AS gamesAppliedId,
COUNT(DISTINCT(jp.id)) totalgameApplied,
ja.application_assign_to_rp_status,
2 AS rpAppliedSource,
jrtm.refer_on AS applied_on
FROM
(`game_refer` AS jr)
JOIN `game_refer_to_member` AS jrtm ON `jrtm`.`rid` = `jr`.`id`
JOIN `games_post` AS jp ON `jp`.`id` = `jr`.`game_id`
JOIN `user_socialconnections` AS usc ON `jrtm`.`referto_addressbookid` = `usc`.`id`
LEFT JOIN `user_user` AS usr ON `usr`.`email` = `jrtm`.`referto_email`
LEFT JOIN `user_member` AS mem ON `mem`.`user_id` = `usr`.`id`
LEFT JOIN `game_applied` AS ja ON `ja`.`referred_by` = `jrtm`.`id`
WHERE
`jrtm`.`status` = '1'
AND `jr`.`referby_user_id` = '2389'
AND `jrtm`.`refer_source` NOT IN ('4')
GROUP BY
`jrtm`.`referto_email`
)
) a
GROUP BY
a.appliedEmail
ORDER BY
a.gamesAppliedId DESC
It sounds like a "groupwise-max" problem. I added a tag that you should research.
At least get rid of the columns that are not relevant to the question.
Try tossing the tables other than jrtm and jr to see if the performance problem persists. (I'm thinking that the LEFT JOINs may be red herrings.)
Try with one part of the UNION, then with the other. This may identify which of the two is more of a burden.
Some indexes to add:
rja: (referby_id, create_date)
jrtm: (status, referto_email)
jrtm: (rid, status, referto_email)
jr: (referby_user_id, refer_on)
DISTINCT is not a function. Don't use parents in DISTINCT(jp.id).

Mysql tekes too much time to excute sql query, based on multiple join

My Sql query takes more time to execute from mysql database server . There are number of tables are joined with sb_tblproperty table. sb_tblproperty is main table that contain more than 1,00,000 rows . most of table contain 50,000 rows.
How to optimize my sql query to fast execution. I have also used indexing.
indexing Explain - query - structure
SELECT `t1`.`propertyId`, `t1`.`projectId`,
`t1`.`furnised`, `t1`.`ownerID`, `t1`.`subType`,
`t1`.`fors`, `t1`.`size`, `t1`.`unit`,
`t1`.`bedrooms`, `t1`.`address`, `t1`.`dateConfirm`,
`t1`.`dateAdded`, `t1`.`floor`, `t1`.`priceAmount`,
`t1`.`priceRate`, `t1`.`allInclusive`, `t1`.`booking`,
`t1`.`bookingRate`, `t1`.`paidPercetage`,
`t1`.`paidAmount`, `t1`.`is_sold`, `t1`.`remarks`,
`t1`.`status`, `t1`.`confirmedStatus`, `t1`.`source`,
`t1`.`companyName` as company, `t1`.`monthly_rent`,
`t1`.`per_sqft`, `t1`.`lease_duration`,
`t1`.`lease_commencement`, `t1`.`lock_in_period`,
`t1`.`security_deposit`, `t1`.`security_amount`,
`t1`.`total_area_leased`, `t1`.`lease_escalation_amount`,
`t1`.`lease_escalation_years`, `t2`.`propertyTypeName` as
propertyTypeName, `t3`.`propertySubTypeName` subType,
`t3`.`propertySubTypeId` subTypeId, `Owner`.`ContactName`
ownerName, `Owner`.`companyName`, `Owner`.`mobile1`,
`Owner`.`otherPhoneNo`, `Owner`.`mobile2`,
`Owner`.`email`, `Owner`.`address` as caddress,
`Owner`.`contactType`, `P`.`projectName` as project,
`P`.`developerName` as developer, `c`.`name` as city,
if(t1.projectId="", group_concat( distinct( L.locality)),
group_concat( distinct(L2.locality))) as locality, `U`.`firstname`
addedBy, `U1`.`firstname` confirmedBy
FROM `sb_tblproperty` as t1
JOIN `sb_contact` Owner ON `Owner`.`id` = `t1`.`ownerID`
JOIN `tbl_city` C ON `c`.`id` = `t1`.`city`
JOIN `sb_propertytype` t2 ON `t1`.`propertyType`= `t2`.`propertyTypeId`
JOIN `sb_propertysubtype` t3 ON `t1`.`subType` =`t3`.`propertySubTypeId`
LEFT JOIN `sb_tbluser` U ON `t1`.`addedBy` = `U`.`userId`
LEFT JOIN`sb_tbluser` U1 ON `t1`.`confirmedBy` = `U1`.`userId`
LEFT JOIN `sb_tblproject` P ON `P`.`id` = `t1`.`projectId` LEFT
JOIN `sb_tblpropertylocality` PL ON `t1`.`propertyId` = `PL`.`propertyId`
LEFT JOIN `sa_localitiez` L ON `L`.`id` = `PL`.`localityId`
LEFT JOIN `sb_tblprojectlocality` PROL ON `PROL`.`projectId` = `P`.`id`
LEFT JOIN `sa_localitiez` L2 ON `L2`.`id` = `PROL`.`localityId`
LEFT JOIN `sb_tblfloor` F
ON `F`.`floorName` =`t1`.`floor`
WHERE `t1`.`is_sold` != '1' GROUP BY `t1`.`propertyId`
ORDER BY `t1`.`dateConfirm`
DESC LIMIT 1000
Please provide the EXPLAIN.
Meanwhile, try this:
SELECT ...
FROM (
SELECT propertyId
FROM sb_tblproperty
WHERE `is_sold` = 0
ORDER BY `dateConfirm` DESC
LIMIT 1000
) AS x
JOIN `sb_tblproperty` as t1 ON t1.propertyId = x.propertyId
JOIN `sb_contact` Owner ON `Owner`.`id` = `t1`.`ownerID`
JOIN `tbl_city` C ON `c`.`id` = `t1`.`city`
...
LEFT JOIN `sb_tblfloor` F ON `F`.`floorName` =`t1`.`floor`
ORDER BY `t1`.`dateConfirm` DESC -- yes, again
Together with
INDEX(is_sold, dateConfirm)
How can t1.projectId="" ? Isn't projectId the PRIMARY KEY? (This is one of many reasons for needing the SHOW CREATE TABLE.)
If my suggestion leads to "duplicate" rows (that is, multiple rows with the same propertyId), don't simply add back the GROUP BY propertyId. Instead figure out why, and avoid the need for the GROUP BY. (That is probably the performance issue.)
A likely case is the GROUP_CONCAT. A common workaround is to change from
GROUP_CONCAT( distinct( L.locality)) AS Localities,
...
LEFT JOIN `sa_localitiez` L ON `L`.`id` = `PL`.`localityId`
to
( SELECT GROUP_CONCAT(distinct locality)
FROM sa_localitiez
WHERE id = PL.localityId ) AS Localities
...
# and remove the JOIN

Full outer join in mysql with movie database

Hi I have the following tables and columns.
movie: ID, title
person: ID, name
involved: personID, movieID
I need to answer the question:
"Which movies have either John Travolta or Uma Thurman, but not both starred in?"
I couldn't figure out how to do this without creating new tables, so I made 2 new tables. And tried to do the full outer join on, where you dont get intersecting results. I found out that you can't do full outer joins in mysql but had to do a left join, unioned with a right join. I tried this but don't get the results I wanted at all. I have been stuck for a while now. Can anyone point me in the right direction?
This is what I have so far.
DROP TABLE IF EXISTS Umatable;
DROP TABLE IF EXISTS Johntable;
CREATE TABLE Umatable(title VARCHAR(500));
CREATE TABLE Johntable(title VARCHAR(500));
INSERT INTO Umatable
SELECT m.title
FROM movie m, person p, involved i
WHERE p.name = "Uma Thurman"
AND p.id = i.personid
AND m.id = i.movieiD;
INSERT INTO Johntable
SELECT m.title
FROM movie m, person p, involved i
WHERE p.name = "John Travolta"
AND p.id = i.personid
AND m.id = i.movieiD;
SELECT *
FROM Umatable
LEFT JOIN Johntable ON Umatable.title = Johntable.title
WHERE Johntable.title IS NULL OR Umatable.title IS NULL
UNION
SELECT *
FROM Umatable
RIGHT JOIN Johntable ON Umatable.title = Johntable.title
WHERE Johntable.title IS NULL OR Umatable.title IS NULL
I would do this using aggregation and having:
select i.movieId
from involved i join
person p
on p.id = i.personId
group by i.movieId
having sum(p.name in ('John Travolta', 'Uma Thurman')) = 1;
A count(*) inside a correlated subquery will work:
select *
from movie m
where 1 = (select count(*)
from involved i
join person p
on p.ID = i.personID
and p.name IN ('John Travolta', 'Uma Thurman')
where i.movieID = m.ID)
SQLFiddle Demo

Unable to FORCE INDEX on LEFT JOIN subquery?

I have a complicated query which involves a subquery inside a LEFT JOIN statement. The process time is ~2s.
If I replace the subquery with a temporary table, I hit the same ~2s.
If I create an index in the temporary table, I have ~0.05s.
I am trying to figure out why I can't work with Mysql USE INDEX or FORCE INDEX directly in the LEFT JOIN STATEMENT.
Here is a simple query which will return a 'check your mysql syntax' error.
SELECT *
FROM table1 t1
LEFT JOIN (SELECT id_project FROM table2) t2
FORCE INDEX FOR JOIN (id_project)
ON (t2.id_project = t1.id_project);
Why can't I use a FORCE INDEX with a LEFT JOIN subquery?
--edit: here is the actual query returning mysql error
SELECT
p.id_projet, pa.piste_affaire, ty.type, p.e_force, gr.groupe, p.client, p.intitule_affaire,
ag.agence, st.statut, p.winratio, p.justification_nogo, p.webdoc, sp.sponsor, dis.domain_is,
cons.constructeur, p.date_chgt_statut, p.date_creation,
p.ca_estimatif, p.tcv, p.marge,
tarp.target_price, p.marketable_price,
p.remise_propal_initiale, p.remise_propal_reel,
p.date_de_signature, p.debut_build, p.fin_build,
his.date, his.login, co.contrat,
p.duree_contrat, p.fqp, cap.capitalisation,
p.international, p.risk_management, p.asi,
GROUP_CONCAT(com.commentaire,',') AS COMMENT
FROM
piste_affaire pa, statut st, TYPE ty, agence ag,
domain_is dis, sponsor sp, constructeur cons, groupe gr,
target_price tarp, contrat co, historique his, capitalisation cap,
projet p
LEFT JOIN commentaire com ON (p.id_projet=com.id_projet)
LEFT JOIN bizdev bizd ON (p.id_bizdev = bizd.id_bizdev )
LEFT JOIN (SELECT eco_projet.id_projet,
GROUP_CONCAT(
CONCAT(ecosysteme)
ORDER BY ecosysteme SEPARATOR ', ' ) AS ecosysteme
FROM ecosysteme NATURAL JOIN eco_projet, projet
WHERE eco_projet.id_projet = projet.id_projet
GROUP BY eco_projet.id_projet) AS tmpeco
/* if I delete that line, everything works as intended with ~2s query*/
FORCE INDEX FOR JOIN (id_projet)
/* ** */
ON (tmpeco.id_projet = p.id_projet)
WHERE
ag.id_division != 2 AND
dis.id_division != 2 AND
st.id_division != 2 AND
pa.id_division != 2 AND
cons.id_division != 2 AND
p.type_division = 1 AND
pa.id_piste_affaire = p.id_piste_affaire AND
p.id_statut = st.id_statut AND
p.id_type = ty.id_type AND
p.id_agence = ag.id_agence AND
p.id_domain_is = dis.id_domain_is AND
p.id_sponsor = sp.id_sponsor AND
p.id_constructeur = cons.id_constructeur AND
p.id_groupe = gr.id_groupe AND
p.id_target_price = tarp.id_target_price AND
p.id_contrat = co.id_contrat AND
p.id_projet = his.id_projet AND
p.id_capitalisation = cap.id_capitalisation
GROUP BY p.id_projet;
Why the subquery? Try this:
SELECT *
FROM table1 t1
LEFT JOIN table2 t2
FORCE INDEX FOR JOIN (id_project)
ON (t2.id_project = t1.id_project);
You probably won't even need to force the index with the above query.

MySQL - How to count number of rows from primary query, ignore subquery rows?

I use the following MySQL to return a list of posts and their corresponding comments.
SELECT *
FROM forum_qa
JOIN user_profiles
ON user_id = forum_qa_author_id
LEFT JOIN (SELECT forum_cm_id,
forum_cm_author_id,
forum_qa_id_fk,
forum_cm_text,
FROM forum_cm
JOIN user_profiles
ON user_id = forum_cm_author_id) AS c
ON forum_qa_id = c.forum_qa_id_fk
WHERE forum_qa_parent_id = $forum_qa_id
If I run
$data['num_answers'] = $query->num_rows();
This allows me to get the number of returned rows and pass the array to my controller and view.
But this is returning all rows (posts + comments). So if 1 post has 10 comments, it returns 10.
How could I have this query count only the number of posts (ie, returning 1) not including the subquery?
Each post has a unique id saved in forum_qa.forum_qa_id
Each comment has a unique id saved in forum_cm.forum_cm_id.
Thanks for helping -- will post more code if needed.
Not the fastest, but you are not restricted in using GROUP BY:
SELECT *,
(SELECT COUNT(*) FROM forum_qa WHERE forum_qa_parent_id = $forum_qa_id) Cnt
FROM forum_qa
JOIN user_profiles
ON user_id = forum_qa_author_id
LEFT JOIN (SELECT forum_cm_id,
forum_cm_author_id,
forum_qa_id_fk,
forum_cm_text,
FROM forum_cm
JOIN user_profiles
ON user_id = forum_cm_author_id) AS c
ON forum_qa_id = c.forum_qa_id_fk
WHERE forum_qa_parent_id = $forum_qa_id
You can run another query or add one more column (with an independent subquery) in the result set:
SELECT *
, ( SELECT COUNT(*)
FROM forum_qa
WHERE forum_qa_parent_id = $forum_qa_id
) AS cntPosts
FROM forum_qa
JOIN user_profiles
ON user_id = forum_qa_author_id
LEFT JOIN (SELECT forum_cm_id,
forum_cm_author_id,
forum_qa_id_fk,
forum_cm_text,
FROM forum_cm
JOIN user_profiles
ON user_id = forum_cm_author_id) AS c
ON forum_qa_id = c.forum_qa_id_fk
WHERE forum_qa_parent_id = $forum_qa_id
COUNT(DISTINCT forum_qa.forum_qa_id)
COUNT(DISTINCT col_name) counts the distinct post ids. This should equal the number of posts.