use field as recursive function in where condition in yii2 - yii

I want to send content of date1 to exp function in where() condition and return the result.
Note: Actually I want to compare two date, that I need to change one of dates to explode
Here is my code:
function exp($date){
$date = explode('/', $date);
$Y = $date[0];
$m = $date[1];
$d = $date[2];
return $Y;
}
$promise = new ActiveDataProvider([
'query' => Post::find()
->where('status = "show"')
->andWhere(['<=', exp('date1'), 'date2'])// date1 is: 2018/02/03
->orderBy('id'),
]);
Is there any way else to do this?

exp is a PHP function, in andWhere you prepare an SQL query that will run on the SQL server. The SQL parser on the SQL server can not execute PHP functions. You should use MySQL DATE_FORMAT function here:
$promise = new ActiveDataProvider([
'query' => Post::find()
->where('status = "show"')
->andWhere(['<=', 'DATE_FORMAT(date1, "%Y")', 'date2'])
->orderBy('id'),
]);
Please change the name of the fields date1 and date2 to make them more informative. For ex. order_date, delivery_date etc.
If status values are often used in your code, you should replace them with a constants of the Postclass.
If date2 contains only year values, it is better to use the YEAR type.

Related

Can Laravel automatically switch between column = ? and column IS NULL depending on value?

When building a complex SQL query for Laravel, using ? as placeholders for parameters is great. However when the value is null, the SQL syntax needs to be changed from = ? to IS NULL. Plus, since the number of parameters is one less, I need to pass a different array.
To get it to work, I have written it like this, but there must be a better way:
if ($cohortId === null) {
// sql should be: column IS NULL
$sqlCohortString = "IS NULL";
$params = [
Carbon::today()->subDays(90),
// no cohort id here
];
} else {
// sql should be: column = ?
$sqlCohortString = "= ?";
$params = [
Carbon::today()->subDays(90),
$cohortId
];
}
$query = "SELECT items.`name`,
snapshots.`value`,
snapshots.`taken_at`,
FROM snapshots
INNER JOIN (
SELECT MAX(id) AS id, item_id
FROM snapshots
WHERE `taken_at` > ?
AND snapshots.`cohort_id` $sqlCohortString
GROUP BY item_id
) latest
ON latest.`id` = snapshots.`id`
INNER JOIN items
ON items.`id` = snapshots.`item_id`
ORDER by media_items.`slug` ASC
";
$chartData = DB::select($query, $params);
My question is: does Laravel have a way to detect null values and replace ? more intelligently?
PS: The SQL is for a chart, so I need the single highest snapshot value for each item.
You can use ->when to create a conditional where clause:
$data = DB::table('table')
->when($cohortId === null, function ($query) {
return $query->whereNull('cohort_id');
}, function ($query) use ($cohortId) {
// the "use" keyword provides access to "outer" variables
return $query->where('cohort_id', '=', $cohortId);
})
->where('taken_at', '>', $someDate)
->toSql();

Linq2DB can't translate a mapped column in Where clause

I'm working with a legacy Oracle database that has a column on a table which stores boolean values as 'Y' or 'N' characters.
I have mapped/converted this column out like so:
MappingSchema.Default.SetConverter<char, bool>(ConvertToBoolean);
MappingSchema.Default.SetConverter<bool, char>(ConvertToChar);
ConvertToBoolean & ConvertToChar are simply functions that map between the types.
Here's the field:
private char hasDog;
[Column("HAS_DOG")]
public bool HasDog
{
get => ConvertToBoolean(hasDog);
set => hasDog = ConvertToChar(value);
}
This has worked well for simply retrieving data, however, it seems the translation of the following:
var humanQuery = (from human in database.Humans
join vetVisit in database.VetVisits on human.Identifier equals vetVisit.Identifier
select new HumanModel(
human.Identifier
human.Name,
human.HasDog,
vetVisit.Date,
vetVisit.Year,
vetVisit.PaymentDue
));
// humanQuery is filtered by year here
var query = from vetVisits in database.VetVisits
select new VetPaymentModel(
(humanQuery).First().Year,
(humanQuery).Where(q => q.HasDog).Sum(q => q.PaymentDue), -- These 2 lines aren't correctly translated to Y/N
(humanQuery).Where(q => !q.HasDog).Sum(q => q.PaymentDue)
);
As pointed out above, the .Where clause here doesn't translate the boolean comparison of HasDog being true/false to the relevant Y/N values, but instead a 0/1 and results in the error
ORA-01722: invalid number
Is there any way to handle this case? I'd like the generated SQL to check that HAS_DOG = 'Y' for instance with the specified Where clause :)
Notes
I'm not using EntityFramework here, the application module that this query exists in doesn't use EF/EFCore
You can define new mapping schema for your particular DataConnection:
var ms = new MappingSchema();
builder = ms.GetFluentMappingBuilder();
builder.Entity<Human>()
.Property(e => e.HasDog)
.HasConversion(v => v ? 'Y' : 'N', p => p == 'Y');
Create this schema ONCE and use when creating DataConnection

how to add new field & value from selected joined table

i have a joined table like this
i want to create new field named qty_exceed and the value is based from
(qty_stock - qty_taken)
do i have to do it in the query or make separate operation and store it in a varible?
my code
$getmaterial = ContractProduct::select(
'product_item.ref_rof_id',
'product_item.code',
'product_item.qty_stock',
'contract_product.qty_taken'
)
->where('ref_rof_id', $getrof->ref_rof_id)
->join('product_item', 'contract_product.ref_product_id', '=', 'product_item.code')
->get();
$data['getquoid'] = $getquoid;
$data['getmaterial'] = $getmaterial;
$view = $this->module_path . '.next-create';
return response()->view($view, $data);
You need to use DB::raw
But remember, raw statements will be injected into the query as strings, so you should be extremely careful to not create SQL injection vulnerabilities.
With DB::raw your code will look like this:
$getmaterial = ContractProduct::select(
'product_item.ref_rof_id',
'product_item.code',
'product_item.qty_stock',
'contract_product.qty_taken',
ContractProduct::raw('product_item.qty_stock - contract_product.qty_taken as qty_exceed')
)
->where('ref_rof_id', $getrof->ref_rof_id)
->join('product_item', 'contract_product.ref_product_id', '=', 'product_item.code')
->get();
$data['getquoid'] = $getquoid;
$data['getmaterial'] = $getmaterial;
$view = $this->module_path . '.next-create';
return response()->view($view, $data);

How count by the first letters in Laravel Query Builder?

I want to make a count by the first letters... I have this column
I would like to count each OE rows and each GICS rows
I'm working with this query
$data4 = DB::table('incidencias')
->select(DB::raw('grupo_asig as grupo_asig'), DB::raw('count(*) as number'))
->whereNotIn('grupo_asig', [''])
->groupBy('grupo_asig')
->orderBy('number', 'desc')
->get();
Use CASE WHEN and count the field like OE and ASIG
$data4 = DB::table('incidencias')
->select(DB::raw("(CASE WHEN grupo_asig LIKE 'OE%' THEN 'OE'
WHEN grupo_asig LIKE 'GICS%' THEN 'GICS'
END) AS grupo_asig_type"),
DB::raw('COUNT(*) as number'))
->whereNotIn('grupo_asig', [''])
->groupBy('grupo_asig_type')
->orderBy('number', 'desc')
->get();
You should try to use the [LIKE][1] function then and add it to your query:
->where('grupo_asig', 'like', 'OE%')
->where('grupo_asig', 'like', 'GICS%')
Edit:
I tried a lot around and came to this solution and made a SQL fiddle: http://sqlfiddle.com/#!9/06a39b/8
Does it help you?
You could use Collections. No real need to change your query much.
$data4 = DB::table('incidencias')
->select('grupo_asig')
->selectRaw('count(*) as number'))
->whereNotIn('grupo_asig', [''])
->groupBy('grupo_asig')
// ->orderBy('number', 'desc') Unless you use this array somewhere, it's not needed.
->get();
use Illuminate\Support\Str;
...
// php >= 7.4.0
$oe_count = $data4->filter(fn($data) => Str::startsWith($data->grupo, 'OE '))->count();
$gigs_count = $data4->filter(fn($data) => Str::startsWith($data->grupo, 'GIGS '))->count();
// php < 7.4.0
$oe_count = $data4->filter(function ($data) {
return Str::startsWith($data->grupo, 'OE ');
})->count();
$gigs_count = $data4->filter(function ($data) {
return Str::startsWith($data->grupo, 'GIGS ');
})->count();
Starting with Laravel 6, you can also use cursor() instead of get() in your query to return a LazyCollection. It's faster for this scenario.
I would suggest using a query for that:
refer to this answer
SELECT
LEFT(grupo_asig, 1) AS first_letter,
COUNT(*) AS total
FROM incidencias
GROUP BY first_letter

CakePHP 3 Between two date

I try to do an query in my controller, I want find all entity where the start and en date are between NOW(),
if $start and $end are between NOW() so show all entity, but i didn't succeed
My find is :
$day = date('l');
$now = Time::now();
$tvs = $this->Tvs
->find('all')
->contain(['Users'])
->where(['Tvs.day' => $day])
->andWhere(function($exp) {
return $exp->between($now, 'Tvs.start', 'tvs.end', $now);
});
How make this query ?
Thanks for you'r help !
Assuming you are trying to find all twhe record having $now between start and end the query is
sice the manual says that the first argument of the between fucntion should be name of the field (so it can't be a date) instead of between you can use two comparison
$tvs = $this->Tvs
->find('all')
->contain(['Users'])
->where(['Tvs.day' => $day])
->andWhere(function($exp) use($now) {
$exp->lte('Tvs.start', $now);
$exp->gte('Tvs.end', $now);
return $exp;
});
anyway you can make use of the mysql NOW() function directly into the query if the $now variable contains the same value of the NOW() function
->andWhere(function($exp, $q) {
return $exp->between($q->func()->now(), 'Tvs.start', 'Tvs.end');
});
there's anothe method: you can use BETWEEN and a palceholder to bind the value of $now
->andWhere([':now BETWEEN Tvs.start AND Tvs.end'])
->bind(':now', $now, 'datetime');
or using cakephp sql functions
->where(function($exp, $q) {
return $exp->between(':now', 'Tvs.start', 'Tvs.end');
})
->bind(':now', $now, 'datetime');
Since CakePHP 3.6 you can use identifiers:
return $query->where(function ($exp, $q) {
$today = $q->func()->now();
$start = $q->identifier('Tvs.start');
$end = $q->identifier('Tvs.end');
return $exp
->between($today,$start,$end);
});