optimize query for current address + documentation

This commit is contained in:
Julien Fastré 2021-09-15 12:24:58 +02:00
parent f63d4fcfba
commit 50b7554aea
4 changed files with 207 additions and 3 deletions

View File

@ -8,6 +8,29 @@ use Chill\PersonBundle\Entity\Person;
use Doctrine\ORM\Mapping as ORM; use Doctrine\ORM\Mapping as ORM;
/** /**
* This class links a person to the history of his addresses, through
* household membership.
*
* It is optimized on DB side, and compute the start date and end date
* of each address by the belonging of household.
*
* **note**: the start date and end date are the date of belonging to the address,
* not the belonging of the household.
*
* Example:
*
* * person A is member of household W from 2021-01-01 to 2021-12-01
* * person A is member of household V from 2021-12-01, still present after
* * household W lives in address Q from 2020-06-01 to 2021-06-01
* * household W lives in address R from 2021-06-01 to 2022-06-01
* * household V lives in address T from 2021-12-01 to still living there after
*
* The person A will have those 3 entities:
*
* 1. 1st entity: from 2021-01-01 to 2021-06-01, household W, address Q;
* 2. 2st entity: from 2021-06-01 to 2021-12-01, household W, address R;
* 3. 3st entity: from 2021-12-01 to NULL, household V, address T;
*
* @ORM\Entity(readOnly=true) * @ORM\Entity(readOnly=true)
* @ORM\Table(name="view_chill_person_household_address") * @ORM\Table(name="view_chill_person_household_address")
*/ */
@ -45,11 +68,23 @@ class PersonHouseholdAddress
*/ */
private $address; private $address;
/**
* The start date of the intersection address/household
*
* (this is not the startdate of the household, not
* the startdate of the address)
*/
public function getValidFrom(): ?\DateTimeInterface public function getValidFrom(): ?\DateTimeInterface
{ {
return $this->validFrom; return $this->validFrom;
} }
/**
* The end date of the intersection address/household
*
* (this is not the enddate of the household, not
* the enddate of the address)
*/
public function getValidTo(): ?\DateTimeImmutable public function getValidTo(): ?\DateTimeImmutable
{ {
return $this->validTo; return $this->validTo;

View File

@ -35,6 +35,7 @@ use Chill\PersonBundle\Entity\Household\HouseholdMember;
use Chill\MainBundle\Entity\HasCenterInterface; use Chill\MainBundle\Entity\HasCenterInterface;
use Chill\MainBundle\Entity\Address; use Chill\MainBundle\Entity\Address;
use Chill\MainBundle\Entity\Embeddable\CommentEmbeddable; use Chill\MainBundle\Entity\Embeddable\CommentEmbeddable;
use Chill\PersonBundle\Entity\Person\PersonCurrentAddress;
use DateTime; use DateTime;
use Doctrine\ORM\Mapping as ORM; use Doctrine\ORM\Mapping as ORM;
use Doctrine\Common\Collections\Collection; use Doctrine\Common\Collections\Collection;
@ -412,6 +413,15 @@ class Person implements HasCenterInterface, TrackCreationInterface, TrackUpdateI
*/ */
private $addresses; private $addresses;
/**
* The current person address.
*
* This is computed through database and is optimized on database side.
*
* @var PersonCurrentAddress|null
*/
private ?PersonCurrentAddress $currentPersonAddress = null;
/** /**
* fullname canonical. Read-only field, which is calculated by * fullname canonical. Read-only field, which is calculated by
* the database. * the database.
@ -564,6 +574,8 @@ class Person implements HasCenterInterface, TrackCreationInterface, TrackUpdateI
return $participation->getAccompanyingPeriod(); return $participation->getAccompanyingPeriod();
} }
} }
return null;
} }
/** /**
@ -1242,13 +1254,31 @@ class Person implements HasCenterInterface, TrackCreationInterface, TrackUpdateI
return $this->addresses; return $this->addresses;
} }
/**
* @deprecated Use `getCurrentPersonAddress` instead
* @param DateTime|null $from
* @return false|mixed|null
* @throws \Exception
*/
public function getLastAddress(DateTime $from = null) public function getLastAddress(DateTime $from = null)
{ {
$from ??= new DateTime('now'); return $this->getCurrentPersonAddress($from);
}
/**
* get the address associated with the person at the given date
*
* @param DateTime|null $at
* @return Address|null
* @throws \Exception
*/
public function getCurrentPersonAddress(?\DateTime $at = null): ?Address
{
$at ??= new DateTime('now');
/** @var ArrayIterator $addressesIterator */ /** @var ArrayIterator $addressesIterator */
$addressesIterator = $this->getAddresses() $addressesIterator = $this->getAddresses()
->filter(static fn (Address $address): bool => $address->getValidFrom() <= $from) ->filter(static fn (Address $address): bool => $address->getValidFrom() <= $at)
->getIterator(); ->getIterator();
$addressesIterator->uasort( $addressesIterator->uasort(
@ -1496,7 +1526,16 @@ class Person implements HasCenterInterface, TrackCreationInterface, TrackUpdateI
public function getCurrentHouseholdAddress(?\DateTimeImmutable $at = null): ?Address public function getCurrentHouseholdAddress(?\DateTimeImmutable $at = null): ?Address
{ {
$at = $at === null ? new \DateTimeImmutable('today') : $at; if (
NULL === $at
||
$at->format('Ymd') === (new \DateTime('today'))->format('Ymd')
) {
return $this->currentPersonAddress instanceof PersonCurrentAddress
? $this->currentPersonAddress->getAddress() : NULL;
}
// if not now, compute the date from history
$criteria = new Criteria(); $criteria = new Criteria();
$expr = Criteria::expr(); $expr = Criteria::expr();

View File

@ -0,0 +1,81 @@
<?php
namespace Chill\PersonBundle\Entity\Person;
use Chill\MainBundle\Entity\Address;
use Chill\PersonBundle\Entity\Person;
use Doctrine\ORM\Mapping as ORM;
/**
* Entity which associate person with his current address, through
* household participation.
*
* The computation is optimized on database side.
*
* The validFrom and validTo properties are the intersection of
* household membership and address validity. See @link{PersonHouseholdAddress}
*
* @ORM\Entity(readOnly=true)
* @ORM\Table("view_chill_person_current_address")
*/
class PersonCurrentAddress
{
/**
* @ORM\Id
* @ORM\OneToOne(targetEntity=Person::class)
*/
protected Person $person;
/**
* @ORM\OneToOne(targetEntity=Address::class)
*/
protected Address $address;
/**
* @ORM\Column(name="valid_from", type="date_immutable")
*/
protected \DateTimeImmutable $validFrom;
/**
* @ORM\Column(name="valid_to", type="date_immutable")
*/
protected ?\DateTimeImmutable $validTo;
/**
* @return Person
*/
public function getPerson(): Person
{
return $this->person;
}
/**
* @return Address
*/
public function getAddress(): Address
{
return $this->address;
}
/**
* This date is the intersection of household membership
* and address validity
*
* @return \DateTimeImmutable
*/
public function getValidFrom(): \DateTimeImmutable
{
return $this->validFrom;
}
/**
* This date is the intersection of household membership
* and address validity
*
* @return \DateTimeImmutable|null
*/
public function getValidTo(): ?\DateTimeImmutable
{
return $this->validTo;
}
}

View File

@ -0,0 +1,49 @@
<?php
declare(strict_types=1);
namespace Chill\Migrations\Person;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;
/**
* Create view for PersonCurrentAddress and related indexes
*/
final class Version20210915093624 extends AbstractMigration
{
public function getDescription(): string
{
return 'Create view for PersonCurrentAddress and related indexes';
}
public function up(Schema $schema): void
{
$this->addSql("CREATE VIEW view_chill_person_current_address AS
SELECT
cphm.person_id AS person_id,
cma.id AS address_id,
CASE WHEN cphm.startdate > COALESCE(cma.validfrom, '-infinity'::date) THEN cphm.startdate ELSE cma.validfrom END AS valid_from,
CASE WHEN COALESCE(cphm.enddate, 'infinity'::date) < COALESCE(cma.validto, 'infinity'::date) THEN cphm.enddate ELSE cma.validto END AS valid_to
FROM chill_person_household_members AS cphm
LEFT JOIN chill_person_household_to_addresses AS cphta ON cphta.household_id = cphm.household_id
LEFT JOIN chill_main_address AS cma ON cphta.address_id = cma.id
WHERE
cphm.sharedhousehold IS TRUE
AND
current_date between cphm.startdate AND coalesce(enddate, 'infinity'::date)
AND
current_date between cma.validfrom AND coalesce(validto, 'infinity'::date)
");
$this->addSql("CREATE INDEX chill_custom_main_address_filtering_idx ON chill_main_address USING btree (id, validfrom ASC, validto DESC)");
$this->addSql("CREATE INDEX chill_custom_person_household_members_sharing_idx ON chill_person_household_members USING btree (person_id, startdate ASC, enddate DESC, household_id) WHERE sharedhousehold IS TRUE");
}
public function down(Schema $schema): void
{
$this->addSql("DROP VIEW view_chill_person_current_address");
$this->addSql("DROP INDEX chill_custom_main_address_filtering_idx");
$this->addSql("DROP INDEX chill_custom_person_household_members_sharing_idx");
}
}