Yii2: How to use orWhere in andWhere - yii

I want create this query with yii2 search model
select * from t1 where (title = 'keyword' or content = 'keyword') AND
(category_id = 10 or term_id = 10 )
But I don't know how to use orFilterWhere and andFilterWhere.
My code in search model:
public function search($params) {
$query = App::find();
//...
if ($this->keyword) {
$query->orFilterWhere(['like', 'keyword', $this->keyword])
->orFilterWhere(['like', 'content', $this->keyword])
}
if ($this->cat) {
$query->orFilterWhere(['category_id'=> $this->cat])
->orFilterWhere(['term_id'=> $this->cat])
}
//...
}
But it creates this query:
select * from t1 where title = 'keyword' or content = 'keyword' or
category_id = 10 or term_id = 10

First, your required sql statement should be something like this:
select *
from t1
where ((title LIKE '%keyword%') or (content LIKE '%keyword%'))
AND ((category_id = 10) or (term_id = 10))
So your query builder should be something like this:
public function search($params) {
$query = App::find();
...
if ($this->keyword) {
$query->andFilterWhere(['or',
['like','title',$this->keyword],
['like','content',$this->keyword]]);
}
if ($this->cat) {
$query->andFilterWhere(['or',
['category_id'=> $this->cat],
['term_id'=> $this->cat]]);
}...

Related

TYPO3 9.5 Extbase Query for UidArray

I´ve got the following SQL-Query
$uidArray = explode(",", $uids);
foreach ($uidArray as $uid) {
$dynamicUid[] = '`uid` LIKE \''.$uid.'\'';
}
$query = $this->createQuery();
$query->statement("SELECT * FROM `tx_myextension_domain_model_thi` WHERE ".implode($dynamicUid, " OR "));
return $query->execute();
This works fine but I want to have it like this:
$uidArray = explode(",", $uids);
$query = $this->createQuery();
foreach ($uidArray as $key => $value) {
$constraints[] = $query->equals('uid', $value);
}
return $query->matching(
$query->logicalAnd($constraints)
)->execute();
Here I get the following Output with the Query Parser :
'SELECT `tx_myextension_domain_model_thi`.* FROM `tx_myextension_domain_model_thi`
`tx_myextension_domain_model_thi`
WHERE ((`tx_myextension_domain_model_thi`.`uid` = :dcValue1) AND
(`tx_myextension_domain_model_thi`.`uid` = :dcValue2)) AND
(`tx_myextension_domain_model_thi`.`sys_language_uid` IN (0, -1)) AND
(`tx_myextension_domain_model_thi`.`pid` = 0) AND
((`tx_myextension_domain_model_thi`.`deleted` = 0) AND (
`tx_myextension_domain_model_thi`.`t3ver_state` <= 0) AND
(`tx_myextension_domain_model_thi`.`pid` <> -1) AND
(`tx_myextension_domain_model_thi`.`hidden` = 0) AND
(`tx_myextension_domain_model_thi`.`starttime` <= 1607084460) AND
((`tx_myextension_domain_model_thi`.`endtime` = 0)
OR (`tx_myextension_domain_model_thi`.`endtime` > 1607084460)))'
And the Uids as dcValue-Array.
dcValue1 => '1' (1 chars)
dcValue2 => '2' (1 chars)
Maybe someone can help me to rewrite this, because unfortunately I can't get any further!
Thanks :)
Did you try the in operator?
public function yourFunctionName($uid)
{
$query = $this->createQuery();
$query->in('uid', $uidArray);
return $query->execute();
}
Assuming that your array looks like this:
$uidArray = [
0 = '34',
1 = '15',
3 = '88'
]
EDIT
If you do not care about where your objects are stored then you can do the following.
public function yourFunctionName($uid)
{
$query = $this->createQuery();
$query->getQuerySettings()->setRespectStoragePage(FALSE);
$query->matching(
$query->in('uid', $uidArray)
);
return $query->execute()];
}
Which is going to ignore the pid in the query

I want to get a variable in typoscript select Query

The title says it all; I need a Typoscript SQL SELECT query with a dynamic WHERE clause.
I get the UID of the current logged in user with:
data = TSFE:fe_user|user|uid
But how can I use that in a database query?
I store the UID in a typoscript variable named {userID}.
That's my code so far. The Problem is Typoscript just merge the two values:
// User One has value: 50
// User Two has value: 32
// With this code the output is : 5032 ????
lib.coins = CONTENT
lib.coins {
table = fe_users
select {
pidInList = 18
where = uid
andWhere.insertData = TSFE:fe_user|user|uid
selectFields = coins
}
renderObj = COA
renderObj {
1 = TEXT
1.field = coins
}
}
You should make use of the select option markers:
lib.coins = CONTENT
lib.coins {
table = fe_users
select {
selectFields = coins
pidInList = 18
where = uid = ###UID###
markers {
UID.data = TSFE:fe_user|user|uid
}
}
renderObj = COA
renderObj {
1 = TEXT
1.field = coins
}
}

generate mysql query from relations using via or viaTable relations

consider the below example...
class Customers extends ActiveRecord
{
public function getOrders()
{
return $this->hasMany(Orders::className(), ['customer_id' => 'id']);
}
public function getOrderItems()
{
return $this->hasMany(OrderItems::className(), ['order_id' => 'id'])
->via('orders');
}
}
how i can generate any one of the follwing query from the getOrderItems() relation
SELECT * FROM `order-items`
LEFT JOIN `orders` ON `orders`.`id` = `order-items`.`order_id`
LEFT JOIN `customers` ON `customer`.`id` = `orders`.`customer_id`
OR
SELECT `order-items`.* FROM `order-items`,`orders`,`customers`
WHERE `customer`.`id` = `orders`.`customer_id` AND `orders`.`id` = `order-items`.`order_id`
OR
SELECT * FROM `order-items` WHERE `order_id` IN(
SELECT * FROM `orders` WHERE `customer_id` IN(
SELECT * FROM `customers`
)
)
i use the following code to do this.
$customers = Customers::findAll();
$query = $customers[0]->getOrderItems()->createCommand()->rawSql;
but it only generates
SELECT * FROM `order-items`
What to do...???
use this :
$q = Customers::findAll()->innerJoinWith('orderItems')->createCommand()->rawSql;
you have to use relation name like this not as function

how to merge duplicate records into one record by linq

ID Name from to
001-1 ABC 2015/05/01 2015/05/31
001-1 ABC 2015/06/01 2015/07/15
003-2 DEF 2015/05/01 2015/05/11
002-1 LMN 2015/05/01 2015/06/15
002-1 LMN 2015/06/16 2015/07/31
003-2 DEF 2015/06/01 2015/07/15
004-5 GHI 2015/05/11 2015/05/15
I want to have merge the records into one which matching the period from 2015/05/15 to 2015/07/15 like the following result in datable.
ID Name from to
001-1 ABC 2015/05/01 2015/07/15
002-1 LMN 2015/05/01 2015/07/31
003-2 and 004-5 are not in new datatable as they are not in the require range.
How can I get this? I only know very basic knowledge about LINQ and it's very fresh to me. thx.
With this class / data as a mockup:
class Item
{
public string ID { get; set; }
public string Name { get; set; }
public DateTime From { get; set; }
public DateTime To { get; set; }
}
List<Item> items = new List<Item> {
new Item { ID = "001-1", Name = "ABC",
From = DateTime.Parse("2015/05/01"),
To = DateTime.Parse("2015/05/31") },
new Item { ID = "001-1", Name = "ABC",
From = DateTime.Parse("2015/06/01"),
To = DateTime.Parse("2015/07/15") },
new Item { ID = "003-2", Name = "DEF",
From = DateTime.Parse("2015/05/01"),
To = DateTime.Parse("2015/05/11") },
new Item { ID = "002-1", Name = "LMN",
From = DateTime.Parse("2015/05/01"),
To = DateTime.Parse("2015/06/15") },
new Item { ID = "002-1", Name = "LMN",
From = DateTime.Parse("2015/06/16"),
To = DateTime.Parse("2015/07/31") },
new Item { ID = "003-2", Name = "DEF",
From = DateTime.Parse("2015/06/01"),
To = DateTime.Parse("2015/07/15") },
new Item { ID = "004-5", Name = "GHI",
From = DateTime.Parse("2015/05/11"),
To = DateTime.Parse("2015/05/15") }
};
you can use the following linq query to get the desired result set:
var result = from i in items
orderby i.From
group i by new { i.ID, i.Name } into iGroup
where iGroup.First().From <= DateTime.Parse("2015/05/15") &&
iGroup.Last().To >= DateTime.Parse("2015/07/1") &&
(iGroup.Last().To - iGroup.First().From).Days + 1 ==
iGroup.Sum(g => (g.To - g.From).Days + 1)
select new Item
{
ID = iGroup.Key.ID,
Name = iGroup.Key.Name,
From = iGroup.First().From,
To = iGroup.Last().To
};
You can adjust datetime comparisons to fit your actual requirement. In the above linq query I am comparing the smallest From date and the largest To date of each group to the matching period dates.
This comparison:
(iGroup.Last().To - iGroup.First().From).Days + 1 ==
iGroup.Sum(g => (g.To - g.From).Days + 1)
checks for groups that have no gaps in their date ranges.
EDIT:
If the source data is stored in a DataTable such as:
DataTable items = new DataTable();
items.Columns.Add("ID", typeof(string));
items.Columns.Add("Name", typeof(string));
items.Columns.Add("From", typeof(DateTime));
items.Columns.Add("To", typeof(DateTime));
then the linq query becomes a bit more complicated:
var q = from i in items.AsEnumerable()
orderby i.Field<DateTime>("From")
group i by new { ID = i.Field<string>("ID"), Name = i.Field<string>("Name") } into iGroup
where iGroup.First().Field<DateTime>("From") <= DateTime.Parse("2015/05/15") &&
iGroup.Last().Field<DateTime>("To") >= DateTime.Parse("2015/07/1") &&
(iGroup.Last().Field<DateTime>("To") - iGroup.First().Field<DateTime>("From")).Days + 1 ==
iGroup.Sum(g => (g.Field<DateTime>("To") - g.Field<DateTime>("From")).Days + 1)
select new
{
ID = iGroup.Key.ID,
Name = iGroup.Key.Name,
From = iGroup.First().Field<DateTime>("From"),
To = iGroup.Last().Field<DateTime>("To")
};
The above query returns an IEnumerable of anonymous type. It can be converted back to a DataTable using Reflection (see this post for example).

Does ServiceStack.OrmLite.JoinSqlBuilder allow to build a simple query

I'm wondering if ServiceStack.OrmLite's JoinSqlBuilder allow to build the following simple query:
SELECT * FROM Table1 a
INNER JOIN Table2 b ON ...
WHERE a.Column1 = 1 AND (a.Column2 = 2 OR b.Column3 = 3);
The problem is to build (a.Column2 = 2 OR b.Column3 = 3) part.
JoinSqlBuilder has a list of methods such as Where<T>, And<T>, Or<T> that allow to add conditions for a query.
For example, if i do:
builder
.Join(...)
.Where<Table1Poco>(a => a.Column1 == 1)
.And<Table1Poco>(a => a.Column2 == 2)
.Or<Table2Poco>(a => a.Column3 == 3)
...;
I will get:
... WHERE a.Column1 = 1 AND a.Column2 = 2 OR b.Column3 = 3;
Is there any way to build a.Column1 = 1 AND (a.Column2 = 2 OR b.Column3 = 3) with ServiceStack.OrmLite?
I know that i can do it with raw sql but it's not an option as i don't want to lose type safety and dialect independence.
I agree with kunjee that this is not really something a Micro-orm is good for. With that said, I can think of 2 potential options...neither of which are really something I would recommend over a full-blown ORM (EF or nHibernate) as a solution. But, maybe this will help solicit better options.
Option 1 - Build up a 'Where clause string' using reflection to keep some 'type safety'. You will still need to write a little SQL.
Example
var jn = new JoinSqlBuilder<Table1, Table2>();
jn = jn.Join<Table1, Table2>(s => s.Column1, d => d.Field1);
//using ExpressionVisitor because I didn't see a way to allow a Where clause string parameter to be used
//on a JoinSqlBuilder method
var ev = OrmLiteConfig.DialectProvider.ExpressionVisitor<Table1>();
ev.Where(
SqlHelper.ToSqlField<Table1>(x => x.Column1) + "={0} AND (" +
SqlHelper.ToSqlField<Table1>(x => x.Column2) + "={1} OR " + SqlHelper.ToSqlField<Table2>(x => x.Column3) +
"={2})", "1", "2", "3");
var sql = jn.ToSql() + ev.WhereExpression;
Helper Class
public static class SqlHelper
{
public static string ToSqlField<T>(Expression<Func<T, object>> expression)
{
//This should return something like 'Table1.Column1'
return typeof(T).Name + "." + GetMemberInfo(expression).Name;
}
// Stolen from FluentNHibernate.ReflectionUtility
public static MemberInfo GetMemberInfo<TEntity>(Expression<Func<TEntity, object>> expression)
{
MemberInfo memberInfo = null;
switch (expression.Body.NodeType)
{
case ExpressionType.Convert:
{
var body = (UnaryExpression)expression.Body;
if (body.Operand is MethodCallExpression)
{
memberInfo = ((MethodCallExpression)body.Operand).Method;
}
else if (body.Operand is MemberExpression)
{
memberInfo = ((MemberExpression)body.Operand).Member;
}
}
break;
case ExpressionType.MemberAccess:
memberInfo = ((MemberExpression)expression.Body).Member;
break;
default:
throw new ArgumentException("Unsupported ExpressionType", "expression");
}
if (memberInfo == null) { throw new ArgumentException("Could not locate MemberInfo.", "expression"); }
return memberInfo;
}
}
Option 2 - Mess/Pollute your Classes and turn off Table prefixes in an ExpressionVisitor to allow the correct SQL to be generated. This will completely blow up if 2 classes have the same property and are used in a Where clause.
//Modify Table1 to include a reference to Table2
public class Table1
{
public string Column1 { get; set; }
public string Column2 { get; set; }
[ServiceStack.DataAnnotations.Ignore]
public Table2 Table2 { get; set; }
}
var ev = OrmLiteConfig.DialectProvider.ExpressionVisitor<Table1>();
ev.PrefixFieldWithTableName = false;
var jn = new JoinSqlBuilder<Table1, Table2>();
jn = jn.Join<Table1, Table2>(s => s.Column1, d => d.Field1);
ev.Where(x => x.Column1 == "1");
ev.Where(x => x.Column2 == "2" || ((Table2)x.Table2).Column3 == "3"); //do cast to avoid InvalidOperationException
var sql = jn.ToSql() + ev.WhereExpression;