How to convert named inner join query to code-igniter syntax? - php

I have the following query
$query = $this->db->query(
'SELECT ii.json
FROM inventory ii
INNER JOIN
(
SELECT json, MAX(id) as MAX_ID
FROM inventory
GROUP BY business_unit
) group_json ON ii.id = group_json.MAX_ID
INNER JOIN business_units
ON ii.business_unit = business_units.id'
);
return $query->result_array();
We've had to migrate to a MSSQL test server and unfortunately the syntax is incorrect. To avoid this in the future I want to convert this to the CodeIgniter typed query.
Can anyone show me what this query would look like in CodeIgniter, I've tried a few things but I'm not getting anywhere close.

SELECT ii.json
FROM inventory ii
INNER JOIN
(
SELECT json, MAX(id) as MAX_ID
FROM inventory
GROUP BY business_unit, json --<-- "json" needs to be in GROUP BY
) group_json -- Since it is in SELECT but not
ON ii.id = group_json.MAX_ID -- contained in any aggregate function
INNER JOIN business_units
ON ii.business_unit = business_units.id
if you use any aggregate function(MAX, MIN, AVG, SUM, COUNT) in your SELECT any other columns that are not contained in any aggregate function in that select, must go in GROUP BY clause of your query.

Related

Optimizing mysql IN query having large values

I have a mysql query like
SELECT `tbl_ticket`.`id`, `tbl_ticket`.`hd_user_username`,
`tbl_ticket`.`hd_user_email`, `tbl_ticket`.`ticket_title`,
`tbl_complain_type`.`complains` FROM `tbl_ticket` LEFT JOIN
`tbl_ticket_complain` ON tbl_ticket_complain.ticket_id=tbl_ticket.id
LEFT JOIN `tbl_complain_type` ON tbl_complain_type.id=tbl_ticket_complain.complain_id
LEFT JOIN `tbl_assignment` ON tbl_assignment.ticket_id=tbl_ticket.id
WHERE ((((`hd_user_username` LIKE '%searchterm%')
AND (`tbl_assignment`.`id` IN ($array)))
OR (`hd_user_email`='searchterm'))
OR (`ticket_title`='searchterm')) OR (`tbl_complain_type`.`complains`='searchterm')
$array contains around 7000 values like `$array=array(1,2,3,..)`
This query takes around 8 seconds to execute. Is there any alternative solution for this query ?
The value of $array is got from another query
select max(id) from tbl_assignment group by ticket_id
The slowness of query is due to multiple joins between tables
If the values in the array use in you IN clause come from a select you could use the fact that
An IN clause is equivalent to an inner join so you could use a inner join between your_table_with_id and the table.column you need for match eg:
SELECT `
tbl_ticket`.`id`
, `tbl_ticket`.`hd_user_username`
, `tbl_ticket`.`hd_user_email`
, `tbl_ticket`.`ticket_title`
, `tbl_complain_type`.`complains`
FROM `tbl_ticket`
LEFT JOIN `tbl_ticket_complain` ON tbl_ticket_complain.ticket_id=tbl_ticket.id
LEFT JOIN `tbl_complain_type` ON tbl_complain_type.id=tbl_ticket_complain.complain_id
LEFT JOIN `tbl_assignment` ON tbl_assignment.ticket_id=tbl_ticket.id
INNER JOIN your_table_with_id ON `tbl_assignment`.`id` = JOIN your_table_with_id.id
WHERE ((((`hd_user_username` LIKE '%searchterm%')
OR (`hd_user_email`='searchterm'))
OR (`ticket_title`='searchterm')) OR (`tbl_complain_type`.`complains`='searchterm')
Remeber also that the content of values use IN clause is limited and fail when the limit is exceeded
and in your case
SELECT `
tbl_ticket`.`id`
, `tbl_ticket`.`hd_user_username`
, `tbl_ticket`.`hd_user_email`
, `tbl_ticket`.`ticket_title`
, `tbl_complain_type`.`complains`
FROM `tbl_ticket`
LEFT JOIN `tbl_ticket_complain` ON tbl_ticket_complain.ticket_id=tbl_ticket.id
LEFT JOIN `tbl_complain_type` ON tbl_complain_type.id=tbl_ticket_complain.complain_id
LEFT JOIN `tbl_assignment` ON tbl_assignment.ticket_id=tbl_ticket.id
INNER JOIN (
select max(id) as id
from tbl_assignment
group by ticket_id
) t ON `tbl_assignment`.`id` = t.id
WHERE ((((`hd_user_username` LIKE '%searchterm%')
OR (`hd_user_email`='searchterm'))
OR (`ticket_title`='searchterm')) OR (`tbl_complain_type`.`complains`='searchterm'))
This is basically your query:
SELECT . . .
FROM tbl_ticket t LEFT JOIN
tbl_ticket_complain tc
ON tc.ticket_id = t.id LEFT JOIN
tbl_complain_type tct
ON tct.id = tc.complain_id LEFT JOIN
tbl_assignment a
ON a.ticket_id = t.id
WHERE (((hd_user_username LIKE '%searchterm%' AND
a.id IN ($array)
) OR
`hd_user_email`='searchterm'
) OR
ticket_title = 'searchterm'
) OR
tct.complain` = 'searchterm';
The issue with performance has nothing to do with IN. In fact, MySQL optimizes IN, as explained in the documentation:
If all values are constants, they are evaluated according to the type
of expr and sorted. The search for the item then is done using a
binary search. This means IN is very quick if the IN value list
consists entirely of constants.
You are not going to get faster than an IN list with constants.
The problem with your query is the string of ORs. These make is almost impossible for the optimizer to use indexes -- so the full result set has to be created and then filtered down.
It is hard for me to see how to improve this in your query. Sometimes, splitting a query into simpler chunks and connecting them using union or union all does the trick. Your conditions are a bit hard to follow, making that approach difficult for an outsider.

Symfony2 Doctrine query builder as a subquery in FROM clause

I got a query using query builder and that is assigned to $qb variable. It works fine both from PHP and from the DB. Now, I was trying to use that query as a subquery like below:
$subQuery = $qb->getQuery()->getSql();
$query = 'select res.some_name
from ('.$subQuery.') as res';
But I get the following exception:
Caused by Doctrine\DBAL\Driver\PDOException: SQLSTATE[HY000]: General error: 1 no such column: res.some_name
As Doctrine already converted the $qb to something like this where Doctrine converted the original SQL query. For instance, there was something called AS legalentity_name but it's showing AS name1:
select res.some_name from (SELECT o0_.id AS id0, o0_.name AS name1, b1_.id AS id2, b1_.display AS display3, m2_.id AS id4, m2_.total AS total5 FROM Invoice i3_ INNER JOIN CodeableItem c4_ ON i3_.id = c4_.id INNER JOIN MonetaryItem m2_ ON i3_.id = m2_.id AND (1=1) INNER JOIN LineItem l5_ ON c4_.id = l5_.codeableItem_id LEFT JOIN MonetaryItem m6_ ON l5_.id = m6_.id AND (1=1) LEFT JOIN PresetLineItem p7_ ON c4_.id = p7_.codeableItem_id LEFT JOIN MonetaryItem m8_ ON p7_.id = m8_.id AND (1=1) INNER JOIN OrgUnit o0_ ON c4_.legalentity_id = o0_.id AND (1=1) INNER JOIN monetaryitem_listitem m9_ ON m2_.id = m9_.monetaryitem_id INNER JOIN BWListItem b1_ ON b1_.id = m9_.bwlistitem_id AND (1=1) INNER JOIN BWList b10_ ON b1_.bwlist_id = b10_.id AND (1=1) WHERE b10_.type = 'Vendor' GROUP BY c4_.legalentity_id, b1_.active, b1_.attributes, b1_.display, b1_.created, b1_.updated, b1_.lft, b1_.lvl, b1_.rgt, b1_.root, b1_.id, b1_.orgunit_id, b1_.bwlist_id, b1_.parent_id, b1_.rootou_id, m2_.created, m2_.updated, m2_.subtotal, m2_.total, m2_.description, m2_.id, c4_.number, c4_.externalId, c4_.status, c4_.overriddenDuringApproval, i3_.invoiceDate, i3_.dueDate, i3_.poNumber, m2_.rootou_id, c4_.image_id, c4_.legalentity_id, c4_.creator, c4_.owner_id ORDER BY o0_.name ASC, b1_.display ASC) as res
My question is: how can I use the raw SQL from $subQuery? Any help would be really beneficial. Cheers!
First off, subqueries in DQL is not possible. See Selecting from subquery in DQL
Secondly, you are putting computed SQL from Doctrine Query Language (DQL) into a subquery. This does not work as the database cannot find the column due to DQL prefixing characters/numeric values to the columns.
This is so the entities can be mapped correctly when using DQL.
You will need to build the subquery NOT using the DQL language (stop using that query builder, not sure if there is one that builds raw SQL).

MYSQL: greatest-n-per-group issue

Problem of greatest-n-per-group and my MySQL version cannot use the LIMIT & IN (error 1235), so I need to use this kind of query (see answer here: answer )
SELECT
t1.idMemberCard,
DATE(MIN(transactions.dateTransaction)) AS first_transaction,
DATE(MAX(transactions.dateTransaction)) AS last_transaction,
t2.*
FROM membersCard AS t1
INNER JOIN transactions ON transactions.idMemberCard = t1.idMemberCard
INNER JOIN
(
SELECT
membersCard.idMemberCard,
membersCard.cardNumber,
membersCard.firstNameMemberCard,
membersCard.lastNameMemberCard,
transactions.dateTransaction
FROM membersCard
INNER JOIN transactions ON transactions.idMemberCard = membersCard.idMemberCard
WHERE membersCard.sexMemberCard = 'M'
AND membersCard.cardNumber = '1100101308655'
AND
DATE(transactions.dateTransaction) BETWEEN ('2013-12-28') AND ('2014-08-13')
LIMIT 100
) AS t2
ON t1.idMemberCard = t2.idMemberCard
Subquery (t2 table) executed for exact match (card Number) returns exactly 5 rows (in this example): all perfect.
My issue/request:
Joining the two tables I would to obtain 5 rows and not only one, with the different 5 rows with the dates.
Your join is working fine. The problem is your select statement:
SELECT t1.idMemberCard,
DATE(MIN(transactions.dateTransaction)) AS first_transaction,
DATE(MAX(transactions.dateTransaction)) AS last_transaction,
t2.*
The use of MIN() and MAX() turn this into an aggregation query that returns only one row. Try this:
SELECT t1.idMemberCard,
DATE(transactions.dateTransaction) AS first_transaction,
DATE(transactions.dateTransaction) AS last_transaction,
t2.*
Or add a group by clause if you want that.

MySQL having clause with where clause

I have this query which is used to populate a dropdown:
SELECT order_details.od_no, order_details.so_number, sales-order.status, sales-order.so-no, products.product_name
FROM order_details
LEFT JOIN sales-order ON order_details.so_number = sales-order.so-number
LEFT JOIN products ON order_details.product_id = products.product_id
WHERE order_details.so_number = '$sonum' AND sales-order.status != 'Canceled'
HAVING SUM(po_prod_qty) < quantity
I have researched that aggregate SUM() will only work in HAVING clause if I want it to act like it's in a WHERE clause. But this query isn't working. Am I missing something?
You are perhaps missing a group by clause, if you really want to use sum(). The use of the sum() in having creates an aggregation query, which returns just one row. You may not need an aggregation at, if this does what you want:
SELECT od.od_no, od.so_number, so.status, so.so-no, p.product_name
FROM order_details od LEFT JOIN
sales_order so
ON od.so_number = so.so-number LEFT JOIN
products p
ON od.product_id = p.product_id
WHERE od.so_number = '$sonum' AND so.status <> 'Canceled' and po_prod_qty < quantity;
Otherwise, it is hard to say exactly what you want without sample data and desired results.
You have to select the same field and use group by clause
SELECT SUM(po_prod_qty) as prod_qty, ....

get count of posts based on count(*)

i am trying to get number of posts that i have
Here is my query
$Query="
SELECT t.*,u.*,c.*
FROM posts as t
LEFT JOIN relations as r on r.post_id = t.post_id
LEFT JOIN users as u on t.auther_id = u.auther_id
LEFT JOIN categories as c on c.cate_id = r.cate_id
GROUP BY t.post_id
";
$Query=mysql_query($Query);
$numberOfPosts=mysql_num_rows($Query);
This query is works very well
but i am trying to convert it, i want make it faster
i want use count(*) instead of t.*
because when i use t.*, it gets the full data of posts and categories
but i want to get count only, So i decided to use count(*) but i don't know how to use it with query like this
Edit
i've replaced SELECT t.*,u.*,c.* with SELECT count(t.*)
But i got mysql Error Warning: mysql_fetch_assoc(): supplied argument
Edit 2:
i am trying SELECT count(t.post_title)
I Got this results
Array ( [count(t.post_id)] => 10 )
But i have only 2 posts!
$Query="
SELECT t.*,u.*,c.*
FROM posts as t
LEFT JOIN relations as r on r.post_id = t.post_id
LEFT JOIN users as u on t.auther_id = u.auther_id
LEFT JOIN categories as c on c.cate_id = r.cate_id
GROUP BY t.post_id
";
$Query=mysql_query($Query);
$numberOfPosts=mysql_num_rows($Query);
Let's take a step back and analyze this query for a moment.
You're selecting everything from three out of four tables used in the query. The joins create some logic to limit what you select to the proper categories, authors, etc. At the end of the day you are getting a lot of data from the database, then in PHP simply asking it how many rows were returned (mysql_num_rows). Instead, what #Dagon is trying to suggest in comments, is that you have MySQL simply count the results, and return that.
Let's refactor your query:
$query = "
SELECT COUNT(t.post_id) AS qty
FROM posts as t
LEFT JOIN relations AS r ON r.post_id = t.post_id
LEFT JOIN users AS u ON t.auther_id = u.auther_id
LEFT JOIN categories AS c ON c.cate_id = r.cate_id
GROUP BY t.post_id
";
$result = mysql_query($query);
$result_row = mysql_fetch_assoc($result);
$numberOfPosts = $result_row['qty'];
(You could also use Barattlo's custom execute_scalar function to make it more readable.)
I would need to see your table structures to be of more help on how to optimize the query and get the desired results.
try doing this:
$Query="
SELECT count(t.*) as count_all
FROM posts as t
LEFT JOIN relations as r on r.post_id = t.post_id
LEFT JOIN users as u on t.auther_id = u.auther_id
LEFT JOIN categories as c on c.cate_id = r.cate_id
GROUP BY t.post_id
";
$Query=mysql_query($Query);
$numberOfPosts=mysql_num_rows($Query);
You want to do
SELECT count(t.id) AS count FROM ....
//do query with PDO or whatever you are using
$rows = mysql_fetch_assoc();
$num_rows = $rows['count'];
You should probably simply use
SELECT count(*) as postingcount FROM posts
Why?
Because you do not have a WHERE clause, so there are no restrictions. Your JOINS do not ADD more rows to the resultset, and in the end your GROUP BY merges every duplicate occurance of a post_id that might have occurred because of joining back into one row. The result should only be counted, so assuming that the real number you want to know is the number of data sets inside the table posts, you do not need any join, and doing count(*) really is a very fast operation on tables in MySQL.
Remember to check if mysql_query returns false, because then you have to check mysql_error() and see why your query has an error.

Categories