MySQL upit - jedna kolona vise vrednosti

Mozda sam ja u startu lose postavio, upravo sa tabelama, brojem tabela.
Generalno, mislim, da ne bi doslo do gomilanja tabela, da za svaki filter postoji posebna tabela, vec sve u jednu, a eventualno vise tabela (za razlicite grupe, motherobards, chipses, rams i ostalo) za relaciju izmedju proizvoda i postojecih filtera.

Vidi molim te, verujem da mozes nesto da uradis.

Jesu jednaki u tabeli pro_filters (sadrzi samo id_filt, id_pro), ali u tabeili “filter” su podeljeni filteri parent - chldren (motherboard -> asus, inte itd).
Nasao bih nacin da odredim, eventualno da se “pro_filters” doda jos jedna kolona u kojoj bi bio ID grupe (motherboards, rams, chipsets)
Ili pak, da za selektovane filtere, napravim upit u kodu koji bi u tom trenutku vratio grupe pa odredjenoj grupi dodelim stavke filtera koje njemu pripadaju i onda odatle formiram nesto za upit.

Isto kao što možeš reći i IN(5, 7), jesi li ikad probao izvesti takav query, ja jesam i radi ali ne u ovoj situaciji, kao što rekoh nisam za računalom, a i @drmko bi trebao kaka dump staviti da se ne moram zajebavati sa kreiranjem tablica i vrijednosti

Pratim te do odgovora od avetma. On ti je riješio problem iz uvodnog pitanja, nakon toga se čini da imaš dodatne zahtjeve koje nisi opisao u uvodu, a koji su (barem meni) malo nejasno naknadno ubačeni u priču.

Ako sam dobro razumio, ti želiš selektirati proizvod koji ima na sebi zakačene filtere npr. 5 i 7.
Zatim bi osim toga htio selektirati i proizvod koji ima na sebi zakačen filter npr 8,2 i 7. …itd.

Ako je tako, koristi se odgovorom od avetma …ali sa tim selektom idi po proizvode koji matchaju samo jedan filter. A da bi dobio sve proizvode, napravi iteraciju kroz sve filtere…i samo nadopunjuj listu selektiranih proizvoda. Uz to, možeš kod svakog sljedećeg kruga selektiranja malo proširiti SQL upit, čisto da ne dohvaćaš proizvode koje već ranije imaš selektirane.

Code bi bio sljedeći:

function selectProducts($filters){

	$selectedProducts = [];
	foreach ($filters as $filter) {
		

		$query = "SELECT * FROM proizvodi p
					INNER JOIN pro_filters pf ON p.id = pf.id_pro
					WHERE p.akt=1 AND pf.id_filt IN @filter AND p.id NOT IN @selectedProductsID
					GROUP BY p.id
					HAVING COUNT(*) = @count";

		$selectedProductsPerLoop = db::select($query, ['bind'=>['count'=>count($filter), 'filter'=>$filter, 'selectedProductsID'=> array_column($selectedProducts, "id")]]);

		$selectedProducts = array_merge($selectedProducts, $selectedProductsPerLoop);
		
	}

	return $selectedProducts;
}


$filters = [[5,7], [8,2,7]];
$products = selectProducts($filters);

…s time da (očito) moraš ovaj dio gdje ja imam klasu db::select() prilagodit prema svom načinu čitanja iz baze. Isto tako sam koristio placholdere varijabli u SQL-u (@), koji su obrađeni od te db klase…pa se možda kod tebe placeholderi ne ponašaju jednako.

Naravno, ako sam uopće dobro pretpostavio što trebaš. :slight_smile:

Što se u stvari traži? Konkretan primjer.
Jesu li tablice zadane ili se i to može mijenjati?

Jesi li siguran da je ovo dobar business request?
Nisam ubijedjen da ima puno [recimo] matičnih ploča kojima je proizvodjač i jedan i drugi manufacturer a tačnije bi bilo reći da nema ni jedna takva.

Pratim cc-a, dump baze bi bio potreban za potpuno razumijevanje.

Edit: Sad vidim da nije očuvan integritet relacija i da bi ovo radilo jer nije nf al’ ostajem pri tome da će dump pomoći.

@bozoru, hvla na ovoj opciji, ali mislim da mi ovakav nacin trenutno ne odgovara, da se za svaku stavku filtera pravi upit, ali na kraju krajeva, mozda ga i iskoristim, ako ne nadjem neko drugo resenje, u medjuvremenu.

TReba da se izvede ovako nesto, kao na linku winwin shopa:
https://www.winwin.rs/racunari-i-komponente/racunarske-komponente/maticne-ploce.html?manufacturer=74623%2C74798
Znaci kad izaberem dva tipa mat. ploca, s obzirom da se radi o stavkama iz iste grupe “Maticne ploce”, izlistava mi maticne ploce za obe selektovane maticne ploce, a kad izaberem jednu stavku iz grupe “Cipset” onda treba da prikaze maticne ploce, od izabranih, koje za cipset imaju jednu od opcija i prikaze ovako:
https://www.winwin.rs/racunari-i-komponente/racunarske-komponente/maticne-ploce.html?ags_cipset=74143&manufacturer=74623%2C74798

Trenutno su zadane tablice, koje ne bih menjao trenutno, jer se na mnoga mesta provlace.

@tpojka, nisam siguran vise nista, znam samo da cu izludeti od ove dece, vike i dreke :smiley:

Napravicu neki dump kasnije.

Filtriras po onome sto ti dodje u postu.

Npr. ako je odabran proizvod , on ima svoj id, ako je odabrana jos neka karakteristika onda u tablicu pro_filters stavis filter i po proizvodu.

Ako je odabran npr. ram.memorija 16 gb, filtriras sve proizvode koji imaju 16 gb.

Zašto ti ne odgovara? :slight_smile:
U suštini ako si SQL engine natjerao na isti broj binary searchova…optimizacijski je to onda vrlo slično. A druga stvar, koliko imaš takvih upita da ti dolazi u obzir što će ovo rješenje biti možda par milisekundi sporije?
Treba racionalizirati kada se negdje štedi, zašto se štedi…u suprotnom se ponekada ulupa trud u nebitne stvari.

Nadalje, prednost ovog pristupa je što ćeš ga lako moći nadograditi. Dok SQL upit koji je nategnut da ti napravi što želiš, i koji nisi samostalno znao složit …njega ćeš teško modificirati naknadno po potrebi. A biti fleksibilan je velika prednost.
Tako bi ja odvagao cons i pros argumente, a volio bi čuti tvoje razloge za protiv?

Zanemari ovo dolje šta sam pisao, bilo mi je žao brisati.

OBAVEZNO moraš dati tablice i strukture te primjer podataka u njima.

Da ja to radim, trebao bih slijedeće tablice:

  • artikl (svi artikli)
  • grupa (sve grupe)
  • atribut (svi atributi)
  • grupa_atribut (definicija atributa za grupu)
  • grupa_atribut_vrijednost (definiranje dozvoljenih vrijednosti za svaki atribut unutar grupe)
  • artikl_grupa_atribut_vrijednost (vrijednosti za svaki atribut artikla)

Primjer:
artikl
1 - Matična ploča ASROCK A320M-HDV R4.0, AMD A320, DDR4, mATX, s. AM4 - grupa …

grupa
1 - matična ploča

atribut
1-Chipset
2-Socket
3-Format
4-memorija
5-USB
6-D-SUB
7-DVI
8-DisplayPort
9-HDMI
10-M.2

grupa_atribut
1,1
1,2
1,3
1,4
1,5
1,6
1,7
1,8
1,9
1,10

grupa_atribut_vrijednost
1,1,1 - AMD
1,1,2 - Intel
1,1,3 - …

1,2,1 - AM4
1,2,2 - AM3
1,2,3 - …

artikl_grupa_atribut_vrijednost
1,1,1,1
1,1,2,1
1,1,3,…

Evo kreirao sam nesto koliko sam pre mogao, nije original, ali to je to, ti podaci se koriste za uslove u upitima, bez index koji mozda ovde nisu kljucni po pitanju funkcionalnosti, dok za optimzaciju jesu:

CREATE TABLE `proizvodi` (
  `id` int(5) NOT NULL,
  `akt` int(1) NOT NULL DEFAULT '0',
  `naziv` varchar(150) COLLATE utf8_unicode_ci DEFAULT NULL,
  `opis` text COLLATE utf8_unicode_ci
) ENGINE=MyISAM DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;


INSERT INTO `proizvodi` (`id`, `akt`, `naziv`, `opis`) VALUES
(1, 1, 'Proizvod 1', 'Opis opis opis opis'),
(2, 1, 'Proizvod 2', 'Opis opis opis opis'),
(3, 1, 'Proizvod 3', 'Opis opis opis opis'),
(4, 1, 'Proizvod 4', 'Opis opis opis opis'),
(5, 1, 'Proizvod 5', 'Opis opis opis opis'),
(6, 1, 'Proizvod 6', 'Opis opis opis opis'),
(7, 1, 'Proizvod 7', 'Opis opis opis opis'),
(8, 1, 'Proizvod 8', 'Opis opis opis opis'),
(9, 1, 'Proizvod 9', 'Opis opis opis opis');


ALTER TABLE `proizvodi`
  ADD PRIMARY KEY (`id`);


ALTER TABLE `proizvodi`
  MODIFY `id` int(5) NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=10;



CREATE TABLE `filteri` (
  `id` int(5) NOT NULL,
  `akt` int(1) NOT NULL DEFAULT '0',
  `naziv` varchar(150) COLLATE utf8_unicode_ci DEFAULT NULL,
  `id_parent` int(10) NOT NULL DEFAULT '0'
) ENGINE=MyISAM DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;

INSERT INTO `filteri` (`id`, `akt`, `naziv`, `id_parent`) VALUES
(1, 1, 'Proizvodjac', 0),
(2, 1, 'Chipset', 0),
(3, 1, 'Asus', 1),
(4, 1, 'Gigabyte', 1),
(5, 1, 'Biostar', 1),
(6, 1, 'X399', 2),
(7, 1, 'Z390', 2);

ALTER TABLE `filteri`
  ADD PRIMARY KEY (`id`);


ALTER TABLE `filteri`
  MODIFY `id` int(5) NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=8;


CREATE TABLE `pro_filters` (
  `id_pro` int(10) NOT NULL DEFAULT '0',
  `id_filt` int(10) NOT NULL DEFAULT '0'
) ENGINE=MyISAM DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;


INSERT INTO `pro_filters` (`id_pro`, `id_filt`) VALUES
(1, 3),
(1, 6),
(2, 3),
(2, 7),
(3, 4);

Potrebna funkcionalnost:
Slucaj 1 -> Cekirane stavke Asus i Gigabyte da vrati sve Maticne ploce (proizvode) koje pripadaju cekiranim stavkama.
U ovom slucaju, vratilo bi proizvode 1, 2 i 3

Slucaj 2 -> Cekirani proizvodjaci iz prethodnog slucaja Asus i Gigabyte, a onda ide dodatno cekiranje stavki za Chipset, npr samo stavka X399, sto bi prema ovom uslovu, trebalo da mi vrati samo proizvod 1, jer samo taj proizvod poseduje cekirane stavke iz obe grupe (Asus i X399).

Naravno, postoje druge grupe filtera, pored Proizvodjaca i Chipseta, koje mogu da budu cekirane, naveo sam samo dva.

Valjda sam dobro objasnio.

1 Like

Uf… Ako nemaš opciju rekonstrukcije baze, ja bih vjerojatno pribjegao rješenju s Javascriptom :smile:

Osnovni filter (u ovom slučaju proizvođači) napraviti kako sam ranije napisao tako da dobiješ izlistaj, a sve podfiltere (recimo čipset) odraditi jednostavnom eliminacijom, tj. skrivanjem divova ako ne sadrže te znakove. Rješenje je malo “van kutije”, ali mi se čini puno elegantnije nego da radiš vratolomije od upita.

Izgleda da je malo teze da se ovo ostvari, s ovom strukturom tabela i brojem tabela, mora se tu dodati jos neka tabela, kako bi se pomoglo da se upit realizuje ili da se iskoristi ovo sto @bozoou predlaze.

@drmko
To ti pokušavam reći od starta. Vratolomija od upita ne nosi skoro baš nikakav benefit…a puno je nezgodnije nadograđivati postojeće rješenje koje se bazira na vratolomiji od upita.

A ono što je relativno teško postići vratolomijom od upita, postaje trivijalno ako se malo nadogradi sa php logike …ili eventualno JS. (Samo u slučaju JS logike, šalješ suvišan dio MB žicom do klijenta, time “otežavaš bandwith”)

Vratolomija od upita se eventualno isplati u slučaju kada postoji zaista usko grlo u aplikacijii koje je nužna potreba optimizirati.

Po ovome što naknadno čitam koji ti je zahtjev, koliko vidim, moje rješenje zadovoljava to.

Recimo za:

…bi zvao:

$filters = [[3], [4]];
$products = selectProducts($filters);

Što će reći da ti vraća sve proizvode koji imaju na sebi filter 3 i koji imaju na sebi filter 4.

A za situaciju:

…bi zvao:

$filters = [[3,6], [4,6]];
$products = selectProducts($filters);

…te ti u tom slučaju vraća samo proizvod 1 koji na sebi ima filtere 3 i 6, pošto niti jedan proizvod nema filtere 4 i 6.

P.S. tablice su ti sasvim OK.
Zaista nema potrebe da imaš više od tri tablice. Jedna za entitet filtera, jedna za entitet proizvoda i jedna za relaciju između njih. Tako se to radi, sve ostalo su suvišna naklapanja i nepotrebno kompliciranje.

A pogotovo je suvišno nepotrebno da netko ide popravljat kuću tako da ide mijenjat temelje. Jasno je da svaki temelj uvijek može biti bolji i jači…ali ako je trenutni dovoljno dobar, onda se popravlja samo ono što treba popravit. Temelji se diraju samo ako je fakat kritično. A tri tablice kako imaš su više nego solidno rješenje…teško bi zapravo bilo definirati bolje.

1 Like

Tačno. Ponajviše jer tabele nisu normirane na način da pomognu.
U jednoj tabeli su izmiješane kategorije i atributi dok koristi hierarchy adjecency model svojstven kategorijama.

Evo ti par linkova da vidiš prednosti i mane takvog (ali i sličnih i.e. nested set) modela.

Managing Hierarchical Data in MySQL

MySQL - Adjacency List Model For Hierarchical Data Using CTE

Adjacency list vs. nested set model

Stored Procedure to update an adjacency Model to Nested Sets Model

What are the options for storing hierarchical data in a relational database?

2 Likeova

Lepo je sve ovo, nisam ni znao da postoji, kao ni mnogo stosta drugog, ali mi je trenutno prekomplikovano da menjam u toj meri.

Nekako mi se cini, da ovaj neki primer moze da zavrsi posao, nisam 100% siguran, treba tek da probam:

SELECT `p`.`id`, `p`.`name`
FROM   `products` `p`
WHERE   `p`.`id` IN (SELECT     `ae`.`product_id`
                     FROM       `attributes_entity` `ae`
                     INNER JOIN `attributes` `a` ON `ae`.`attribute_id`=`a`.`id`
                     INNER JOIN `attributes_values` `av` ON `ae`.`value_id`=`av`.`id`
                     WHERE       ((`a`.`name`='samsung' AND `av`.`value`='samsung') OR
                                  (`a`.`name`='smartv' AND `av`.`value`='yes'))
                     HAVING COUNT(*) >= 2 -- number of matching attributes required
                    );

[off]
Zato framework i orm.
Naravno ni fw ne znači puno ako se ne postavi struktura baze i tabela.
Kroz Laravel + Eloquent sa ovim gore ispunjenim uslovom (struktura), bi to bilo ovako:

public function search(Request $request)
{
    $query = Product::newQuery();

    $query->when(count($request->manufacturers), function ($q) use ($request) {
        $q->whereHas('manufacturer', function ($r) use ($request) {
            $r->whereIn('id', $request->manufacturers);
        })
    });

    $query->when(count($request->attributes), function ($q) use ($request) {
        $q->whereHas('product_attributes', function ($r) use ($request) {
            $r->whereIn('id', $request->attributes);
        })
    });

    // other request data conditionals
    // ...

    return $query->get();
}

[/off]

Zaključak, isplati se restruktuirati bazu da bi nastavak rada bio podnoštljiv a nekad i samo moguć.