[ draganc__ @ 15.06.2006. 08:52 ] @
Pozdrav svima,
kao sto vidite iz naslova treba mi savjet kako da selektujem tri slucajna reda iz access tabele.

Ja imam jedno rjesenje koje je jako neelegantno, a to je da cijelu tabelu ucitam u DataSet, pa onda generisem tri random broja od 0 do DataSet.Rows.Count, ali mi nikako ne odgovara jer je tabela velika, tj. glupo je da trosim mnogo memorije na serveru zbog samo tri reda. Znaci pitanje je da li se moze ovo odraditi direktno na bazi?

Hvala svima
[ Dejan Vesic @ 15.06.2006. 09:56 ] @
Citat:
draganc__: Pozdrav svima,
kao sto vidite iz naslova treba mi savjet kako da selektujem tri slucajna reda iz access tabele.

Ja imam jedno rjesenje koje je jako neelegantno, a to je da cijelu tabelu ucitam u DataSet, pa onda generisem tri random broja od 0 do DataSet.Rows.Count, ali mi nikako ne odgovara jer je tabela velika, tj. glupo je da trosim mnogo memorije na serveru zbog samo tri reda. Znaci pitanje je da li se moze ovo odraditi direktno na bazi?

Hvala svima


Relaciona baza ne poznaje pojam "redosleda" sem kada sortiraš koristeći ORDER BY klauzulu. Tako da prirodno rešenje tipa:

"Nađi 3 slučajna broja od 1 - Broj_slogova i dohvati te slogove iz baze" ne igra jer niti postoji jednostavan način da dohvatiš n-ti red niti pojam "slučajnog" reda ima odgovarajući kontekst - ako ne znaš kako su redovi sortirani, onda ne znaš koji je 5-ti red.

Zato, ako možeš, daj malo više detalja šta je u tabeli i zašto biraš slučajne redove, čisto da vidimo alternativne načine.
[ draganc__ @ 15.06.2006. 10:04 ] @
Imam tabelu Categories povezanu sa tabelom Items (standardno). Kad posjetilac sajta izabere odredjeni Item tj. proizvod, na strani productDetails.aspx hocu da mu ponudim jos tri proizvoda iz te kategorije, ali necu da uvijek iste proizvode nudim, zato sam mislio random. Pretpostavljam da vec znas kako tabele izgledaju.

Categories: Cat_ID, Cat_Name
Items: Item_ID, Item_Name, Item_Desc itd...
[ vujkev @ 15.06.2006. 10:28 ] @
Imao sam sličan problem koji sam rešio tako što sam u bazu dodao još jedno polje "DatumPrikazivanja" u koje sam smestao datum (i vreme) kad sam taj detalj prikazivao u nekoj "Pogledajte i..." listi. Posle samo izaberes

Code:

  "Select TOP 3 ... from .... order by DatumPrikazivanja"


i odmah stavis u datumprikazivanja trenutno vreme. Na ovaj način šanse da uvek izabereš iste, u tvom slučaju, proizvode su veoma male.

Ako ima neki bolji predlog slobono recite
[ dusans @ 15.06.2006. 10:53 ] @
Mislim da je bolje da dodas jednu kolonu "Random" popunjenu random brojevima, selektuj TOP 3 orderovano po ovom polju i onda upisi u ove redove opet random brojeve isto kao sto je receno u prethodnom postu samo sto ne upisujes datum i vreme jer onda bi dobijao uvek istim redosledom proizvode, ne bi bili random.
[ draganc__ @ 15.06.2006. 11:01 ] @
Sve mi je jasno kao dan, rjesenja su vam sjajna, uradicu kao sto je dusan rekao, najjednostavnije je...

Hvala svima
[ sstanko78 @ 15.06.2006. 11:03 ] @
Sa SQL om: SELECT COUNT(NekoPolje) FROM NekaTabela izbroj koliko zapisa ima ta
tabela. Posle toga generisi slucajan broj. Pa pomocu DataReadera nadji te redove.
DataReader radi u Connected nacinu rada. U petlji u kojoj koristis DataReader znaci
citas samo kada neki tvoj brojac redova ima istu vrednost kao prethodno generisan
broj...


PSEUDOCODE

Code:

String sqlString="SELECT COUNT(NekoPolje) FROM NekaTabela";
....

int iBrojRekorda=sqlCommand.ExecuteNonQuery();
int iSlucajniBroj=...... generisanje slucajnog broja ......

int iTrenutniRecord=0;

while(Reader.Read());
{
     if(iSlucajniBroj==iBrojRekorda)
    {
           ..... citanje podataka .....
    }
    iTrenutniRecord++;
}


Ovo sam napisao posto sam ustao ..... nisam se jos ni umio .....
[ draganc__ @ 15.06.2006. 11:29 ] @
I ovo je isto dobro, meni je nesto slicno bilo vec palo na pamet, medjutim ne znam kako da u ovoj petlji bind-ujem reader na datalist, jer sam do sada nakon sto selektujem ono sto mi treba, radio samo jednostavne varijante tipa:

OleDbDataReader reader = command.ExecuteReader();
DataList1.DataSource = reader;
DataList1.DataBind();



[ Dejan Vesic @ 15.06.2006. 12:32 ] @
Citat:
sstanko78: Sa SQL om: SELECT COUNT(NekoPolje) FROM NekaTabela izbroj koliko zapisa ima ta
tabela. Posle toga generisi slucajan broj. Pa pomocu DataReadera nadji te redove.
DataReader radi u Connected nacinu rada. U petlji u kojoj koristis DataReader znaci
citas samo kada neki tvoj brojac redova ima istu vrednost kao prethodno generisan
broj...


Možda je bolji sledeći algoritam:

- svako polje ima neki ID (pretpostavljam numerički)
- nađeš maksimum: SELECT Max(Field_ID) From Table
- generišeš tri slučajna broja u rasponu 1 - MaxField_ID: Num1, Num2, Num3
- dohvatiš sledeće slogove:

SELECT TOP 1 * From Table Where Field_ID > Num1

SELECT TOP 1 * From Table Where Field_ID > Num2

SELECT TOP 1 * From Table Where Field_ID > Num3

I time dobijaš tačno tri sloga relativno radnom tipa.


[ draganc__ @ 15.06.2006. 12:52 ] @
Ovim algoritmom bi se izbjegle velike petlje jer DataReader mora proci kroz sve zapise u tabeli dok cita u Stankovom algoritmu, ali opet imam par nejasnoca:
1. 1-MaxField_ID je negativan broj, a kod mene su ID-ovi uvijek pozitivni
2. Sta ako zbog brisanja bude ovakav sadrzaj tabele:

ID
1
2
3
10
11

a slucajni brojevi budu 4,5,6. Tada ce sva tri sql-a selektovati proizvod sa id=10
[ negyxo @ 15.06.2006. 13:37 ] @
Code:

SELECT TOP 3 * FROM Items
ORDER BY rnd(Item_ID)


Ovo je ako ti je Item_ID numericko polje, a ko zna mozda radi i sa ostalim tipovima, pogledaj rnd f-ju za jet.


[ Dejan Vesic @ 15.06.2006. 13:53 ] @
Citat:
draganc__: Ovim algoritmom bi se izbjegle velike petlje jer DataReader mora proci kroz sve zapise u tabeli dok cita u Stankovom algoritmu, ali opet imam par nejasnoca:
1. 1-MaxField_ID je negativan broj, a kod mene su ID-ovi uvijek pozitivni


Ma ne nego tražiš slučajan broj u rasponu 1 - MaxFieldID

Citat:

2. Sta ako zbog brisanja bude ovakav sadrzaj tabele:

ID
1
2
3
10
11

a slucajni brojevi budu 4,5,6. Tada ce sva tri sql-a selektovati proizvod sa id=10


Nisam ni misio da je ovo gotov algoritam Ako ti treba ceo pseudo algoritam, izgledalo bi ovako:

Code:

int counter = 0;

int maxID = GetMaxIdForThatTable();

Hashtable nadjenoDoSadaRand = new Hashtable();
Hashtable nadjenoDoSadaRec = new Hashtable();

while (counter < 3) {

   bool dobarSlucajni = false;
   int noviSlucajni;

   while(!dobarSlucajni) {
      noviSlucajni = GetSlucajniBroj(1, maxID);
      dobarSlucajni = !nadjenoDoSadaRand.ContainsKey( noviSlucajni );
   }
 
   nadjenoDoSadaRand.Add( noviSlucajni, true);

   // Ovde imamo novi slučajni

   int newProductId = GetRecordForRandID( noviSlucajni);

   if( !nadjenoDoSadaRec.ContainsKey( newProductId ) ) {
       counter ++;
       nadjenoDoSadaRec.Add(newProductId , true );
   }
   
}


Bolje sada? Pri izlasku iz petlje sigurno ćeš imati tri RAZLIČITA Id-a u nadjenoDoSadaRec

Naravno, ovde definitivno treba predvideti (malo verovatnu, ali predvideti) mogućnost kada nemaš tri sloga u bazi, ili kada se previše vrti a da iz nekog razloga ne može da ih nađe - onda moraš da proveriš zašto ne može
[ mmix @ 15.06.2006. 14:46 ] @
Citat:
negyxo
Code:

SELECT TOP 3 * FROM Items
ORDER BY rnd(Item_ID)



Ovo resenje je ok funkcionalno (pod uslovom da su svi IDevi pozitivni), ali nije performance ok, zato sto se rnd evaluira za svaki red i onda se radi sort, za velike tabele je to veoma sporo.

Isto vazi i za dodatno random polje, sa tim sto je situacija jos gora, da bi sort radio brzo, polje mora da je indeksirano, u kom slucaju je update uzasno spor i lockuje celu tabelu. Da ne pominjem da Access sucks sa transakcijama pa se postavlja pitanje sta ces da radis kad dva korisnika krenu da rade u isto vreme, ko kad radi select a ko kad update?

Genralno je Access lose resenje za bilo koju e-Commerce primenu, moj savet je predji na minimum MSDE/SQLExpress, narocito sad kad je dzabe

Ako to nije opcija, moj predlog je da skeniranje ogranicis na primary key i radis samo read za locirani red kao sto je Dejan Vesic napomenuo. Iako statisticki postoje problemi koje si naveo (sa rupama), sansa da se tako nesto desi je obrnuto proporcionalna broju redova i povecava se sa batch-brisanjima. Jedan od nacina da to resis je dodatna indexed kolona koju ces update jednom dnevno (nocu na primer), ili cak redje u zavisnosti od frekvencije brisanja redova i da taj red koristis umesto ID-a. Ubaci ovaj kod u neki modul:

Code:

Private rednibroj As Long

Sub resetRedniBroj()
    rednibroj = 0
End Sub

' paarmetar je obaveza da bi query optimizer pozvao za svaki red (access tretira funkcije kao deterministicke)
Function NoviRedniBroj(dummy As Long) As Long
    rednibroj = rednibroj + 1
    NoviRedniBroj = rednibroj
End Function


a ovo je query koji pokreces po potrebi (nako sto prvo pozoves ResetRedniBroj ):

Code:

update items
set RedniID = NoviRedniBroj(ID)

[ negyxo @ 15.06.2006. 15:19 ] @
Covek je trazio resenje, ja sam mu ga dao. Nisam hteo da ulazim u detalje kako to radi i sta se desava. Da radi sporo - to ne znam, probao sam na malom setu podataka. Najveca mana ovde je kao sto si i sam primetio access. Sve u vezi data source tj. njihovog izvlacenja ja radim na strani servera, nikakvo dodatno pisanje na strani klijenta mi ne pada na pamet da radim posle par aplikacija pisanih u VB6 u kombinaciji sa accessom (jednom kad se covek navikne na SP, posle povratka nazad nema ) Ako je vec izabrao access neka iskoristi JET koliko moze olaksace sebi posao a ako mu to ne odgovara onda neka procita par tema na DB forumu koju bazu izabrati.
[ mmix @ 15.06.2006. 16:25 ] @
Citat:
negyxo: Covek je trazio resenje, ja sam mu ga dao. Nisam hteo da ulazim u detalje kako to radi i sta se desava. Da radi sporo - to ne znam, probao sam na malom setu podataka.


Dobro de, nemoj se ljutiti, lepo sam reko da je funkcionalno, samo je sporo. Naisao sam skoro bas na post nekog lika koji je radio rnd() varijantu i trebalo mu je 12 minuta da izvuce random red iz tabele sa 2 miliona redova na ultra brzom serveru. Postoje u takvim situacijama i druge tehnike (sa pred-query-em koji sracuna koliki procenat seta cini npr 20 redova, onda se sa WHERE dbo.realrand(ID)<@procenat scope skeniranja spusti na samo 20-tak od svih redova pa je sortiranje po randomu bezaleno.

Takvo limitarnje + select koji samo vrati ID (na osnovu kojeg posle izvuces redove) je na SQL serveru ultra brzo, sto je veoma vazno za e-commerce. Postoje izvesni problemi (SQL server je ekstremno zadrt za deteminizam u korisnickim funkcijama, sql 2005 je jos gori, mora da se radi managed funkcija), ali efektivno mi izvlacimo 10 random redova iz tabele sa 4.5 miliona redova za appx. 2.5 sec.

Alsi sve to naravno, kao sto se svi slazemo, NE na accessu



[ negyxo @ 15.06.2006. 17:25 ] @
Ma ko se ljuti... JA :)

Citat:

Alsi sve to naravno, kao sto se svi slazemo, NE na accessu


Exactly!

[ draganc__ @ 16.06.2006. 09:46 ] @
negyxo, ovo je veoma jednostavno i jako mi se svidja, medjutim UVIJEK izbaci iste zapise, probao sam 10 puta.

SELECT TOP 3 * FROM Items ORDER BY rnd(Item_ID)

Ovaj isti query napravim u accessu i on izbacuje random kako treba, ali iz mog koda uvijek iste zapise selektuje.
Znas li mozda u cemu je problem???

P.S.
Valjda nece biti previse sporo, ja necu imati vise od 10000 artikala sigurno...

[Ovu poruku je menjao draganc__ dana 16.06.2006. u 11:51 GMT+1]
[ negyxo @ 17.06.2006. 08:54 ] @
Otkucaj randomize

Problem je sto on ili kesira podatke ili mu je seed za random generator uvek isti ili boga pitaj sta radi, uglavnom ne radi

Probaj ovo
Code:

SELECT TOP 3 * FROM Items ORDER BY rnd(second(time()) * Item_ID)


Ovo ti je random na bazi jedne sekunde, pa sad ako ti je ovo dovoljno...
Postoji i jos jedno resenje a to je da prosledis ti neki random broj JET-u
Code:

Random r = new Random();

OleDbCommand cmd = new OleDbCommand("SELECT TOP 3 * FROM Items ORDER BY rnd(? * Item_ID)");
cmd.Parameters.Add(new OleDbParameter("@random", OleDbType.Double));
cmd.Parameters["@random"].Value = r.NextDouble();                

...


Sad ce se mmix javiti i reci da nije vise toliko funkcionalno i moram priznati u pravu bi bio

Sto se tice sporosti najbolji nacin je da testiras sam, pa ako zadovoljava koristi a ako ne trazi drugo resenje.