Complex doctrine query form validation help! - doctrine

I am trying to check that a combination of entries across five columns (I have parent1, parent2, parent3, parent4, and parent5 all of which are integers) does not already exist in my form validation.
I tried:
$query = Doctrine_Query::create()
->select('m.id', 'm.name') -> from ('Mix m')
->where('m.parent1=?', $values['parent1'])
->orWhere('m.parent1=?', $values['parent2'])
->orWhere('m.parent1=?', $values['parent3'])
->orWhere('m.parent1=?', $values['parent4'])
->orWhere('m.parent1=?', $values['parent5'])
->andWhere('m.parent2=?', $values['parent1'])
->orWhere('m.parent2=?', $values['parent2'])
...and so on.
Probably not the most efficient way, but regardless it is throwing an error if one of the values matches any of the columns already in the database, it's not checking for the actual combination...
Any help would be very appreciated!

How about...
// Set of possible matches
$set = array($values['parent1'], $values['parent2'], $values['parent3'], $values['parent4'], $values['parent5']);
// Let's make it two-dimensional for simpler DQL syntax
$sets = array($set, $set, $set, $set, $set);
// Multiple WHERE IN AND
$query = Doctrine_Query::create()
->select('m.id', 'm.name')
->from ('Mix m')
->where('m.parent1 IN ? AND m.parent2 IN ? AND m.parent3 IN ? AND m.parent4 IN ? AND m.parent5 IN ?', $sets);
The WHERE IN AND should make sure each value is matched. However, at least one fail case that comes to mind is that if multiple values in the $set are the same (e.g. '5, 5, 1, 4, 1', the query will give false matches. Maybe someone else can improve on that. Might be a slow query as well.

Related

Efficient way to query database with multiple conditions in laravel

Is it possible to make this a single query?
$yl_min = DB::connection($this->db2)->table('historical')
->where([['slug','=',$crypto_id],['low_usd','!=', null]])
->whereBetween('created_time',[$this->range_1y,$this->hislatest])
->min('low_usd');
$yl = DB::connection($this->db2)->table('historical')
->select('id','coin','low_usd','created_time','created_at')
->where([['slug','=',$crypto_id],['low_usd',$yl_min]])
->whereBetween('created_time',[$this->range_1y,$this->hislatest])
->first();
I've tried this but no luck:
$yl = DB::connection($this->db2)->table('historical')
->select('id','coin','created_time','created_at',DB::raw('SELECT MIN(low_usd) as low_usd'))
->where([['slug','=',$crypto_id],['low_usd','!=', null]])
->whereBetween('created_time',[$this->range_1y,$this->hislatest])
->first();
After looking at your query code, I found the two query condition is same, and you just want to get min low_usd record,
I think you can just use the multiple condition and ORDER BY low_usd ASC, then take the first one:
$yl = DB::connection($this->db2)->table('historical')
->where([['slug','=',$crypto_id],['low_usd','!=', null]])
->whereBetween('created_time',[$this->range_1y,$this->hislatest])
->orderBy('low_usd','asc')
->select('id','coin','low_usd','created_time','created_at')
->first();
After this, if you want to make this query more efficient,
you need to add index on slug, low_usd, created_time

Laravel Offset not offsetting and/or Skip not skipping

i have a database with data and i want to skip/offset the first 3 row.
$data = Data::orderBy('created_at','desc')->skip(3)->paginate(1);
$data = Data::orderBy('created_at','desc')->offset(3)->paginate(1);
both query is returning all result from the start. can anyone help me with this?
Thanks.
skip doesn't seem to work with paginate. What you can do is exclude the row by using whereNotIn.
$data = Data::orderBy('created_at','desc')->whereNotIn('id', [1,2,3])->paginate(1);
If you don't know the id you can query and use the result.
$id = Data::orderBy('created_at','desc')->take(3)->pluck('id');
$data = Data::orderBy('created_at','desc')->whereNotIn('id', $id)->paginate(1);
You can not use paginate() and skip() together. You can do is :
$data = Data::orderBy('created_at','desc')->skip(3)->take(10)->get(); and update these values skip and take values as per your custom implementation.
If you literally want to skip first 3 rows and never ever use them in pagination, you can do :
$dataToEliminate = Data::orderBy('created_at','desc')->take(3)->select('id')->pluck('id');
$data = Data::whereNotIn('id', $dataToEliminate)->orderBy('created_at','desc')->skip(3)->paginate(1);
See documentation for reference.
I have explored the paginate method from the code and came to know
$data = Data::orderBy('created_at','desc')->paginate(1, '*', null, 2);
The 4th parameter, you need to provide the page number (not the offset).

Laravel Eloquent - distinct() and count() not working properly together

So I'm trying to get the number of distinct pids on a query, but the returned value is wrong.
This is what I try to do:
$ad->getcodes()->groupby('pid')->distinct()->count()
what returns the value "2", while the value it should return, should be "1".
As a workaround, I'm doing this:
count($ad->getcodes()->groupby('pid')->distinct()->get())
what works fine and returns "1"
Is there any rule where count and distinct cannot be on the same query? I find the workaround kind of "heavy", I would like to make the original query work :(
The following should work
$ad->getcodes()->distinct()->count('pid');
A more generic answer that would have saved me time, and hopefully others:
Does not work (returns count of all rows):
DB::table('users')
->select('first_name')
->distinct()
->count();
The fix:
DB::table('users')
->distinct()
->count('first_name');
Anyone else come across this post, and not finding the other suggestions to work?
Depending on the specific query, a different approach may be needed. In my case, I needed either count the results of a GROUP BY, e.g.
SELECT COUNT(*) FROM (SELECT * FROM a GROUP BY b)
or use COUNT(DISTINCT b):
SELECT COUNT(DISTINCT b) FROM a
After some puzzling around, I realised there was no built-in Laravel function for either of these. So the simplest solution was to use use DB::raw with the count method.
$count = $builder->count(DB::raw('DISTINCT b'));
Remember, don't use groupBy before calling count. You can apply groupBy later, if you need it for getting rows.
You can use the following way to get the unique data as per your need as follows,
$data = $ad->getcodes()->get()->unique('email');
$count = $data->count();
Hope this will work.
I had a similar problem, and found a way to work around it.
The problem is the way Laravel's query builder handles aggregates. It takes the first result returned and then returns the 'aggregate' value. This is usually fine, but when you combine count with groupBy you're returning a count per grouped item. So the first row's aggregate is just a count of the first group (so something low like 1 or 2 is likely).
So Laravel's count is out, but I combined the Laravel query builder with some raw SQL to get an accurate count of my grouped results.
For your example, I expect the following should work (and let you avoid the get):
$query = $ad->getcodes()->groupby('pid')->distinct();
$count = count(\DB::select($query->toSql(), $query->getBindings()));
If you want to make sure you're not wasting time selecting all the columns, you can avoid that when building your query:
$query = $ad->select(DB::raw(1))->getcodes()->groupby('pid')->distinct();
I came across the same problem.
If you install laravel debug bar you can see the queries and often see the problem
$ad->getcodes()->groupby('pid')->distinct()->count()
change to
$ad->getcodes()->distinct()->select('pid')->count()
You need to set the values to return as distinct. If you don't set the select fields it will return all the columns in the database and all will be unique. So set the query to distinct and only select the columns that make up your 'distinct' value you might want to add more. ->select('pid','date') to get all the unique values for a user in a day
Based on Laravel docs for raw queries I was able to get count for a select field to work with this code in the product model.
public function scopeShowProductCount($query)
{
$query->select(DB::raw('DISTINCT pid, COUNT(*) AS count_pid'))
->groupBy('pid')
->orderBy('count_pid', 'desc');
}
This facade worked to get the same result in the controller:
$products = DB::table('products')->select(DB::raw('DISTINCT pid, COUNT(*) AS count_pid'))->groupBy('pid')->orderBy('count_pid', 'desc')->get();
The resulting dump for both queries was as follows:
#attributes: array:2 [
"pid" => "1271"
"count_pid" => 19
],
#attributes: array:2 [
"pid" => "1273"
"count_pid" => 12
],
#attributes: array:2 [
"pid" => "1275"
"count_pid" => 7
]
$solution = $query->distinct()
->groupBy
(
[
'array',
'of',
'columns',
]
)
->addSelect(
[
'columns',
'from',
'the',
'groupby',
]
)
->get();
Remember the group by is optional,this should work in most cases when you want a count group by to exclude duplicated select values, the addSelect is a querybuilder instance method.
Wouldn't this work?
$ad->getcodes()->distinct()->get(['pid'])->count();
See here for discussion..
Distinct do not take arguments as it adds DISTINCT in your sql query, however, you MAY need to define the column name that you'd want to select distinct with. Thus, if you have
Flight->select('project_id')->distinct()->get() is equialent to SELECT DISTINCT 'project_id' FROM flights and you may now add other modifiers like count() or even raw eloquent queries.
Use something like this
DB::table('user_products')->select('user_id')->distinct()->pluck('user_id')->toArray();
This was working for me so
Try This:
$ad->getcodes()->distinct('pid')->count()
try this
$ad->getcodes()->groupby('pid')->distinct()->count('pid')

Query Strings and Laravel (with inequalities)

I am creating an API in Laravel and using query strings to handle complex queries.
It is easy to handle queries like url/item/?color=red&age=3... to collect all items that are red and 3 years old.
But this is because these are discrete variables being queried for equality. Say for example I want to retrieve all users who registered after a certain date. How would I handle this?
I was thinking maybe:
url/item/?registered_later_than=DDMMYYYY
Is there a better way?
I'd suggest something like this:
url/item/?registered=>:DDMMYYYY
The parameter name is the name of the attribute
Right at the beginning of the parameter value is the operator
Operator and value is separated by a : (it actually can be any separation character you want)
Other examples:
url/item/?name=like:foo
url/item/?email==:foo.bar#example.com
I agree email==:foo looks a bit weird. You could also use words or abbreviations ("eq", "gt", etc) instead of operator signs.
How to parse it
$filters = Input::all();
$query = Model::newQuery();
foreach($filters as $attribute => $filter){
$parts = explode(':', $filter, 2);
$operator = $parts[0];
$value = $parts[1];
$query->where($attribute, $operator, $value);
}
I hope this gives you an idea how you could do it ;)
Not really a "better way" but you can try something like this :
url/item/?operator=lt&date=20141223
Operator can be :
lt : lesser than
gt : greater than
eq : equals to
etc.
Or whatever you want (maybe it's more readable with in full text: "greater_than", etc.). Hope I understood well your question and it will help you.

How and where to modify Magento search query?

First of all, I found similar questions in SO but there is not any answer for them. So, the first part of the question is a little bit duplicated. I want to improve search results in Magento. Here is what I've done already:
1. Search with AND instead of OR when there are multiple words.
2. Ajax search starts searching from anywhere and not only from the beginning of the fields.
3. Trim the last s from the words to prevent empty results when searching with plurals.
4. I changed the search type from Like to Fulltext or Combine but the results were not better and even were worst, so I leave it as is. It's Like now, so there is no relevance ordering.
The last thing which I want to try is adding this to the search query:
SELECT ... other non-full-text-cols
MATCH (product_title) AGAINST ('lean body for her') AS rel1,
MATCH (content) AGAINST ('lean body for her') AS rel2
FROM table
WHERE MATCH (product_title,content) AGAINST ('lean body for her')
ORDER BY (rel1*1.5)+(rel2)
Here is my query but I'm not sure if it would work because I can't test it:
$this->_productCollection->addAttributeToSelect(
array(
'rel1' => new Zend_Db_Expr('MATCH (name) AGAINST ("'.$queryText.'")'),
'rel2' => new Zend_Db_Expr('MATCH (short_description) AGAINST ("'.$queryText.'")')
)
);
$this->_productCollection->getSelect()
->where('MATCH (name,short_description) AGAINST ("'.$queryText.'")')
->order('(rel1*1.5)+(rel2)');
The main idea is to add bonus weight to a result if the search query is found in the title of the product. The problem is that I don't know where to modify the query. I can't find where it is at all. $this->_productCollection is not the right object, I know it. I looked at all the Collection.php files, resource models, models and even the query log but no luck. There are just little 1 or 2 row parts in some files but not a full query. I'm new to Magento and still have problems with finding this type of stuff. So, where I have to place my additional stuff when I have to extend a query?
Community Edition Magento, version 1.6.1.0.
Note: I know that some extension for improving search results will work much better than my solutions but for now I have to do it in that way. It would be a good experience for me, too.
Edit:
So, I figured out how to add my custom fields for the ordering but it's
untruly I think. In class Mage_CatalogSearch_Model_Layer extends Mage_Catalog_Model_Layer's prepareProductCollection method I added two joins to the query and get the fields rel1 and rel2:
$collection->getSelect()->joinLeft(
array('cpev' => 'catalog_product_entity_varchar'),
'cpev.entity_id = e.entity_id AND cpev.attribute_id = 96',
array('rel1' => new Zend_Db_Expr('2.01*(LENGTH(cpev.value) - LENGTH(REPLACE(LCASE(cpev.value), LCASE("'.$queryText.'"), ""))) / LENGTH("'.$queryText.'")'))
);
$collection->getSelect()->joinLeft(
array('cpet' => 'catalog_product_entity_text'),
'cpet.entity_id = e.entity_id AND cpet.attribute_id = 506',
array('rel2' => new Zend_Db_Expr('(LENGTH(cpet.value) - LENGTH(REPLACE(LCASE(cpet.value), LCASE("'.$queryText.'"), ""))) / LENGTH("'.$queryText.'")'))
);
I have these fields now but as you can see I have hard coded stuff like attribute_id = 96 etc. which is not good at all and it will not work everytime - I checked these ids directly from the database tables. I wrote it like this because I haven't access to name and short_description fields but they are in the result. Don't know why. So, cpev.value is name field and cpet.value is the short_description field. Moreover I can't order the results by these fields. I tried $collection->addOrder('SUM(rel1+rel2)');, $collection->getSelect()->order(new Zend_Db_Expr('SUM(rel1+rel2)').' DESC');, some addAttributeToFilter stuff etc. but it's not working.
Edit 2:
I accepted #james' answer but finally we bought an extension for improving the search results.
In Mage_CatalogSearch_Model_Resource_Fulltext check out prepareResult (line 310 in 1.7 CE) and look for the following:
$select->columns(array('relevance' => new Zend_Db_Expr(0)));
Magento sets all search result relevances as 0 (!); add the relevances you want (higher is better) here. You can create a custom query in Zend_Db_Expr() to generate higher relevances on attribute matches.
The answer to your first question (1):
To make an AND search instead of OR, you will need to rewrite the class
Mage_CatalogSearch_Model_Resource_Fulltext
In the method
public function prepareResult($object, $queryText, $query)
you want to switch the part
$likeCond = '(' . join(' OR ', $like) . ')';
to
$likeCond = '(' . join(' AND ', $like) . ')';
Be sure to reindex the search index afterwards to have an effect.

Resources