Counting number of statuses in group concat row - php

I've taken over a project that is a real mess so I've left with bad code structure that is forcing me to basically program in SQL. So changing the way of calculating this is for now not an option.
I have $sqlAdd variable that i need to populate in function and then concatenate that to main query to count number of lost tickets.
Main query looks like this:
$sql = "SELECT COUNT(*) as num_tickets, SUM(t.total_amount) as total_payin, SUM(t.total_payout) as total_payout
FROM t WHERE t.tickettime BETWEEN '$dateFrom' AND '$dateTo' AND t.bsid = $bsID
$sqlAdd";
So $sqlAdd is getting from another function
$sqlAdd = getSqlAdd();
And in that function i have this:
$sqlAdd = " AND 'WON' NOT IN (
SELECT GROUP_CONCAT(tr.ticketstatus)
FROM tr INNER JOIN m ON tr.ticketid = m.ticketid
WHERE tr.ticketid = t.ticketid GROUP BY m.ticket_groupid
)
AND 'PAYEDOUT' NOT IN (
SELECT GROUP_CONCAT(tr.ticketstatus)
FROM tr INNER JOIN m ON tr.ticketid = m.ticketid
WHERE tr.ticketid = t.ticketid GROUP BY m.ticket_groupid
)
AND 'CLOSED' NOT IN (
SELECT GROUP_CONCAT(tr.ticketstatus)
FROM tr INNER JOIN m ON tr.ticketid = m.ticketid
WHERE tr.ticketid = t.ticketid GROUP BY m.ticket_groupid
)
AND 'OPEN' NOT IN (
SELECT GROUP_CONCAT(tr.ticketstatus)
FROM tr INNER JOIN m ON tr.ticketid = m.ticketid
WHERE tr.ticketid = t.ticketid GROUP BY m.ticket_groupid
)";
GROUP_CONCAT(tr.ticketstatus) is getting me these rows when i execute it
CLOSED,CLOSED,CLOSED
PAYEDOUT,PAYEDOUT
CLOSED,CLOSED
WON,LOST
LOST,WON,WON,WON,WON,WON
CLOSED,CLOSED
LOST,LOST,WON
WON,WON,WON,LOST,LOST,WON,WON
LOST
I just want to count rows that have only LOST status in it. So the result should be 1. But i keep getting 7. It it counting every LOST status in results.

You would seem to want something like this:
SELECT COUNT(*) as num_tickets, SUM(t.total_amount) as total_payin,
SUM(t.total_payout) as total_payout
FROM t
WHERE t.tickettime BETWEEN '$dateFrom' AND
'$dateTo' AND t.bsid = $bsID AND
NOT EXISTS (SELECT 1
FROM tr NATURAL JOIN
m NATURAL JOIN
tg
WHERE tr.ticketid = t.ticketid AND
tr.ticketstatus IN ('WON', 'PAYEDOUT', 'CLOSED', 'OPEN')
);
Some notes:
GROUP_CONCAT() is not appropriate for this type of comparison. In SQL, you don't convert lists to strings and then do comparisons -- at least if you want performance.
You should avoid NATURAL JOIN. A small change to any of the tables can totally change the semantics of the query. In addition, it is unclear what the JOIN keys are. I would recommend USING instead.
You might want tr.ticketstatus NOT IN ('LOST') in the subquery.

So i've wrote very ugly query that is probably slow but its working i'm getting results that i need.
$sql = " AND ticket_groupid IN (
SELECT tg FROM t as tt
NATURAL JOIN tr NATURAL JOIN m
WHERE tt.tickettime BETWEEN '$dateFrom' AND '$dateTo' AND tt.bsid = $bsid
AND 'LOST' IN (
SELECT GROUP_CONCAT(tr.ticketstatus)
FROM tr NATURAL JOIN m as mm
WHERE mm.ticket_groupid = m.ticket_groupid
)
GROUP BY mm.ticket_groupid )"

Related

MySQL counting in assocative tables with reference to third table

How can I count rows based on a association with a third row?
In the example below I get the same result for both counts which is not the amount of "cwt_packages " nor the "cwt_products " table.
Find below the query I wrote
SELECT *, COUNT(p1.pkg_id) pkg_count, COUNT(p2.prd_id) prd_count FROM cwt_companies
LEFT JOIN cwt_packages p1 ON p1.pkg_ins_cmp = ic_id
LEFT JOIN cwt_products p2 ON p2.prd_pkg = p1.pkg_id AND p1.pkg_ins_cmp = ic_id
It's a bit hard to write a query, without knowing the structure of the tables, but something like this shoud work:
select *, count( pkg1.pkg_id ) as pkg_count,
( select count( prd.prd_id ) as prd_count
from cwt_companies c2
left join cwt_packages pkg2 on pkg2.pkg_ins_cmp = c2.ic_id
left join cwt_products prd on prd.prd_pkg = pkg2.pkg_id
where c1.ic_id = c2.ic_id ) as prd_count
from cwt_companies c1
left join cwt_packages pkg1 on pkg1.pkg_ins_cmp = c1.ic_id
group by c1.ic_id;

How to optimize mysql query for performance

I have five tables with join. This query takes more than 5 sec to execute. I have four queries like the one below, so my PHP page takes more than 30 sec to load. I need to improve the performance. Could you please help me? I have struggled for a week. I added indexing.
MY QUERY
SELECT f.SortField , f.id
FROM tbl_store_brands AS sb
INNER JOIN tbl_products AS p
ON sb.categoryID = p.pkCategory AND sb.brandID = p.pkBrand
INNER JOIN ModelPrice AS mp ON (p.id = mp.product_id)
INNER JOIN Brand_Data AS bd ON bd.pkID = p.pkBrand+0
INNER JOIN tbl_filters AS f
ON (
p.pkID = f.pkID and
p.pkCategory = f.Category AND
p.cgSlug = 'bathroom-fixtures'
)
WHERE mp.Available = 1
GROUP BY f.SortField
ORDER BY if (
f.SortField = 'CAPACITY' OR
f.SortField = 'WIDTH' OR
f.SortField = 'HEIGHT' OR
f.SortField = 'DEPTH', 4, f.ValueDisplayOrder
) ASC
MY Query EXPLANATION
Few modification can be done, to improve you query performance.
1. Add index for ValueDisplayOrder, this will improve the sorting performance.
2. Avoid in query per functioning. Remove the "p.pkBrand+0", this add the execution process time. If possible remove the if clause from order by and do the ordering in your PHP code.
3. Move the "p.cgSlug = 'bathroom-fixtures'" code to on clause of product table join to narrow down the inner join result.
Your modified query will look like:
SELECT f.SortField , f.id
FROM tbl_store_brands AS sb
INNER JOIN tbl_products AS p
ON sb.categoryID = p.pkCategory AND sb.brandID = p.pkBrand AND p.cgSlug = 'bathroom-fixtures'
INNER JOIN ModelPrice AS mp ON (p.id = mp.product_id)
INNER JOIN Brand_Data AS bd ON bd.pkID = p.pkBrand
INNER JOIN tbl_filters AS f
ON (
p.pkID = f.pkID and
p.pkCategory = f.Category
)
WHERE mp.Available = 1
GROUP BY f.SortField
ORDER BY f.ValueDisplayOrder
ASC

Add a filter to a large mysql query

I have this large query, and I just need to filter out any results where the tbl_dealinterest.Active = 'n'. There sometimes isn't an entry in that table for the product, and sometimes it might there might be and entry and set to y.
Here is the large ugly query:
SELECT tbl_product.id, tbl_productspecification.id AS specificationId,
tbl_product.ProductId, tbl_seller.CompanyName, tbl_product.ProductName, tbl_product.Description, mst_Categories.id AS 'Category',
tbl_productspecification.RetailPrice, tbl_productspecification.SalePrice,
tbl_product.image, tbl_productspecification.Discount, tbl_product.EndTime, tbl_product.Seller_Id, tbl_dealinterest.Active AS thumbsActive
FROM tbl_product
LEFT OUTER JOIN tbl_seller ON tbl_seller.SelId = tbl_product.Seller_Id
LEFT OUTER JOIN mst_Categories ON (mst_Categories.id = tbl_product.Category OR mst_Categories.id = tbl_product.SubCategory)
LEFT OUTER JOIN tbl_productspecification ON tbl_productspecification.ProductId = tbl_product.ProductId
LEFT OUTER JOIN mst_image ON mst_image.Product = tbl_product.ProductId
LEFT OUTER JOIN tbl_dealinterest ON tbl_dealinterest.ProductId = tbl_product.ProductId AND tbl_dealinterest.BuyerId = '$token'
WHERE tbl_product.Active='y'
AND tbl_product.StartTime <= '".date("Y-m-d H:i:s")."'
AND tbl_product.EndTime > '".date("Y-m-d")." 06:00:00'
".$subquery."
GROUP BY tbl_productspecification.ProductId";
Thanks for any suggestions.
SELECT ...
WHERE tbl_product.Active='y'
AND (tbl_dealinterest.Active <> 'n' OR tbl_dealinterest.Active IS NULL)
...
LEFT OUTER JOIN tbl_dealinterest ON (tbl_dealinterest.ProductId = tbl_product.ProductId
AND tbl_dealinterest.BuyerId = '$token'
AND tbl_dealinterest.Active<>'n')

Limiting a left join to returning one result?

I currently have this left join as part of a query:
LEFT JOIN movies t3 ON t1.movie_id = t3.movie_id AND t3.popularity = 0
The trouble is that if there are several movies with the same name and same popularity (don't ask, it just is that way :-) ) then duplicate results are returned.
All that to say, I would like to limit the result of the left join to one.
I tried this:
LEFT JOIN
(SELECT t3.movie_name FROM movies t3 WHERE t3.popularity = 0 LIMIT 1)
ON t1.movie_id = t3.movie_id AND t3.popularity = 0
The second query dies with the error:
Every derived table must have its own alias
I know what I'm asking is slightly vague since I'm not providing the full query, but is what I'm asking generally possible?
The error is clear -- you just need to create an alias for the subquery following its closing ) and use it in your ON clause since every table, derived or real, must have its own identifier. Then, you'll need to include movie_id in the subquery's select list to be able to join on it. Since the subquery already includes WHERE popularity = 0, you don't need to include it in the join's ON clause.
LEFT JOIN (
SELECT
movie_id,
movie_name
FROM movies
WHERE popularity = 0
ORDER BY movie_name
LIMIT 1
) the_alias ON t1.movie_id = the_alias.movie_id
If you are using one of these columns in the outer SELECT, reference it via the_alias.movie_name for example.
Update after understanding the requirement better:
To get one per group to join against, you can use an aggregate MAX() or MIN() on the movie_id and group it in the subquery. No subquery LIMIT is then necessary -- you'll receive the first movie_id per name withMIN() or the last with MAX().
LEFT JOIN (
SELECT
movie_name,
MIN(movie_id) AS movie_id
FROM movies
WHERE popularity = 0
GROUP BY movie_name
) the_alias ON t1.movie_id = the_alias.movie_id
LEFT JOIN movies as m ON m.id = (
SELECT id FROM movies mm WHERE mm.movie_id = t1.movie_id
ORDER BY mm.id DESC
LIMIT 1
)
you could try to add GROUP BY t3.movie_id to the first query
Try this:
LEFT JOIN
(
SELECT t3.movie_name, t3.popularity
FROM movies t3 WHERE t3.popularity = 0 LIMIT 1
) XX
ON t1.movie_id = XX.movie_id AND XX.popularity = 0
On MySQL 5.7+ use ANY_VALUE & GROUP_BY:
SELECT t1.id,t1.movie_name, ANY_VALUE(t3.popularity) popularity
FROM t1
LEFT JOIN t3 ON (t3.movie_id=t1.movie_id AND t3.popularity=0)
GROUP BY t1.id
more info
LEFT JOIN only first row
https://dev.mysql.com/doc/refman/5.7/en/group-by-handling.html
Easy solution to left join the 1 most/least recent row is using select over ON phrase
SELECT A.ID, A.Name, B.Content
FROM A
LEFT JOIN B
ON A.id = (SELECT MAX(id) FROM B WHERE id = A.id)
Where A.id is the auto-incremental primary key.
LEFT JOIN (
SELECT id,movie_name FROM movies GROUP BY id
) as m ON (
m.id = x.id
)
// Mysql
SELECT SUM(db.item_sales_nsv) as total FROM app_product_hqsales_otc as db
LEFT JOIN app_item_target_otc as it ON
db.id = (SELECT MAX(id) FROM app_item_target_otc as ot WHERE id = db.id)
and db.head_quarter = it.hqcode
AND db.aaina_item_code = it.aaina_item_code AND db.month = it.month
AND db.year = it.year
WHERE db.head_quarter = 'WIN001' AND db.month = '5' AND db.year = '2022' AND db.status = '1'

Is there an alternative for MYSQL HAVING

I'm trying to find some records based on the tags they have. In order to find out which tags a record has i add them to the result using a subquery. To find out which results should be returned i added a having statement on the end of the query. But something tells me this is not the best way.
SELECT e.id, e.title, e.text, e.introduction,
UNIX_TIMESTAMP(e.starts_on) AS starts_on,
UNIX_TIMESTAMP(e.ends_on) AS ends_on,
m.id AS meta_id,
m.url,
cm.title AS category_title,
cm.url AS category_url,
CONCAT(
",",
(
SELECT GROUP_CONCAT(t.id)
FROM modules_tags AS mt
JOIN tags AS t ON t.id = mt.tag_id
WHERE mt.other_id = e.id
),","
) AS tags_search
FROM event_posts AS e
INNER JOIN meta AS m ON e.meta_id = m.id
LEFT JOIN event_categories AS c ON e.category_id = c.id
LEFT JOIN meta AS cm ON c.meta_id = cm.id
LEFT JOIN modules_tags AS mt ON mt.other_id = e.id
LEFT JOIN tags AS t ON t.id = mt.tag_id
WHERE 1 HAVING tags_search LIKE '%,5,%' AND tags_search LIKE '%,6,%'
FIND_IN_SET() is the best way to find values in comma separated values in MySQL.

Categories