Zend_DB subselect / subquery how to? - zend-framework

I have this raw sql statement which I am trying to execute through Zend_DB.
$sql = 'SELECT relocationaction.id, relocationaction.vehicle, relocationaction.start, relocationaction.end, relocationaction.return ' .
'FROM relocationaction,
(SELECT vehicle, MAX(end) AS maxend
FROM relocationaction
GROUP BY vehicle) AS co2
WHERE co2.vehicle = relocationaction.vehicle
AND(relocationaction.monitor = 1)
AND (relocationaction.return IS NULL)
AND (start <= ?)
AND relocationaction.end = co2.maxend';
I have found a possible solution using this type of notation, but it is rendered to a totally different and wrong sql statement with joins and strange table names.
$tbl = $this->getDbTable();
$select = $tbl->select()->setIntegrityCheck(false);
$subSelect = $select->from('relocationaction', array('vehicle', 'maxend' => 'MAX(relocationaction.end)'))
->group('vehicle');
$subSelectString = '(' . $subSelect->__toString() . ')';
$select ->from(
array('relocationaction'), array('id', 'date' => 'start', 'enddate' => 'end', 'return'),
array('co2' => $subSelectString)
)
->joinLeft('exhibitvehicle', 'exhibitvehicle.id = relocationaction.vehicle', array())
->where('co2.vehicle = relocationaction.vehicle')
->where('relocationaction.monitor = 1')
->where('relocationaction.return IS NULL')
->where('start <= ?', $start->get('yyyy-MM-dd'))
->where('relocationaction.end = co2.maxend');
Can anyone please give me a hint?
Thanks
Jesse
UPDATE
This is the result of the second expression (total rubbish)
SELECT `relocationaction`.`vehicle`,
MAX(relocationaction.end) AS `maxend`,
`relocationaction_2`.`id`,
`relocationaction_2`.`start` AS `date`,
`relocationaction_2`.`end` AS `enddate`,
`relocationaction_2`.`return`
FROM `relocationaction`
INNER JOIN `(
SELECT ``relocationaction``.``vehicle``,
MAX(relocationaction.end) AS ``maxend`` FROM ``relocationaction`` GROUP BY ``vehicle``)`.`relocationaction` AS `relocationaction_2`
LEFT JOIN `exhibitvehicle` ON exhibitvehicle.id = relocationaction.vehicle
WHERE (col2.vehicle = relocationaction.vehicle)
AND (relocationaction.monitor = 1)
AND (relocationaction.return IS NULL)
AND (start <= '2013-05-08')
AND (relocationaction.end = col2.maxend)
GROUP BY `vehicle`

If you use a string in from(), Zend_Db_Select will consider it to be a table name so it escapes it.
The solution is to wrap your subselect into a Zend_Db_Expr.
$tbl = $this->getDbTable();
$select = $tbl->select()->setIntegrityCheck(false);
$subSelect = $select->from('relocationaction', array('vehicle', 'maxend' => 'MAX(relocationaction.end)'))
->group('vehicle');
$subSelectString = '(' . $subSelect->__toString() . ')';
$select ->from(
array('relocationaction'), array('id', 'date' => 'start', 'enddate' => 'end', 'return'),
array('co2' => new Zend_Db_Expr($subSelectString))
)
->joinLeft('exhibitvehicle', 'exhibitvehicle.id = relocationaction.vehicle', array())
->where('co2.vehicle = relocationaction.vehicle')
->where('relocationaction.monitor = 1')
->where('relocationaction.return IS NULL')
->where('start <= ?', $start->get('yyyy-MM-dd'))
->where('relocationaction.end = co2.maxend');

Ok here we go. I tried hard to find a solution with Zend_Db_Table but failed big time. That's why I finally did it with PDO, as suggested by #user466764. Thanks for your help.
$tbl = $this->getDbTable();
$query = 'SELECT relocationaction.id,
relocationaction.vehicle,
relocationaction.start,
relocationaction.end,
relocationaction.return
FROM relocationaction
(SELECT vehicle, MAX(end) AS maxend
FROM relocationaction
GROUP BY vehicle) AS co2
WHERE co2.vehicle = relocationaction.vehicle
AND(relocationaction.monitor = 1)
AND (relocationaction.return IS NULL)
AND (start <= "' . $start->get('yyyy-MM-dd') . '")
AND relocationaction.end = co2.maxend';
$sth = $tbl->getAdapter()->prepare($query);
$sth->execute();
$entries = $sth->fetchAll();

Related

converting sql statement back to lambda expression

I have the query below, and its sql code. It's running really slow, so it was re written in sql, now I'm just not sure how to convert the sql back to a lambda expression.
This is the part of the expression giving me the problems, somewhere in
r.RecordProducts.Any()
records = records
.Include(r => r.Employer)
.Include(r => r.Contractor)
.Include(r => r.RecordProducts)
.ThenInclude(rp => rp.ProductDefendant.Defendant)
.Where(r => EF.Functions.Like(r.Employer.DefendantCode, "%" + input.DefendantCode + "%")
|| EF.Functions.Like(r.Contractor.DefendantCode, "%" + input.DefendantCode + "%")
|| r.RecordProducts.Any(rp => EF.Functions.Like(rp.ProductDefendant.Defendant.DefendantCode, "%" + input.DefendantCode + "%") && rp.IsActive == true));
the any clause does an exist and some funky stuff in the sql where clause below
SELECT [t].[Id], [t].[StartDate], [t].[EndDate], [t].[WitnessName], [t].[SourceCode], [t].[JobsiteName], [t].[ShipName], [t].[EmployerCode]
FROM (
SELECT DISTINCT [r].[RecordID] AS [Id], [r].[StartDate], [r].[EndDate], [r.Witness].[FullName] AS [WitnessName], CASE
WHEN [r].[SourceID] IS NOT NULL
THEN [r.Source].[SourceCode] ELSE N'zzzzz'
END AS [SourceCode], CASE
WHEN [r].[JobsiteID] IS NOT NULL
THEN [r.Jobsite].[JobsiteName] ELSE N'zzzzz'
END AS [JobsiteName], CASE
WHEN [r].[ShipID] IS NOT NULL
THEN [r.Ship].[ShipName] ELSE N'zzzzz'
END AS [ShipName], CASE
WHEN [r].[EmployerID] IS NOT NULL
THEN [r.Employer].[DefendantCode] ELSE N'zzzzz'
END AS [EmployerCode]
FROM [Records] AS [r]
LEFT JOIN [Ships] AS [r.Ship] ON [r].[ShipID] = [r.Ship].[ShipID]
LEFT JOIN [Jobsites] AS [r.Jobsite] ON [r].[JobsiteID] = [r.Jobsite].[JobsiteID]
LEFT JOIN [Sources] AS [r.Source] ON [r].[SourceID] = [r.Source].[SourceID]
LEFT JOIN [Witnesses] AS [r.Witness] ON [r].[WitnessID] = [r.Witness].[WitnessID]
LEFT JOIN [Defendants] AS [r.Contractor] ON [r].[ContractorID] = [r.Contractor].[DefendantID]
LEFT JOIN [Defendants] AS [r.Employer] ON [r].[EmployerID] = [r.Employer].[DefendantID]
WHERE ([r].[IsActive] = 1) AND (([r.Employer].[DefendantCode] LIKE (N'%' + 'cert') + N'%' OR [r.Contractor].[DefendantCode] LIKE (N'%' + 'cert') + N'%') OR EXISTS (
SELECT 1
FROM [Records_Products] AS [rp]
INNER JOIN [Product_Defendant] AS [rp.ProductDefendant] ON [rp].[DefendantProductID] = [rp.ProductDefendant].[DefendantProductID]
INNER JOIN [Defendants] AS [rp.ProductDefendant.Defendant] ON [rp.ProductDefendant].[DefendantID] = [rp.ProductDefendant.Defendant].[DefendantID]
WHERE ([rp.ProductDefendant.Defendant].[DefendantCode] LIKE (N'%' + 'cert') + N'%' AND ([rp].[IsActive] = 1)) AND ([r].[RecordID] = [rp].[RecordID])))
) AS [t]
ORDER BY [t].[SourceCode]
OFFSET 0 ROWS FETCH NEXT 500 ROWS ONLY
Here is the new sql that works better, just not sure how to convert it back to a lambda expression
SELECT [t].[Id]
,[t].[StartDate]
,[t].[EndDate]
,[t].[WitnessName]
,[t].[SourceCode]
,[t].[JobsiteName]
,[t].[ShipName]
,[t].[EmployerCode]
FROM (
SELECT DISTINCT [r].[RecordID] AS [Id]
,[r].[StartDate]
,[r].[EndDate]
,[r.Witness].[FullName] AS [WitnessName]
,CASE
WHEN [r].[SourceID] IS NOT NULL
THEN [r.Source].[SourceCode]
ELSE N'zzzzz'
END AS [SourceCode]
,CASE
WHEN [r].[JobsiteID] IS NOT NULL
THEN [r.Jobsite].[JobsiteName]
ELSE N'zzzzz'
END AS [JobsiteName]
,CASE
WHEN [r].[ShipID] IS NOT NULL
THEN [r.Ship].[ShipName]
ELSE N'zzzzz'
END AS [ShipName]
,CASE
WHEN [r].[EmployerID] IS NOT NULL
THEN [r.Employer].[DefendantCode]
ELSE N'zzzzz'
END AS [EmployerCode]
FROM [Records] AS [r]
LEFT JOIN [Ships] AS [r.Ship] ON [r].[ShipID] = [r.Ship].[ShipID]
LEFT JOIN [Jobsites] AS [r.Jobsite] ON [r].[JobsiteID] = [r.Jobsite].[JobsiteID]
LEFT JOIN [Sources] AS [r.Source] ON [r].[SourceID] = [r.Source].[SourceID]
LEFT JOIN [Witnesses] AS [r.Witness] ON [r].[WitnessID] = [r.Witness].[WitnessID]
LEFT JOIN [Defendants] AS [r.Contractor] ON [r].[ContractorID] = [r.Contractor].[DefendantID]
LEFT JOIN [Defendants] AS [r.Employer] ON [r].[EmployerID] = [r.Employer].[DefendantID]
LEFT JOIN (
SELECT [rp].[RecordID]
FROM [Records_Products] AS [rp]
INNER JOIN [Product_Defendant] AS [rp.ProductDefendant] ON [rp].[DefendantProductID] = [rp.ProductDefendant].[DefendantProductID]
INNER JOIN [Defendants] AS [rp.ProductDefendant.Defendant] ON [rp.ProductDefendant].[DefendantID] = [rp.ProductDefendant.Defendant].[DefendantID]
WHERE (
[rp.ProductDefendant.Defendant].[DefendantCode] LIKE (N'%' + 'cert') + N'%'
AND ([rp].[IsActive] = 1)
)
) AS RecordProduct ON [r].[RecordID] = RecordProduct.[RecordID]
WHERE ([r].[IsActive] = 1)
AND (
(
[r.Employer].[DefendantCode] LIKE (N'%' + 'cert') + N'%'
OR [r.Contractor].[DefendantCode] LIKE (N'%' + 'cert') + N'%'
)
OR RecordProduct.RecordID IS NOT NULL --OR EXISTS ( -- SELECT 1 -- FROM [Records_Products] AS [rp] -- INNER JOIN [Product_Defendant] AS [rp.ProductDefendant] ON [rp].[DefendantProductID] = [rp.ProductDefendant].[DefendantProductID] -- INNER JOIN [Defendants] AS [rp.ProductDefendant.Defendant] ON [rp.ProductDefendant].[DefendantID] = [rp.ProductDefendant.Defendant].[DefendantID] -- WHERE ([rp.ProductDefendant.Defendant].[DefendantCode] LIKE (N'%' + 'cert') + N'%' -- AND ([rp].[IsActive] = 1)) AND ([r].[RecordID] = [rp].[RecordID]) -- ) )) AS [t]ORDER BY [t].[SourceCode]OFFSET 0 ROWS FETCH NEXT 500 ROWS ONLY
)
)
The linq expression you supplied and the SQL generated do not match. For one, the linq expression is performing an Include on the various related tables which would have included all of those entity columns in the top-level SELECT which are not present in your example SQL. I also don't see conditions in the Linq expression for the Take 500 & OrderBy, or IsActive assertion on Record.
To be able to help determine the source of any performance concern we need to see the complete Linq expression and the resulting SQL.
Looking at the basis of the Linq expression you provided:
records = records
.Include(r => r.Employer)
.Include(r => r.Contractor)
.Include(r => r.RecordProducts)
.ThenInclude(rp => rp.ProductDefendant.Defendant)
.Where(r => EF.Functions.Like(r.Employer.DefendantCode, "%" + input.DefendantCode + "%")
|| EF.Functions.Like(r.Contractor.DefendantCode, "%" + input.DefendantCode + "%")
|| r.RecordProducts.Any(rp => EF.Functions.Like(rp.ProductDefendant.Defendant.DefendantCode, "%" + input.DefendantCode + "%") && rp.IsActive == true));
There are a few suggestions I can make:
There is no need for the Functions.Like. You should be able to achieve the same with Contains.
Avoid using Include and instead utilize Select to retrieve the columns from the resulting structure that you actually need. Populate these into ViewModels or consume them in the code. The less data you pull back, the better optimized the SQL can be for indexing, and the less data pulled across the wire. Consuming entities also leads to unexpected lazy-load scenarios as systems mature and someone forgets to Include a new relation.
.
records = records
.Where(r => r.IsActive
&& (r.Employer.DefendantCode.Contains(input.DefendantCode)
|| r.Contractor.DefendantCode.Contains(input.DefendantCode)
|| r.RecordProducts.Any(rp => rp.IsActive
&& rp.ProductDefendant.Defendant.DefendantCode.Contains(input.DefendantCode))
.OrderBy(r => r.SourceCode)
.Select(r => new RecordViewModel
{
// Populate the data you want here.
}).Take(500).ToList();
This also adds the IsActive check, OrderBy, and Take(500) based on your sample SQL.

ExecuteSQL Concatenate string and integer

I am using trying to populate a dropdown using magicVaueList hack and ExecuteSQl and I am having trouble with joining a text and number.
Here is my code:
MVL_Dropdown ( ExecuteSQL ( "select L.Product + GetAsText( L.Quantity )
from T08_ESTIMATES E
join T09_EST_LINE_ITEMS L on E.ID_Estimate = L.id_estimate
where E.ID_Estimate = ? "; ""; ""; T19_TASKS::preitem ) )
The trouble I get is with the + GetAsText( L.Quantity ) where I have also tried & GetAsText( L.Quantity ) will no results but if I was to remov ethe join like:
MVL_Dropdown ( ExecuteSQL ( "select L.Product
from T08_ESTIMATES E
join T09_EST_LINE_ITEMS L on E.ID_Estimate = L.id_estimate
where E.ID_Estimate = ? "; ""; ""; T19_TASKS::preitem ) )
Then it works minus the fact that I will need both values. I'm therefore certain the problem exists Concatenating my text and number but I'm quite new to FileMaker and not sure what to use to get it to work.
.
Any help appreciated.
For anyone wondering i had to use a double pipe like so:
L.Product || ' - ' || L.Quantity
MVL_Dropdown ( ExecuteSQL ( "SELECT L.Product || ' - ' || L.Quantity || ' off'
FROM T08_ESTIMATES E
JOIN T09_EST_LINE_ITEMS L ON E.ID_Estimate = L.id_estimate
WHERE E.ID_Estimate = ? "; ""; ""; T19_TASKS::preitem ) )
STRVAL - Converts a value of any type to a character string
Here I wanted to concatenate an integer "Quantity" and string "Units":
ExecuteSQL ( "
SELECT p.Description, STRVAL ( m.Quantity ) || p.Units
FROM JobMaterials m
JOIN Products p
ON m.kf_Product = p.kp_ProductID
WHERE m.kf_JobID = ?
" ;
"¦" ; "¶" ;
Jobs::kp_JobID
)

EF Core FromSql Returning Odd Error Message

This code throws an exception when rawVoters.Count() is called:
string sql = #"select * from Voters as v
inner join Homes as h on v.HomeID = h.ID
inner join Locations as l on h.LocationID = l.ID
inner join Streets as s on l.StreetID = s.ID
inner join Cities as c on s.CityID = c.ID
inner join VoterAgencies as va on v.CountyID = va.CountyID and v.VoterID = va.VoterID
where (va.AgencyID = #agencyID)
and (c.Name like '%' + #city + '%')
and (v.FirstName like '%' + #firstName + '%')
and (v.LastName like '%' + #lastName + '%')
and (s.Name like '%' + #street + '%')
and ((#voterID = 0) or (v.VoterID = #voterID))";
List<SqlParameter> parameters = new List<SqlParameter>();
parameters.Add( new SqlParameter( "#agencyID", agencyID ) );
parameters.Add( new SqlParameter( "#city", model.City ) );
parameters.Add( new SqlParameter( "#firstName", model.FirstName ) );
parameters.Add( new SqlParameter( "#lastName", model.LastName ) );
parameters.Add( new SqlParameter( "#street", model.Street ) );
parameters.Add( new SqlParameter( "#voterID", model.VoterID ) );
IQueryable<Voter> rawVoters = _context.Voters
.AsNoTracking()
.FromSql( sql, parameters.ToArray() );
int numVoters = 0;
try
{
numVoters = rawVoters.Count();
}
catch( Exception e )
{
int i = 9;
i++;
}
The error message is:
"The column 'ID' was specified multiple times for 'v'."
I thought this might be because EF Core doesn't like the "as x" phrasing, so I substituted the table names for each of the identifiers...and got the same error message.
I'm curious as to what's going on here.
The problem was that the T-SQL was returning all fields (select *). Under EF Core, the returned fields must match the fields specified for the entity being returned, in this case Voter.
An inner join, like the one I was using, by default returns far more than just the Voter fields.
Changing the SQL to be select v.* (where v is the alias for Voters) solved the problem.
Hence you're just getting the Count,you can specify your column name as shown below.Then no issues :)
string sql = #"select v.ID from Voters as v

OR clause in Zend DB update?

I'd like to do a Zend db update with an OR clause. What would be the equivalent statement to:
UPDATE mail
SET message_read = 1
WHERE id = 5
OR id = 10
When calling Zend_Db_Adapter::update(), multiple WHERE conditions will automatically be combined using AND (line 698 of Zend/Db/Adapter/Abstract.php in function _whereExpr).
You can get around this by creating your own Zend_Db_Expr which you will use as the WHERE condition and it will be left untouched.
For example:
$where[] = new Zend_Db_Expr(
$table->getAdapter()->quoteInto('id = ?', 5) . ' OR ' .
$table->getAdapter()->quoteInto('id = ?', 10)
);
// resulting expression:
// WHERE (id = 5 OR id = 10)
$table->update($data, $where);
If you had additional WHERE conditions, they would be combined with the OR condition by an AND.
Example:
$where[] = new Zend_Db_Expr(
$table->getAdapter()->quoteInto('id = ?', 5) . ' OR ' .
$table->getAdapter()->quoteInto('id = ?', 10)
);
$where[] = $table->getAdapter()->quoteInto('type = ?', 'sometype');
// resulting expression:
// WHERE (id = 5 OR id = 10) AND (type = 'sometype')
->where() will add a where clause to the query and will put an 'AND'. There is an orWhere method that exists to do that.
$select = $this->select();
$select->where('id = 5');
$select->orWhere('id = 10');
$this->fetchAll($select);

How do I add complex where clause to Zend Table Select?

I searched the Web and could not find anything that would show me a good solid example. My question is basically this:
How do I convert this:
SELECT * FROM table WHERE ((a = 1 AND b = 2) OR (c = 3 OR c = 4)) AND d = 5;
To Zend syntax similar to this:
$this
->select()
->from($this->_schema.'.'.$this->_name)
->where('a = ?', '1');
So how can it be done?
Thank a lot in advance.
I had a similar problem. See the code example in the answer here: Grouping WHERE clauses with Zend_Db_Table_Abstract
So you would end up with something like:
$db = $this->getAdapter();
$this->select()
->where('(' . $db->quoteInto('a = ?', 1) . ' AND ' . $db->quoteInto('b = ?', 2) . ') OR (' . $db->quoteInto('c = ?', 3) . ' OR ' . $db->quoteInto('c = ?', 4) . ')')
->where('d = ?', 5);
Which would give you:
SELECT `table_name`.* FROM `table_name` WHERE ((a = 1 AND b = 2) OR (c = 3 OR c = 4)) AND (d = 5)
1) Build a condition for all groups Where/orWhere:
$conditions = $this->select()
->where('a= ?', 5)
->orWhere('b= ?', 6)
->getPart(Zend_Db_Select::WHERE);
// result: $conditions = "(a= 5) OR (b= 6)";
Use getPart() method to get the where condition.
2) Next, reset the where part of current select object:
$this->select()->reset(Zend_Db_Select::WHERE);
3) Finally, use where condition as you want:
$this->select()
->where('d= ?', 5)
->where(implode(' ', $conditions));
http://framework.zend.com/manual/1.12/ru/zend.db.select.html
Per a message board post on the Zend Framework website, this may not be possible.
It seems to me that where() and orWhere() in the Zend_Db_Select class are not enough to be able to write all queries. It does not support the nesting of conditions, which doesn't enforce the user with abstraction in somewhat more complex cases. With where() and orWhere() I cannot write this:
Edit
The array functionality of Zend_Db_Select->where is designed only for using it with the IN clause.
Example #17 Example of an array parameter in the where() method
// Build this query:
// SELECT product_id, product_name, price
// FROM "products"
// WHERE (product_id IN (1, 2, 3))
$productIds = array(1, 2, 3);
$select = $db->select()
->from('products',
array('product_id', 'product_name', 'price'))
->where('product_id IN (?)', $productIds);
Original
As Peder said you can't nest orWhere but you can pass multiple arguments into where and orWhere.
$this->select()
->from($this->_schema.'.'.$this->_name)
->where(' ( a = ? AND b = ? ) OR ( c = ? OR c = ? ) ', array(1,2,3,4))
->where('d = ?',array(5));