Doctrine 2 – filtrace podle prvků v kolekci

kejlicz
Člen | 201
+
0
-

Ahoj všem.

Celé odpoledne se trápím, jak dát dohromady dotaz.

Mám v aplikaci entity Ubytovací zařízení (Object), Piktogram (Pictogram) a spojovací tabulku Object2Pictogram.

V té spojovací tabulce to mám řešené takto

	/**
	 * @ORM\ManyToOne(targetEntity="Object", inversedBy="pictograms")
	 **/
	private $object;

	/**
	 * @ORM\ManyToOne(targetEntity="Pictogram", inversedBy="objects")
	 **/
	private $pictogram;

Mám v databázi asi 12 piktogamů (TV, sprcha, krb …). Ke každému objektu může být přiřazeno různé množství piktogramů.
To vše funguje.

Nedaří se mi ale pomocí query builderu vyfiltrovat objekty které mají přiřazené určité piktogramy. Tedy např. výpis objektů, které mají současně např. wifi, krb, TV. Nevím, jak to řešit, když je tam ta spojovací tabulka.

Díky moc za nasměrování.

Martin

Mysteria
Člen | 797
+
+1
-

Ahoj, klasicky to najoinuj. :)

$this->entityManager
			->createQueryBuilder()
			->select(['object'])
    ->from(Object::class, 'object')
    ->leftJoin('object.pictograms', 'objectPictograms')
    ->leftJoin('objectPictograms.pictogram', 'pictogram')
    ->where('pictogram.name = :name')
    ->setParameter('name', 'Pictogram');
kejlicz
Člen | 201
+
0
-

Mysteria napsal(a):

Ahoj, klasicky to najoinuj. :)

$this->entityManager
			->createQueryBuilder()
			->select(['object'])
    ->from(Object::class, 'object')
    ->leftJoin('object.pictograms', 'objectPictograms')
    ->leftJoin('objectPictograms.pictogram', 'pictogram')
    ->where('pictogram.name = :name')
    ->setParameter('name', 'Pictogram');

Díky za reakci. Tímhle směrem jsem šel taky a to co jsi napsal mi , podle mého názoru, vytáhne objekty, které mají jeden určitý Pictogram. Nedaří se mi ale zprovoznit to, abych dostal všechny objekty, které mají více určitých piktogramů současně. Tedy když uživatel zaškrtne, že chce objekty co mají třeba wifi a televizi.

Mohl by jsi mi prosím třeba u toho případu napsat, jak by jsi získal objekty, které mají v kolekci „pictograms“

  • pictogram.name= ‚wifi‘

a současně

  • pictogram.name= ‚tv‘

Díky moc.

Editoval kejlicz (3. 1. 2019 23:25)

Mysteria
Člen | 797
+
+1
-

Jasně, v tom případě stačí upravit poslední dva řádky na

->where('pictogram.name IN (:names)')
->setParameter('names', ['Pictogram One', 'Pictogram Two']);

EDIT: Tak ne, blbě jsem si to přečetl, ty chceš AND, takže bych asi jednoduše dal cyklus:

foreach ($pictograms as $key => $pictogram) {
	$queryBuilder
		->andWhere(sprintf('pictogram.name = :%s', $key))
		->setParameter($key, $pictogram);
}

EDIT2: Jsem rád, že nejsem jedinej, kdo neumí číst a dal by tam IN :D

Editoval Mysteria (3. 1. 2019 23:50)

Ondřej Kubíček
Člen | 494
+
0
-

a zkoušel jsi to vůbec?
dyt ti to Mysteria napsal dobře, jen ti tam teda napíšu wifi, abys to pochopil líp

...
->where('pictogram.name = :name')
    ->setParameter('name', 'wifi')
...

a jelikož to budeš chtít podle více pictogramů, tak použíješ nejspíš IN že

$pictograms = ['wifi', 'tv'];
...
->where('pictogram.name IN (:pictograms)')
             ->setParameter('pictograms', $pictograms)
kejlicz
Člen | 201
+
0
-

Ondřej Kubíček napsal(a):

a zkoušel jsi to vůbec?
dyt ti to Mysteria napsal dobře, jen ti tam teda napíšu wifi, abys to pochopil líp

...
->where('pictogram.name = :name')
    ->setParameter('name', 'wifi')
...

a jelikož to budeš chtít podle více pictogramů, tak použíješ nejspíš IN že

$pictograms = ['wifi', 'tv'];
...
->where('pictogram.name IN (:pictograms)')
             ->setParameter('pictograms', $pictograms)

Omlouvám se, že mi to hlava nebere.

Jasně, že jsem to zkoušel. Vždyť píšu, že to je pro filtraci podle jednoho piktogramu a potřebuji podle více.

Teď jsem to udělal s tím IN () a opět z toho nejsem moc moudrý.
Když hledám třeba objekty, které mají piktogramy „sauna“ a „wifi“, vrátí mi to objekty, které mají „sauna“ + objekty, které mají „wifi“. Jak jsem psal, potřebuji to ale aby to vrátilo objekty, které mají současně saunu i wifi. Ne jako objekty co mají saunu + objekty co mají wifi.

Zde je moje osekaná metoda na získávání počtu výsledků a samotných výsledků. Když je použit $params[‚count‘], tak to vrací počet. Když není použit, tak to vrací výsledky. O ostatní věci jsem to osekal, kvůli přehlednosti.

public function get( $params, $limit = null, $offset = null ) {
		$qb = $this->em->createQueryBuilder();

		if ( isset( $params['count'] ) ) {
			$qb->select( 'count(object.id)' );
		} else {
			$qb->select( [ 'object' ] );
		}

		$qb->from( \App\Entities\Object::class, 'object' )
		   ->where( 'object.active = :active' )
		   ->setParameter( 'active', true );

		$qb->leftJoin( 'object.pictograms', 'ObjectPictograms' );
		$qb->leftJoin( 'ObjectPictograms.pictogram', 'pictogram' );
		$qb->andWhere( 'pictogram.slug IN (:slugs)' )
		   ->setParameter( 'slugs', [ 'tv', 'wifi' ] );

		if ( isset( $params['count'] ) ) {
			return $qb->getQuery()->getSingleScalarResult();
		} else {

			$qb->orderBy( 'object.id', 'DESC' );

			return $qb->getQuery()->getResult();
		}
	}

Já už to dneska musím vypnout, protože mi mozek asi jede v nouzovým režimu. Snad to bude zítra lepší :-)

Díky moc.

Mysteria
Člen | 797
+
+2
-

@kejlicz IN ti nebude fungovat, protože IN je zkratka pro OR a ty potřebuješ AND. S @OndřejKubíček jsme ti oba poradili blbě, protože jsme přehlédli, že chceš současně. :( Mrkni na můj zeditovanej příspěvek, tam je už správný řešení toho, co potřebuješ (druhej blok kódu).

On je totiž podle mě i trošku neštastně položenej dotaz, protože já osobně jsem z něj myslel, že nevíš jak najoinovat ty tabulky a podmínka už není problém, ale ono to očividně bylo přesně opačně. Nevadí, stane se. :)

kejlicz
Člen | 201
+
0
-

@Mysteria Díky za trpělivost :-) Omlouvám se, pokud jsem to nepopsal přesně. I když se o to vždy snažím, často mě v půlce psaní příspěvku napadne řešení, tak to jdu zkusit. Když to nejde, pokračuji v psaní a třeba mě zase něco napadne. Tímhle stylem mám příspěvek rozepsaný třeba hodinu a tom pak se v tom už můžu ztrácet.

Tohle řešení co jsi mi teď psal, jsem zkoušel už ze začátku, ale dostával jsem chybu a asi jsem to toto řešení unáhleně opustil a hledat jinou cestu.

Tedy k té chybě. Mám to teď takhle:

		$pictograms = [ "wifi", "tv"];

		foreach ($pictograms as $key => $pictogram) {

			$qb
				->andWhere(sprintf('pictogram.slug = :%s', $key))
				->setParameter($key, $pictogram);
		}

a když pak zavolám $qb->getQuery()->getSingleScalarResult() nebo $qb->getQuery()->getResult(), tak to vyhodí chybu

Invalid parameter format, : given, but :<name> or ?<num> expected.

Pokud se nepletu, tak to vyřeším úpravou, že dvojtečku nahradím otazníkem, nebo před ten číselný $key, přidám nějaký znak, aby to bylo jako název

pictogram.slug = ?%s', $key

nebo

pictogram.slug = :%s', "cokoliv" . $key

V tomhle stavu se mi to spustí, ale zase

  • když dám do pole $pictograms pouze „tv“, vrátí to 28 objektů, což je správně
  • když dám do pole $pictograms pouze „wifi“, vrátí to 10 objektů, což je správně
  • když dám do pole $pictograms [„wifi“, „tv“], vrátí to 0 objektů, i když jich tam je asi 6 co mají wifi i tv

generuje to tento dotaz (je tam navíc jen omezení regionu, abych nepracoval s celou db

SELECT o0_.id AS id_0, o0_.active AS active_1, o0_.city AS city_2, o0_.title AS title_3, o0_.gps_n
AS gps_n_4, o0_.gps_e AS gps_e_5, o0_.slug AS slug_6, o0_.bedroom AS bedroom_7, o0_.description AS
description_8, o0_.new_description AS new_description_9, o0_.catalog_number AS catalog_number_10,
o0_.season AS season_11, o0_.bed AS bed_12, o0_.extra_bed AS extra_bed_13, o0_.old_code AS
old_code_14, o0_.old_winter_code AS old_winter_code_15, o0_.heating AS heating_16, o0_.note AS
note_17, o0_.our_note AS our_note_18, o0_.keywords AS keywords_19, o0_.object_number AS
object_number_20, o0_.object_code AS object_code_21, o0_.price_text AS price_text_22, o0_.region_id
AS region_id_23
FROM object o0_
LEFT JOIN object2pictogram o1_ ON o0_.id = o1_.object_id
LEFT JOIN pictogram p2_ ON o1_.pictogram_id = p2_.id
WHERE o0_.active = 1 AND o0_.region_id = 2 AND p2_.slug = 'wifi' AND p2_.slug = 'tv'
ORDER BY o0_.id DESC

Editoval kejlicz (4. 1. 2019 11:44)

Mysteria
Člen | 797
+
+2
-

OK, tak jednoduchý to zase nebude. Podle tohohle máš na výběr několik možností, „stačí“ to jenom přepsat do QueryBuilderu.
Zkusil bych třeba tohle, ale budeš muset vyzkoušet jestli to stále vrací pole objektů, aby to nebylo jenom pole polí kvůli tomu grupování.

$pictograms = ['Pictogram One', 'Pictogram Two'];
$queryBuilder
    ->select(['object'])
    ->from(Object::class, 'object')
    ->leftJoin('object.pictograms', 'objectPictograms')
    ->leftJoin('objectPictograms.pictogram', 'pictogram')
    ->where('pictogram.name IN (:names)')
    ->groupBy('object.id')
    ->having('COUNT(object.id) = :count')
    ->setParameter('names', $pictograms)
    ->setParameter('count', count($pictograms));
kejlicz
Člen | 201
+
0
-

Díky. Během víkendu na to kouknu a snad se s tím nějak poperu.

Edit : Tak mi to nedalo a sedl jsem k tomu hned a Tvoje řešení funguje perfektně.

Díky MOC @Mysteria . Jsem Tvým dlužníkem :-)

Editoval kejlicz (4. 1. 2019 16:48)