amii
18.01.2011, 12:36:22
Mam takie proste zapytanie, którym pobieram określoną ilość rekordów i wszystko byłoby ok gdy w tabeli pola id występują stale po sobie czyli np. 1,2,3,4,5 itd. Lekki problem jest gdy tabela ma taką strukturę id 1,2,3,11,12,13 wtedy skrypt dla wartości $min = 4 i $max = 5 wykona pusty przebieg.
Próbowałem to rozwiązać wczytując w pętli id do tablicy, tylko to jest nieefektywne, nieeleganckie i w ogóle be. Może jest jakaś inna metoda, może być też inne zapytanie byleby pobierało rekordy z jakiegoś przedziału.
$zapytanie = "SELECT * FROM tabela WHERE id BETWEEN $min AND $max";
nospor
18.01.2011, 12:40:12
$zapytanie = "SELECT * FROM tabela WHERE order by id asc limit ".($min-1).",2";
gdzie 2 to $max - $min + 1
wookieb
18.01.2011, 12:41:07
Czyli pole ID jest tekstem a nie numerem (czyli tak jak być powinno). Zmień to.
Przeszukiwanie rekordów zapytaniem operatorem beetween na głównym kluczu tabeli jest bardzo szybkie więc o "puste" przebiegi się nie martw.
@nospor pamiętaj, żę beetween jest tu lepsze
nospor
18.01.2011, 12:42:57
Cytat
pamiętaj, żę beetween jest tu lepsze
ta, szczegolnie gdy trafi na luke....

Przeczytaj jeszcze raz dokladnie o co chodzi
wookieb
18.01.2011, 12:47:40
Czytam

I biorę poprawkę na to, że autor tematu chyba nie do końca rozumie znaczenie operatora "BETWEEN"
nospor
18.01.2011, 13:02:28
ALeż rozumie. I właśnie dlatego zadał pytanie na forum jak to inaczej napisac, bo gdy są luki to beetwen bedzie o kant 4 liter przy tym co on chce osiągnąć. I ja mu podałem alternatywę
amii
18.01.2011, 13:44:55
Tego chyba się jednak nie da rozwiązać tak jak tego oczekuje ale dzięki za odpowiedzi i poświęcony czas. Skrypt odczytuje z bazy danych limity $max i $min przy czym uruchamiany jest z CRON co 1 minutę.
wookieb - nie przejmuję się obciążeniem tylko jałowymi uruchomieniami, bo program mając np. różnicę 1000 pomiędzy numerami id czyli 100,101,102,1000,1001,1002 uruchomi się 1000 razy niepotrzebnie.
Ale LIMIT tu nic nie zmienia bo program odczyta z bazy wartość pierwszą wstawi w $min, później obliczy $max na podstawie tego ale to NIE WYKLUCZA, że pomiędzy tymi wartościami nie będzie rekordów.
nospor
18.01.2011, 17:14:14
Cytat
Ale LIMIT tu nic nie zmienia bo program odczyta z bazy wartość pierwszą wstawi w $min, później obliczy $max na podstawie tego ale to NIE WYKLUCZA, że pomiędzy tymi wartościami nie będzie rekordów.
order by oraz limit dziala na zestawie danych nie zależnie czy są miedzy nimi dziury czy nie wiec naprawde nie wiem w czym masz teraz problem.
thek
18.01.2011, 22:03:42
A ja bym zapytał skąd Ty bierzesz $min i $max. Może sie bowiem okazać, że wyliczasz te dane niepotrzebnie, a istnieje zupełnie inne, prostsze podejście do rozwiązania. Nie wiem co konkretnie chcesz uzyskać, ale moim zdaniem niegłupi pomysł to zrobić zamiast jednego - dwa zapytania.
1) Uzyskaj z bazy liczbę rekordów spełniających zależność x >= $min AND x <= $max, z użyciem count()
2) jeśli uzyskany_wynik > 0 to zrób zapytanie SELECT (...) WHERE x >= $min ORDER BY x ASC LIMIT uzyskany_wynik
Takie rozwiązanie, mimo iż pozornie wolniejsze może być szybkie i tylko na poziomie bazy jeśli zrobisz procedurę, do której jako paramtery prześlesz $min i $max. Count na indeksowanym polu będzie bardzo szybki i całość będzie kompletnie niewrażliwa na luki pomiędzy identyfikatorami.
Jeszcze innym rozwiązaniem jest pobranie identyfikatorów rekordów spełniających warunek i tylko dla nich wykonać operację. Zwyczajnie opis szerzej problem, a może znajdzie się lepsze rozwiązanie problemu niż to co jest akurat w Twojej głowie i tak jak Ty byś tochciał rozwiązać.
amii
19.01.2011, 09:25:34
Cytat(nospor @ 18.01.2011, 17:14:14 )

order by oraz limit dziala na zestawie danych nie zależnie czy są miedzy nimi dziury czy nie wiec naprawde nie wiem w czym masz teraz problem.
W tym mam problem, że skrypt się i tak uruchomi na pustym zestawie bo takie są odczytywane wartości z bazy danych. Żeby to działało tak jak chce to musiałbym zmodyfikować zapisywanie wartości w bazie dla $min i $max a nie wiem jak to zrobić czyli jaki warunek dać w linii EPIC FAIL. Zresztą tak wygląda fragment kodu za to odpowiedzialny.
Program przy każdym uruchomieniu robi:
CREATE TABLE `settings` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(20) NOT NULL DEFAULT '',
`value` varchar(255) NOT NULL DEFAULT '',
PRIMARY KEY (`id`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8 AUTO_INCREMENT=7 ;
--
-- Zrzut danych tabeli `settings`
--
INSERT INTO `settings` (`id`, `name`, `value`) VALUES (1, 'proxy', '441'),
(2, 'katalog', '579'),
(3, 'licznik', '1'),
(4, 'pula', '1'),
(5, 'paginator', '10'),
(6, 'limit', '460');
//Funkcja pobiera ważne ustawienia z bazy danych
function s($var) {
connect_to_db();
$query = 'SELECT name, value FROM settings';
$site_settings[$r['name']] = $r['value'];
}
$value = $site_settings[$var];
return $value;
}
//czytanie przesuniecia w tabeli katalog
$pobierz = "SELECT MIN(id) FROM main as wynik1";
$zap = mysql_query($pobierz) or
die('Nastapil blad w polaczeniu w zapytaniu do bazy w instrukcji 1 SELECT MIN(id) FROM id_katalog as wynik : '. mysql_error()); $katalog_limit = mysql_fetch_array($zap); //pobranie wartosci minimalnej id z katalogi_id $katalog_limit_max = $katalog_limit['MIN(id)'] + s('katalog'); //limit dodan dla katalogow domyslnie 5
$limit_dodan_katalogi = s('pula'); //pobieramy limit do katalogow z bazy
//i inkrementujemy wartosc przesunicia o limit po czym zapisujemy w bazie i tu jest pies pogrzebany bo niby jak inaczej to by maiło byc rozwiazne
$pobierz5 = "UPDATE settings SET value=value+$limit_dodan_katalogi WHERE name='katalog'"; //LINIA EPIC FAIL
$zap5 = mysql_query($pobierz5) or
die('Nastapil blad w polaczeniu w zapytaniu do bazy w instrukcji UPDATE increment dla katalog : '. mysql_error());
thek
19.01.2011, 10:34:54
Po pierwsze to optymalizuj sobie. Skoro z bazy pobierasz s($var) jakieś ustawienie konfiguracyjne, to po co w zapytaniu każesz my wypluć do dalszej obróbki całą tabelę? Nie prościej będzie w WHERE nazwę ustawienia konfiguracyjnego pchnąć?
function s($var) {
connect_to_db();
if( $result && mysql_num_rows( $result ) > 0 ) { //nie sprawdzałeś czy coś jest w wyniku. Jeśli zapytanie było prawidłowe, ale nie zwróciło wyniku jakiegokolwiek to były jaja return $r['value'];
} else {
return FALSE; //lepiej rzucić wyjątek, ale to ma być proste więc tylko false popycham.
}
}
inna sprawa... funkcja s('katalog') zwraca w chwili obecnej 579, a więc
$katalog_limit_max = min(id) + 579; a więc minimum 579 a nie 5... Nie wiem gdzie te 5 widzisz.
Nie widzę sensu tej operacji. Dla mnie w chwili obecnej cały mechanizm jaki zrobiłeś wygląda jak jakaś dziwna implementacja AUTO_INCREMENT dla kolumny z licznikiem katalogów... Napisz CO ma w zamiarach ten mechanizm robić. O co chodzi ze zmienną 'katalog' w ustawieniach. W chwili obecnej mam wrażenie, że przechowujesz liczbę dodanych katalogów w niej, ponieważ przy każdym uruchomieniu skryptu inkrementujesz go o liczbę s('pula'), czyli 1. Dla mnie ten kod jest jakiś "dziki"

Naprawdę... Napisz co masz na wejściu, co mniej więcej ma kod zrobić i co ma dostać na wyjściu. Najlepiej w krokach i 2-3 krotnie, byśmy wiedzieli jak w "debugu" Twoich myśli zmienna katalog z settings miała by działać. Inaczej nie dojdziemy do zrozumienia pełnego co Twój kod ma tak naprawdę robić.
amii
19.01.2011, 11:15:19
sorki tam zostały stare komentarze chciałem edytować ale wyskoczyło jakieś formatowanie html
OK zatem:
na wejściu pobieram:
- przesunięcie w katalog za pomocą s('katalog') jest to przesunięcie w tabeli katalogów liczone za pomocą pobrania minimalnej wartości id i dodania do niego zmiennej pobranej z bazy czyli s('katalog') która jest inkremantowana za każdym uruchomieniem programu o wartość s('pula').
- liczniki przesunięcia czyli s('pula') czyli ile katalogów ma za jednym uruchomieniem obsługiwać program, wartość tą można zmienić tylko z panelu admina i jak widać tutaj wynosi 1
Czyli jeśli uruchamiam program to program liczy $max w liniach 16-20 a $min = $max - s('pula')
Następnie jest zapytanie:
$zapytanie = "SELECT * FROM tabela WHERE id BETWEEN $min AND $max";
//tu program wykonuje jakies operacje na pobranych danych
}
//tu program uaktualnia licznik przesunięcia w katalogach o wartość równą s('pula')
$pobierz5 = "UPDATE settings SET value=value+$limit_dodan_katalogi WHERE name='katalog'";
$zap5 = mysql_query($pobierz5) or
die('Nastapil blad w polaczeniu w zapytaniu do bazy w instrukcji UPDATE increment dla katalog : '. mysql_error());
thek
19.01.2011, 12:24:55
Jeśli więc dobrze rozumiem (nie mam pewności, więc jakby co mnie popraw), bierzesz sobie od startowego katalogu o min(id) ileś wpisów z bazy i obrabiasz je, po czym zapisujesz przesunięcie inkrementowane o pulę obrabianych. W następnym odpaleniu musisz obliczyć gdzie był zakończony cykl i od tego miejsca pracować dalej. Tak robisz aż do końca tabeli. Pytanie powstaje: "Czy ma sens taka implementacja przesunięcia?" Zauważ, że nie. Robisz masę niepotrzebnych obliczeń i odwołań do bazy w sytuacji gdzie nie jest to konieczne, ponieważ implementacja przesunięcia to u Ciebie lekko zakręcony algorytm, na dodatek problematyczny. Nieco źle podeszłeś do problemu, gdyż uparłeś się na taką, a nie inną realizację liczenia offsetu, który jest wrażliwy na dziury.
Wystarczy że podejdziesz do tego jak do paginacji. Tak naprawdę wystarczy gdy znasz pulę i numer kolejnego cyklu.
Przebieg ma pulę 1 i tyle katalogów obrabia, ale zmieńmy to dla lepszej widoczności na 3. Rekordy w bazie mają dziury i wynoszą ich id: 1,2,3,4,6,7,9,12,15,16,17.
Rusza cykl i bierze on pulę katalogów przesuniętą o numer cyklu przemnożony przez pulę. W bazie na chwilę obecna jest wartość cyklu czy tam katalogu = 0. Operacje się kończą inkrementujemy numer cyklu. Zauważ co się dzieje w kolejnych krokach by zauważyć ogromne uproszczenie całości
Cykl 0: Pobranie 0*3, 3 czyli LIMIT 0, 3 -> rekordy 1,2,3 -> potem inkrementacja cyklu o 1 (do 1)
Cykl 1: Pobranie 1*3, 3 czyli LIMIT 3, 3 -> rekordy 4,6,7 -> potem inkrementacja cyklu o 1 (do 2)
Cykl 2: Pobranie 2*3, 3 czyli LIMIT 6, 3 -> rekordy 9,12,15 -> potem inkrementacja cyklu o 1 (do 3)
Cykl 3: Pobranie 3*3, 3 czyli LIMIT 9, 3 -> rekordy 16,17 -> potem inkrementacja cyklu o 1 (do 4)
Cykl 4: Pobranie 4*3, 3 czyli LIMIT 12, 3 -> BRAK rekordów wyniku, skrypt kończy działanie.
Czy to nie o wiele prostsze rozwiązanie? Nie muszę znać min(id), nie muszę liczyć niczego tak naprawdę. Wystarczy że znam pulę, numer cyklu i nic więcej nie jest mi potrzebne do obliczenia wartości dla LIMIT. Masa niepotrzebnych zapytań do bazy oraz obliczeń "na boku" zredukowana do minimum niezbędnego
Pilsener
19.01.2011, 12:44:06
Skoro problem dotyczy
Cytat
i wszystko byłoby ok gdy w tabeli pola id występują stale po sobie czyli np. 1,2,3,4,5
to nie prościej ponumerować rekordy w bazie jednym szybkim zapytaniem przed tą operacją? Różnego rodzaju numerowanie często stosuje się w różnych rankingach czy zestawieniach, które z wiadomych powodów nie mogą być przeliczne w czasie rzeczywistym.
amii
20.01.2011, 12:39:58
thek tak skrypt powinien działać w ten sposób jaki opisujesz. Jednak LIMIT nie rozwiązuje tutaj problemu gdyż skrypt cały czas się uruchamia dla nie istniejących rekordów.
Ostatecznie skorzystałem z pomysłu Pilsener-a i takim zapytaniem zmodyfikowałem bazę tak żeby rekordy w id były po kolei ponumerowane i teraz jest już ok:
connect_to_db();
$i = 1;
$sql = "SELECT id FROM main";
$table[$i] = $row['id'];
$sql1 = "UPDATE main SET id=$i WHERE id=$table[$i]";
$i++;
}
thek
20.01.2011, 13:19:54
Ale jak się uruchamia dla nie istniejących rekordów? Widzisz by to robił? Pobieram za każdym razem tylko istniejące. LIMIT mi pobiera tylko kolejne po sobie rekordy... Skoro nakazuję mu pobrać 3, to nie robi mi luki w cyklu 1 gdzie mam 4,6,7 do postaci 4,6 co postulujesz jakoby robił. LIMIT pobiera kolejne pasujące do wzorca i NIE wstawia nie istniejących, w tym wypadku nie robi pustego przebiegu dla 5, ale pobiera brakujący mu dla 3 rekordów następny element, a więc o id =7... Chciałeś w zapytaniu kolejne 3 rekordy? Dostałeś 3, a nie 2. Chyba kompletnie nie rozumiesz jak działa LIMIT w MySQL. On nie robi sobie LIMIT X, Y jako pobierz od rekordu o id = X do rekordu o id = Y. LIMIT działa na zasadzie: pobierz od rekordu mającego indeks X spośród wybranych zapytaniem wyników kolejne Y rekordów. A to cholernie duża różnica.
Popatrz na wywołania. Czy dla cyklu 0 zaczyna mi od rekordu o id 0 czy od 1? a przecież mam LIMIT 0, 3 i patrz dalej... Dlaczego mi się "cudem" kompletnie liczby w LIMIT nie pokrywają z uzyskiwanymi jako wyniki? LIMIT 6, 3 daje rekordy 9,12,15, więc jak to wyjaśnisz? Jakoś nie daje od rekordu o id = 6 do 6+3, czyli id = 9

Samo zapytanie daje dobre rezultaty. Widocznie gdzieś
inna część skryptu ma walniętą pętlę for($i = $min; $i < $min+$pula; $i++) i to ona Ci brudzi, a nie zapytanie. Lepiej posprawdzaj dokładnie, bo jak na razie to Twój opis działania LIMIT jest dla mnie herezją

Gdy już ją znajdziesz zamień ją na foreach lub while i dopiero wtedy sprawdź czy wszystko jest cacy. Bo jak dla mnie tak masz najpewniej zrobione i szukasz byka w logice nie tam gdzie on faktycznie jest.
Pilsener
21.01.2011, 13:34:06
Możesz ponumerować rekordy jednym zapytaniem używając zmiennej:
http://nospor.pl/mysql-faq.html#faq-6