Contain complex query date_format and between - cakephp

Code Update 2017/07/20
I followed the recommendations of #Arcesilas (here) with the use of date_format of MySQL.
I upgraded my data structure by merging start_month and start_day (ex 2017-01-16 = 0116)
[...]
->contain([
'Bookings',
'Amenities',
'Periodicities' => function ($q) use ($check_in, $check_out) {
$start = $q->func()->date_format([
'Periodicities.start' => $check_in->format('Ymd'),
"'%m%d'" => 'literal'
]);
$end = $q->func()->date_format([
'Periodicities.end' => $check_out->format('Ymd'),
"'%m%d'" => 'literal'
]);
return $q
->where(function ($exp) use ($start) {
$exp->between($start, 'Periodicities.start', 'Periodicities.end');
return $exp;
})
->orWhere(function ($exp) use ($end) {
$exp->between($end, 'Periodicities.start', 'Periodicities.end');
return $exp;
});
}
])
[...]
Cakephp SQL Log
SELECT
PeriodicitiesRooms.id AS `Periodicities_CJoin__id`,
PeriodicitiesRooms.periodicity_id AS `Periodicities_CJoin__periodicity_id`,
PeriodicitiesRooms.room_id AS `Periodicities_CJoin__room_id`,
PeriodicitiesRooms.price AS `Periodicities_CJoin__price`,
Periodicities.id AS `Periodicities__id`,
Periodicities.name AS `Periodicities__name`,
Periodicities.start AS `Periodicities__start`,
Periodicities.end AS `Periodicities__end`,
Periodicities.price AS `Periodicities__price`,
Periodicities.modified AS `Periodicities__modified`
FROM
periodicities Periodicities
INNER JOIN periodicities_rooms PeriodicitiesRooms ON Periodicities.id = (
PeriodicitiesRooms.periodicity_id
)
WHERE
(
date_format('20170726', '%m%d') BETWEEN 'Periodicities.start'
AND 'Periodicities.end'
OR (
PeriodicitiesRooms.room_id in ('1', '2', '3')
AND date_format('20170720', '%m%d') BETWEEN 'Periodicities.start'
AND 'Periodicities.end'
)
)
This does not give me any more result because ORM of CakePHP adds apostrophes (') to the end of BETWEEN' Periodicities.start 'AND' Periodicities.end'
If in my database I paste the CakePHP query without the apostrophes (') it works. Do you have any idea what changes to make to the end of my query so that it looks like this: BETWEEN 'Periodicities.start' AND 'Periodicities.end' to BETWEEN Periodicities.start AND Periodicities.end ?
'SELECT * FROM periodicities WHERE date_format(' . $check_in->format('Ymd') . ', '%m%d') BETWEEN start AND end'

EDIT
From your comment I see you don't want to store years because the year is not relevant.
In that case, you can still store all your dates as a specific year and then hard code that year into the query. I have modified the query to use year 2000.
ORIGINAL
The conversation is unclear to me but I will attempt to answer the question I believe you are asking.
It appears you want to know how to find Periodicities between the checkin time or Periodicities between the checkout time.
It also appears you want to know the best way to store the dates.
Just store the dates as dates. No need to make it complicated
Between is also overly complicated.
This will do what you need it to do without being confusing.
[...]
->contain([
'Bookings',
'Amenities',
'Periodicities' => function ($q) use ($check_in, $check_out) {
$q->where([
'Periodicities.start >' => $check_in->format('2000-m-d'),
'Periodicities.end <' => $check_in->format('2000-m-d')
]);
$q->orWhere([
'Periodicities.start >' => $check_out->format('2000-m-d'),
'Periodicities.end <' => $check_out->format('2000-m-d')
]);
return $q;
}
])
[...]

I solved my problem with this query :
->contain([
'Bookings',
'Amenities',
'Periodicities' => function ($q) use ($check_in, $check_out) {
$q->where(function ($exp) use ($check_in, $check_out) {
$exp->gte('Periodicities.start', $check_in->format('md'));
$exp->lte('Periodicities.end', $check_out->format('md'));
});
return $q;
}
])
I keep storing in database dates with the month and day md format
Ex: 0901 < 1030
01 September < 30 October

Related

Cakephp 3 - 'matching' - how to filter the data for the booking system?

I have 'accommod_units' and 'accommod_bookings' table and I want to check the availability of accommodation units for a certain period. For example, Date of arrival: 2019-08-20 and Departure date: 2019-08-27.
'accommodation_units' table is shown below:
'accommod_bookings' table is shown below:
This code does not work:
$this->loadModel('AccommodUnits');
$this->conditionBookings = [
'NOT' => [
[
'AccommodBookings.date_start >=' => '2019-08-20',
'AccommodBookings.date_end <=' => '2019-08-27'
],
]
];
$accommodUnits = $this->AccommodUnits
->find('all')
->distinct(['AccommodUnits.id'])
->contain([
'AccommodBookings' => function ($q) {
return $q->where($this->conditionBookings);
},
])
->where($conditionUnits)
->matching('AccommodBookings', function ($q) {
return $q->where($this->conditionBookings);
});
Their associations are:
$accommodUnits->hasMany('AccommodBookings');
How to solve this problem?
Simple you must overlaps booking periods
[
'AccommodBookings.date_start <=' => '2019-08-27', // Here put end date greater than start date <=
'AccommodBookings.date_end >=' => '2019-08-20' // Here put start date smaller than end date >=
],
Now:
$this->loadModel('AccommodUnits');
$accommodBookings = [
'AccommodBookings.date_start <=' => '2019-08-27', // not match start date lower than end date
'AccommodBookings.date_end >=' => '2019-08-20' // not mach end date greater than start date
];
$accommodUnits = $this->AccommodUnits
->find() // if empty default is 'all'
->where(['AccommodUnits.id' => 124]) // find units 124 where not matching:
->notMatching('AccommodBookings', function ($q) use ($accommodBookings) {
return $q->where($accommodBookings);
});
RESULT is null if unit 124 has overlaps booking date period.
Using notMatching()
The opposite of matching() is notMatching(). This function will change
the query so that it filters results that have no relation to the
specified association:
// In a controller or table method.
$query = $articlesTable
->find()
->notMatching('Tags', function ($q) {
return $q->where(['Tags.name' => 'boring']);
});
The above example will find all articles that were not tagged with the
word boring.
Poz

CakePHP OR condition not properly working

I have the following problem: I have a table for customers, with a prename and a name. Now I have variable keywords (in this example hardcoded 2) in an array and I want to check whether one of the keywords match with either the name OR the prename. So I figured to use the OR condition in the following codesnippet. Unfortunately it doesnt provide the wanted output:
$searchValueArr = ["keyword1","keyword2"];
$customers = $customersTable->find()
->where(function (QueryExpression $exp) {
$orConditions = $exp->or_(function ($or) {
foreach($searchValueArr as $searchValue) {
$or = $or
->eq('prename LIKE', "%".$searchValue."%")
->eq('name LIKE', "%".$searchValue."%");
}
return $or;
});
return $orConditions;
})
->all();
You need to list $searchValueArr in the use part of each anonymous function, otherwise it's not in context, ex:
$searchValueArr = ["keyword1","keyword2"];
$customers = $customersTable->find()
->where(function (QueryExpression $exp) use ($searchValueArr){
$orConditions = $exp->or_(function ($or) use ($searchValueArr){
foreach($searchValueArr as $searchValue) {
$or = $or
->eq('prename LIKE', "%".$searchValue."%")
->eq('name LIKE', "%".$searchValue."%");
}
return $or;
});
return $orConditions;
})
->all();
Also this is personal preference really, but you technically can still use array formatting for a query like this (a nested set of OR's), for example:
$searchValueArr = ["keyword1","keyword2"];
$searchTerms = [];
foreach($searchValueArr as $searchValue) {
$searchTerms[] = [
'OR' => [
'prename LIKE' => "%".$searchValue."%",
'name LIKE' => "%".$searchValue."%"
]
];
}
$customers = $customersTable->find()
->where([
'OR' => $searchTerms
])
->all();

time array converson cakephp3

I have a simple question as I just want to convert the time (in array format) to a 24hr time string. The issue I get the time from user input and it places it in an array object which the formatting isnt working .
I couldnt find the answer from https://book.cakephp.org/3.0/en/core-libraries/time.html#conversion
time format inputted from a form
'start_time' => [
'hour' => '02',
'minute' => '00',
'meridian' => 'pm'
],
view//
echo $this->Form->input('start_time', ['label' => 'Class Start Time','type' => 'time',
'interval' => 5,'timeFormat'=>12,'value'=>$startTime,]);
//controller
if ($this->request->is('post')) {
debug($this->request->data['start_time']->i18nFormat('HH:mm:ss'));//cant use on an array
//this works but is there a better way
$startTime = $this->request->data['start_time']['hour'].":".$this->request->data['start_time']['minute']." ".
$this->request->data['start_time']['meridian'];
$this->request->data['start_time'] = date("H:i:s", strtotime( $startTime));
debug($this->request->data);
You should create a Time instance, because $this->request->data['start_time'] is not formated:
use Cake\I18n\Time;
...
$StartTimeHour = $this->request->data['start_time']['hour'];
$StartTimeMinute = $this->request->data['start_time']['minute'];
$StartTimeMeridian = $this->request->data['start_time']['meridian'];
$time = new Time("${StartTimeHour}:${StartTimeMinute} ${StartTimeMeridian}");
echo $time->i18nFormat('HH:mm:ss');
*Tested and working.

Converting SQL Server query containing GROUP BY into NHibernate LINQ

I'm basically trying to retrieve a paged list of unique GUIDs, sorted by (row) creation date.
I've been able to draft a SQL Server query that seems to work for me based on this answer, but now I have to translate that into LINQ.
SELECT TOP 15 payment.ClientRef,
MAX(payment.CreatedDateUtc)
FROM PaymentTransactionState payment
INNER JOIN OrderState orderstate ON payment.ClientRef = orderstate.ClientRef
WHERE orderstate.UserId = 2 AND
payment.PaymentState IN (
'Rejected',
'Authorized')
GROUP BY payment.ClientRef
ORDER BY MAX(payment.CreatedDateUtc) DESC,
payment.ClientRef
Problem is, I can't apply GroupBy on an IQueryOver, I'm probably missing the appropiate syntax:
session
.QueryOver<Payment>()
.JoinAlias(orderState => orderState.OrderStateEntity, () => orderStateRow)
.Where(() => orderStateRow.UserId == customer.UserId)
.WhereRestrictionOn(payment => payment.PaymentState).IsIn(paymentStates)
.GroupBy(pts => pts.ClientRef)
.OrderBy(payment => payment.CreatedDateUtc).Desc
.Skip(pageIndex*pageSize)
.Take(pageSize)
.List();
I could probably do the group by in query syntax, but I'm not so sure about the Skip & Take bit.
I would try like this:
var query = db.PaymentTransactionState
.Where( pts => pts.OrderState.UserId == 2 &&
new string[] {"Rejected", "Authorized"}.Contains(pts.PaymentState) )
.GroupBy( pts => pts.ClientRef )
.OrderByDescending( pts => pts.Max( p => p.CreatedDateUtc))
.ThenBy( p => p.Key )
.Take(15);
So here's what worked for me: basically I had to use SelectList instead of GroupBy; SelectGroup, SelectMax & TransformUsing were easy to tackle once I found that;
PaymentRow paymentAlias = null;
OrderStateRow orderStateRow = null;
var transactionStateRows = session
.QueryOver<PaymentRow >()
.JoinAlias(orderState => orderState.OrderStateEntity, () => orderStateRow)
.Where(() => orderStateRow.UserId == customer.UserId)
.WhereRestrictionOn(payment => payment.PaymentState).IsIn(paymentStates)
.SelectList(list => list
.SelectGroup(payment => payment.ClientRef).WithAlias(() => paymentAlias.ClientRef)
.SelectMax(payment => payment.CreatedDateUtc).WithAlias(() => paymentAlias.CreatedDateUtc))
.TransformUsing(Transformers.AliasToBean<PaymentRow >())
.OrderBy(payment => payment.CreatedDateUtc).Desc
.Skip(pageIndex*pageSize)
.Take(pageSize)
.List();
I'll leave this here in case someone might find my travails useful in the future. Thank you for your replies.

Yii2 ignore first database result

is there any way to ignore the first result record in Yii2 at a query? I have a list of numbers that represents a client. For designing purposes i had to query the first record separatly but now i have it duplicated. My questin is how can I query in Yii2 to ignore the first result?
Regards,
Gábor
The second find is the query where i need to ignore the first result:
public function actionGeneratePage() {
public function actionGeneratePage() {
$behivott = Sorszam::find()->with('ablak')
->orderBy(['behivas_datum' => SORT_DESC])
->limit(1)
->all();
$sorszamok = Sorszam::find()->with('ablak')
->orderBy(['behivas_datum' => SORT_DESC])
->limit(4)
->all();
$reklam = Reklam::find()->all();
return $this->render('generatePage', [
'sorszamok' => $sorszamok,
'reklam' => $reklam,
'behivott' => $behivott,
]);
}
You use offset() to skip the first record:
$sorszamok = Sorszam::find()->with('ablak')
->orderBy(['behivas_datum' => SORT_DESC])
->limit(4)
->offset(1)
->all();
Also you can use a single query to get both $behivott and $sorszamok with array_shift:
$sorszamok = Sorszam::find()->with('ablak')
->orderBy(['behivas_datum' => SORT_DESC])
->limit(5)
->all();
$behivott = array_shift($sorszamok);

Resources