Complex SQL query into Zend - php

I am trying to build a social network using Zend Framework. However, recently I have difficulty in making some complex SQL queries into Zend language. For example:
"SELECT t.plural_name, p.name as users_name, u.ID
FROM users u, profile p, relationships r, relationship_types t
WHERE t.ID=r.type
AND r.accepted=1
AND (r.usera={$user} OR r.userb={$user})
AND IF( r.usera={$user},u.ID=r.userb,u.ID=r.usera)
AND p.user_id=u.ID"
How could I execute this query using Zend's select() object? Thank you very much!

Whenever I have complex queries to deal with, I usually try to run them in MySQL. Once I have found the exact SQL statement, I start to 'translate' it to Zend. I seems a bit complex but in the long run, you get to do them in Zend right away. Here is how I break it down.
First figure out what the SQL statement is:
SELECT t.plural_name, p.name as users_name, u.ID
FROM users u, profile p, relationships r, relationship_types t
WHERE t.ID=r.type AND r.accepted=1 AND
(r.usera={$user} OR r.userb={$user}) AND
IF( r.usera={$user},u.ID=r.userb,u.ID=r.usera) AND
p.user_id=u.ID
Now, you will want to convert all the relationships to joins. The joins are somewhat scary at the beginning but it's simply a way to put the table and the criteria on which it joins to another table in one line.
SELECT t.plural_name, p.name AS users_name, u.ID
FROM users u
INNER JOIN profile AS p ON p.user_id = u.ID
INNER JOIN relationships AS r ON IF(r.usera={$user}, u.ID=r.userb, u.ID=r.usera)
INNER JOIN relationship_types AS t ON t.ID = r.type
WHERE
(r.usera={$user} OR r.userb={$user}) AND
r.accepted=1
Now that it is written in a more 'Zend friendly' way, you can easily start to convert it to Zend:
$select = $this->select()->setIntegrityCheck(false);
$select->from(array('u'=>'users'), '');
$select->join(array('p'=>'profile'), 'p.user_id = u.ID', '');
$select->join(array('r'=>'relationships'), 'IF(r.usera={$user}, u.ID=r.userb, u.ID=r.usera)', '');
$select->join(array('t'=>'relationship_types'), 't.ID = r.type', '');
$select->columns(array(
't.plural_name',
'users_name'=>'p.name',
'u.ID'));
$select->where('r.usera={$user}');
$select->orWhere('r.userb={$user}');
$select->where('r.accepted=1');
And that should do the job.

You can take this as example:(though its not complete)
<?php
$this->_query = $this->_DB->select()->from(array('u'=>'users'),array('(u.ID)'))
->join(array('p'=>'profile'),
'p.user_id = u.ID',
'p.name AS users_name')
->join(array('r'=>'relationships'),
'r.userb = u.ID')
->join(array('t'=>'relationship_types'),
't.ID = r.type',
't.plural_name')
->where('t.ID = r.type AND r.accepted = 1')
->where('r.usera = :user')
->orWhere('r.userb = :user')
->bind(array('user'=>$user));

For really complex queries it might be easier just to use SQL. It is only important that all parameters going into the query are properly escaped to prevent SQL injections.

Related

How to make long sql sentence look beautiful in PHP(or other language)?

This is an example; For looking comfortable I fill some tab and line feed in it; I'm looking for some better ways except using '.' to link them.
$str_sql = "SELECT
sc.`id` AS 'payment_id',
sc.`order_id` AS 'order_id',
od.`partner_id` AS 'partner_id',
pt.`partner_name` AS 'partner_name',
od.`term` AS 'order_terms',
od.`has_pay` AS 'paid_terms',
sc.`sort_order` AS 'current_term',
FROM_UNIXTIME(sc.`repayment_time`) AS 'payment_time',
FROM_UNIXTIME(sc.`receive_time`) AS 'pay_time',
sc.`order_price` AS 'term_price',
od.`add_time` AS 'order_time',
sc.`status` AS 'status',
od.`order_price` AS 'order_price',
usr.`user_name` AS 'user_name',
usr.`id` AS 'user_id',
usr.`user_cell` AS 'user_cellphone',
IF(sc.`status` IN (0,3), 1 , 0 ) AS 'overdue',
FROM
mayidev.`lzh_order_payment_schedule` sc
JOIN lzh_order od
ON sc.`order_id` = od.`id`
JOIN lzh_partners pt
ON od.`partner_id` = pt.`id`
JOIN lzh_order_user usr
ON od.id = usr.order_id
JOIN lzh_partner_overdue_interest po
ON po.`partner_id` = pt.id
WHERE sc.`repayment_time` < 1477441800
AND sc.`receive_time` = 0
AND sc.`status` != 1
AND od.`status` = 4 ;";
The best way to make long SQL sentence look beautiful in PHP is to separate SQL code from PHP code. And the best way to do that is create views and stored procedures in SQL to get the data. You don't really need all this JOINs logic in PHP. It is best stored on the SQL server to keep the code maintainable.
And on PHP side you can just SELECT * FROM view or CALL procedure().

php pdo selecting from multiple tables with more than one where clauses

I am new to PHP PDO and converting some regular MySQL queries to work with PDO.
The query below when tested in phpMyAdmin works great when the assigned values replaced the current placeholders in the SQL statement. But when I configure it to work as it is now with PDO, it does not produce any results or errors. Can someone please tell me or show me what is it that I am doing wrong?
Someone told me that I cannot pass parameters as references in the array.
And if correct, what is the best way for creating a solution and by using only the user ID passed through to the variable $uid. Thanks.
<p>// For testing</p>
<pre>$uid = 1;</pre>
<p> </p>
<pre>$array = array(
':uId' => ''.$uid.'',
':aId' => 'u.user_id',
':gID' => 'a.group_id',
':eID' => 'a.entry_id',
':pID' => 'a.permit_id'
);</pre>
// create the sql for qd_user_usam table
$sql = "SELECT u.user_id, a.acl_id, g.group_name, e.entry_level, p.permit_level
FROM qd_users as u, qd_users_acl as a, qd_users_group as g, qd_users_entry as e, qd_users_permission as p
WHERE u.user_id = :uID
AND a.acl_id = :aID
AND g.group_id = :gID
AND e.entry_id = :eID
AND p.permit_id = :pID";
<p>try
{</p>
<p>// Build the database PDOStatement</p>
<pre>$_stmt = $this->_dbConn->prepare($sql);</pre>
<pre>$_stmt->execute($array);</pre>
<pre>}
catch(PDOException $e)
{</pre>
<pre>$this->_errorMessage .= 'Error processing user login access. <br /> Line #'.__LINE__ .' '.$e ;</pre>
<pre>die($this->_errorMessage);
}</pre>
<pre>$results = $_stmt->fetchAll(PDO::FETCH_ASSOC);</pre>
<pre>return $results;</pre>
<pre>$results = null;</pre>
<pre>$this->_dbConn = null;</pre>
You take prepared statements wrong.
Thy have to be used not to represent whatever value in the query, but dynamically added data only.
While a.group_id is a column name and have to be written as is, without prepared statements.
// For testing
$uid = 1;
// create the sql for qd_user_usam table
$sql = "SELECT u.user_id, a.acl_id, g.group_name, e.entry_level, p.permit_level
FROM qd_users as u, qd_users_acl as a, qd_users_group as
g, qd_users_entry as e, qd_users_permission as p
WHERE u.user_id = ?
AND a.acl_id = u.user_id
AND g.group_id = a.group_id
AND e.entry_id = a.entry_id
AND p.permit_id = a.permit_id";
$_stmt = $this->_dbConn->prepare($sql);
$_stmt->execute(array($uid));
The problem is that you're trying to write your JOINs implicitly by binding the joined columns as parameters, which would not work. The parameters can not reference another column; they are seen as strings in this case. If you rewrite the query like this it should fix the JOIN problem:
SELECT u.user_id, a.acl_id, g.group_name, e.entry_level, p.permit_level
FROM qd_users AS u
JOIN qd_users_acl AS a ON (u.user_id = a.acl_id)
JOIN qd_users_group AS g ON (g.group_id = a.group_id)
JOIN qd_users_entry AS e ON (e.entry_id = a.entry_id)
JOIN qd_users_permission AS p ON (p.permit_id = a.permit_id)
WHERE u.user_id = :uID

mysql trasnposing rows to columns

I need to output the following query as csv.
I can easily write php logic to transpose the rows to columns from my group_concat column
However I am keen to keep as much of the data part in the database and minimize the manipulations on the php side.
I am experimenting with the two columns below the group_concat in the query.
The problem is the abundance value also returns for life_stage column. If there is no way around this other than manipulating the group_concat key values then that's fine, I just wanted to double check. Thanks in advance
SELECT
`tr`.`tr_id_pk` as 'RecordKey',
`t`.`tax_name` as `TaxonName`,
`tr`.`tr_date` as 'Date',
`s`.`si_name` as 'SiteName',
`tr`.`tr_grid_reference` as 'GridReference',
`tr`.`tr_is_site_grid` as 'IsSiteGrid',
`r`.`rec_name` as 'Recorder',
`r`.`rec_email` as 'RecorderEmail',
`tr`.`tr_comment` as 'RecordComment',
`tr`.`tr_last_update` as 'LastUpdated',
`tr`.`tr_form_key` as 'FormKey',
`c`.`co_name` as 'County',
`vc`.`vc_name` as 'ViceCounty',
`h`.`hab_name` as 'Habitat',
GROUP_CONCAT(DISTINCT CONCAT_WS('=', `ra`.`ra_name`, `rad`.`rad_value`)) as 'RecordAttributeKeyValuePairs',
`rad`.`rad_value` AS `abundance`,
`rad`.`rad_value` AS `life_stage`
FROM
`taxon_record`as `tr`
INNER JOIN
`taxon`as `t` ON `tr`.`tax_id_fk` = `t`.`tax_id_pk`
INNER JOIN
`recorder`as `r` ON `tr`.`rec_id_fk` = `r`.`rec_id_pk`
INNER JOIN
`site`as `s` ON `tr`.`si_id_fk` = `s`.`si_id_pk`
LEFT JOIN
`county`as `c` ON `tr`.`co_id_fk` = `c`.`co_id_pk`
LEFT JOIN
`vice_county`as `vc` ON `tr`.`vc_id_fk` = `vc`.`vc_id_pk`
LEFT JOIN
`habitat`as `h` ON `tr`.`hab_id_fk` = `h`.`hab_id_pk`
LEFT JOIN
(`record_attribute_data`as `rad`
INNER JOIN `record_attribute`as `ra` ON (`rad`.`ra_id_fk` = `ra`.`ra_id_pk`)) ON (`tr`.`tr_id_pk` = `rad`.`tr_id_fk`)
WHERE
`r`.`rec_email` = 'some_email#somewhere.com'
GROUP BY `tr`.`tr_id_pk`;
What you wish to do is known as "pivoting" your data and is something for which some other RDBMS have native support, but MySQL does not (by design, as the developers feel that such manipulations belong in the presentation layer).
However, as you've hinted at, you can construct a rather horrible MySQL query to perform the pivoting operation manually (one needs to join the attributes tables to the query once for each output column):
SELECT tr.tr_id_pk, abundance, life_stage -- etc.
FROM taxon_record AS tr
LEFT JOIN (
SELECT rad.tr_id_fk, rad.rad_value AS abundance
FROM
record_attribute_data ra JOIN record_attribute rad
ON rad.ra_id_fk = ra.ra_id_pk
WHERE ra.ra_name = 'abundance'
) AS tAbundance ON tAbundance.tr_id_fk = tr.tr_id_pk
LEFT JOIN (
SELECT rad.tr_id_fk, rad.rad_value AS life_stage
FROM
record_attribute_data ra JOIN record_attribute rad
ON rad.ra_id_fk = ra.ra_id_pk
WHERE ra.ra_name = 'life_stage'
) AS tLife_Stage ON tLife_Stage.tr_id_fk = tr.tr_id_pk
-- etc.
If you choose to go down this path, you can make your life slightly easier by generating this query using either a looping construct in PHP or a prepared statement in MySQL.
Is the problem that you are fetching the same column twice under different names?:
`rad`.`rad_value` AS `abundance`,
`rad`.`rad_value` AS `life_stage`
Looks like you'll always get the same thing for abundance and life_stage that way.

Strange PDO MySQL Problem

I am trying to perform a query in a PHP script. The query works fine in my MySQL client however it does not seem to working within my code. I am using PDO. I am thinking it might have limitations, because it seems to work fine with a less complicated query.
Here is the query:
SELECT D.status, D.createdBy, D.createDate, D.modifiedBy, D.modifiedDate,D.IPAddress, D.adminStatus, D.campus, D.buildingID, D.deviceShortName, D.distributionID, D.networkKey, D.deviceName, D.test, D.pkDevices, D.DNSRule, D.Domain, D.DNSOverride, D.noDNS, D.pkModel, DM.Model, DM.pkManufacturer, M.manufacturer FROM Devices AS D INNER JOIN DeviceModel AS DM ON D.pkModel = DM.pkModel INNER JOIN Manufacturer AS M ON DM.pkManufacturer = M.pkManufacturer WHERE D.status = '1' AND D.adminStatus = 'Active' ORDER BY D.deviceName
Here is where I am trying to call it in my script:
$dbhDevices = newPDO('mysql:host='.$_SESSION['OpsDBServer'].'.ops.tns.its.psu.edu;dbname='.$_SESSION['OpsDB'], $_SESSION['yoM'], $_SESSION['aMa']);
$sqlDevices = "SELECT D.status, D.createdBy, D.createDate, D.modifiedBy,
D.modifiedDate, D.IPAddress, D.adminStatus, D.campus, D.buildingID,
D.deviceShortName, D.distributionID, D.networkKey, D.deviceName, D.test,
D.pkDevices, D.DNSRule, D.Domain, D.DNSOverride, D.noDNS, D.pkModel, DM.Model,
DM.pkManufacturer, M.manufacturer
FROM Devices AS D
INNER JOIN DeviceModel AS DM ON D.pkModel = DM.pkModel
INNER JOIN Manufacturer AS M ON DM.pkManufacturer = M.pkManufacturer
WHERE D.status = '1' AND D.adminStatus = 'Active' ORDER BY D.deviceName";
foreach ($dbhDevices->query($sqlDevices) as $rowDevices)
{
Again, the query does work with the MySQL client.
try enabling errors if haven't done already:
error_reporting(E_ALL);
$dbhDevices->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
In the older versions of PDO usage you have to "initialize" your PDO variable when you declare it for some reason. Try putting a line before this code that generically initializes $dbhDevices and see if that does the trick. I have my doubts because you say it works fine with less complicated queries but it's worth a try.
I got it working by using a LEFT JOIN instead of an INNER JOIN. Strange, but it is working.

Slow query - Multiple joins and a lot of data

I'm sure there is a better way I could be doing this. I have three main tables with large amounts of data to run through:
records_main, sales, and appointments. Each with close to 20,000 records.
I need to join these three tables as well as a few others that arn't two large.
$SQL = "SELECT DISTINCT appointments.id AS aid, appointments.date, appointments.estimate_price, appointments.next_action, appointments.next_action_date, appointments.result, appointments.result_type, appointments.notes,
customer.id AS cid, customer.homeowner1_fname, customer.homeowner1_lname, customer.address, customer.city, customer.state, customer.zipcode, customer.phone1, customer.phone1_type, customer.phone2, customer.phone2_type, customer.phone3, customer.phone3_type, customer.phone4, customer.phone4_type, customer.phone5, customer.phone5_type, customer.lead_source, customer.lead_category, customer.primary_interest, customer.secondary_interest, customer.additional_interest1, customer.additional_interest2,
originator.employee_id AS originator_employee_id,originator.fname AS originator_fname,originator.lname AS originator_lname,
setter.employee_id AS setter_employee_id,setter.fname AS setter_fname,setter.lname AS setter_lname,
resetter.employee_id AS resetter_employee_id, resetter.fname AS resetter_fname, resetter.lname AS resetter_lname,
salesrep.employee_id AS salesrep_employee_id, salesrep.fname AS salesrep_fname, salesrep.lname AS salesrep_lname,
salesrep2.employee_id AS salesrep2_employee_id, salesrep2.fname AS salesrep2_fname, salesrep2.lname AS salesrep2_lname
FROM
core_records_appointments as appointments
INNER JOIN core_records_main as customer ON appointments.customer = customer.id
LEFT JOIN core_employees_main as originator ON appointments.originator = originator.id
LEFT JOIN core_employees_main as setter ON appointments.setter = setter.id
LEFT JOIN core_employees_main as resetter ON appointments.resetter = resetter.id
LEFT JOIN core_employees_main as salesrep ON appointments.sales_representative = salesrep.id
LEFT JOIN core_employees_main as salesrep2 ON appointments.sales_representative2 = salesrep2.id
";
This takes a few seconds but finally does load. The last join I put though seems to break the query together:
LEFT JOIN core_records_sales as sales ON appointments.day = sales.day_sold AND appointments.customer = sales.customer
After this I have a limit set and a group by
Anything I can do to improve this? I am using this with jqgrid, not sure if that helps
As a first approach, try to execute the query on the database using the EXPLAIN syntax. This will give you an analysis of the statement and tells you, if it uses indexes at all. If it doesn't use indexes or not enough indexes, try adding them to the tables.
Have you tried giving index on all the columns you are LEFT JOINing on?
TRY giving index to all these if they are not already declared as index

Categories