Pomoc - Szukaj - Użytkownicy - Kalendarz
Pełna wersja: losowe rekordy z tabeli z priorytetami
Forum PHP.pl > Forum > Bazy danych
dtb
witam,
mam przykładową tabelę:
Kod
id | punkty
---+-------
1  | 4
2  | 7
3  | 1
4  | 8
5  | 3
...

i chce wyciągnąć losowe 3 rekordy, jednak, aby wiersze z większą wartością 'punkty' częściej wypadały - tzn. szansa, że wypadnie rekord o id=4 jest 2 razy większa, niż w dla id=1.
czy wiek ktoś jak napisać takie zapytanie? mam nadzieje, że jasno zobrazowałem sytuację.
najtje
Potrafię to zrobić w postgresie:

  1. SELECT * FROM (SELECT id, generate_series(1,punkty) AS p FROM tabela AS yy )AS zz ORDER BY random() LIMIT 3


Idea jest taka że tworzysz relacje z powielenia wierszy zgodnie z ilością punktów jakie mają przypisane następnie sortujesz wg random() i zwracasz pierwsze 3
dtb
a w mysql to nie jest możliwe?

EDIT:
udało mi się do tej pory stworzyć coś takiego:
  1. SELECT group_concat(repeat(concat(id,','),prio)) AS list FROM links ORDER BY rand() LIMIT 3

zwraca mi w 1 rekordzie wszystkie id, oddzielone przecinkami, gdzie każde id jest tyle razy powtórzone, ile wynosi wartość pola punkty. teraz bym musiał podzielić to wszystko na osobne rekordy, ale nie za bardzo wiem jak.
najtje
Nie za bardzo bo jedną z wad mysql jest to że nie ma on typów tablicowych.
W zasadzie żadna z tych funkcji nie zwraca tablicy.
Potrzebna by nam była funkcja która ze skalara zwróci tablice.

No chyba że taką funkcję można napisać samemu wtedy można byłoby napisać sobie generate_series() i mój przykład zadziałby normalnie lub z drobnymi modyfikacjami.




Jeszcze jeden pomysł:
Może zmodyfikujesz strukturę bazy tak żeby umożliwić zapytanie w inny sposób ?
Tzn. wiadomo że suma prawdopodobieństw to jest 1.
funkcja rand() w mysql zwraca liczbę od 0..1 (float)
Należałoby podzielić ten '1' przez sumę wszystkich punktów i proporcjonalnie każdemu rekordowi przydzielić jakiś odcinek na osi od 0..1
np jeśli suma wszystkich punktów w bazie to 30 to dla pierwszego rekordu przedział na osi to: [0..4/30] (bo tyle ma punktów) następny rekord ma od (4/30..11/30) dalej (11/30..12/30) itd

id | punkty | lewa granica | prawa_granica
1 4 0 4/30
2 7 4 11/30
...
i wtedy miałbyś zapytanie takie:
  1. SELECT *, rand() FROM tabela HAVING rand() >= lewa_granica AND rand() <= prawa_granica
dtb
myslalem nad czyms takim, ale bedzie z tym duzy klopot, poniewaz punkty sie czesto zmieniaja i trzeba by modyfikowac wszystkie rekordy przy kazdej zmianie.
odnoście mojego wcześneiejszego kodu: z użyciem substring_index() i rand() udalo mi sie wyciagnac 1 losowe id, ALE lista z id (pole 'list' w którym sa id oddzielone przecinkami) nie może być większa niż 1kb, czyli o wiele za malo. mozna to jakos zmienic? ale obowiam sie, ze przy przewidywanej ilosci wierszy i wartosciach bede potrzebowal n mb...
kitol
Część rozwiązania (zapytanie zwraca 1 liczbę wylosowaną ze zbioru z priorytetami). Aby wylosować 3 wartości należy je powtórzyć trzykrotnie.

  1. SELECT table1.id
  2. FROM `table1` WHERE (SELECT SUM(x.punkty) FROM table1 AS x WHERE x.id <= table1.id)> RAND()*(SELECT SUM(punkty) FROM table1) LIMIT 1
osiris
Moze sprobuj czegos takiego:
  1. (SELECT id FROM tab) UNION
  2. (SELECT id FROM tab WHERE punkty > 3)
  3. UNION
  4. (SELECT id FROM tab WHERE punkty > 6)
  5. ...
  6. ORDER BY RAND() LIMIT 3
najtje
W sumie kolega nie powiedział nam jeszcze czy losowania są z powtórzeniami czy bez powtórzeń smile.gif
Rozwiązanie kitola jest fajne. Zastanawiam się tylko czy funkcja rand() jeśli umieści się ją w kauzuli WHERE czy nie jest ewaluowana z każdym porównaniem.
Wersja która nie oblicza rand() z każdym porównaniem jest poniżej:

  1. SELECT * FROM ( SELECT
  2. id, (prawa_strona-punkty)/pts AS l_strona, prawa_strona/pts AS p_strona, random
  3. FROM (SELECT id,punkty,(SELECT sum(punkty) FROM t1 WHERE t1.id <=t.id )AS prawa_strona FROM t1 AS t) AS q1,
  4. (SELECT rand() AS random) AS q2,
  5. (SELECT sum(punkty)AS pts FROM t1) AS q3
  6. ) AS q4
  7. WHERE random>l_strona AND random<=p_strona OR random=0.0 AND l_strona=0.0


Można to też zastosować w postgresie ale trzeba zmienić wywołanie rand() na random().
Oczywiście wersja mysql >=5 (chyba 4.1 też wspiera subselecty)
To jest wersja lo-fi głównej zawartości. Aby zobaczyć pełną wersję z większą zawartością, obrazkami i formatowaniem proszę kliknij tutaj.
Invision Power Board © 2001-2025 Invision Power Services, Inc.