[ srki @ 13.03.2007. 12:19 ] @
Postoji vip restoran u koji menadzeri raznih kompanija dovode svoje poslovne partnere, kolege i clanove upravnih odbora...Nazalost taj restoran ima ogranicen kapacitet (recimo 200 osoba) i zbog toga svaka osoba mora da se unapred najavi, da kaze vreme kada ce biti tu i koliko osoba dovodi (ukljucujuci i sebe)
Tabela vip_restoran koja sadrzi vremena dolaska i odlaska odredjenih grupa osoba koje dovodi vip_customer.
Code:
CREATE TABLE vip_restoran (
  vip_customer_id INTEGER NOT NULL,
  start_time TIMESTAMP,
  end_time TIMESTAMP,
  broj_musterija_koje_customer_dovodi INTEGER
);


Zamislimo da ja hocu da povedem mojih p_number kolega u taj restoran izmedju p_start_time i p_end_time. (p je prefiks koji oznacava parametar zbog lakseg citanja koda). Sada postoji problem ako se to ubaci u tabelu jer moze da se desi da u nekom trenutku bude vise od 200 osoba u restoranu. Stoga nama treba query koji ce da u tabelu vip_restoran ubaci (p_customer_id, p_start_time, p_end_time, p_number) samo ako to kapacitet restorana dozvoljava (znaci ne sme da bude vise od 200 osoba u nekom trenutku). Da vas sad vidim :-)
[ goranvuc @ 13.03.2007. 12:48 ] @
@srki, problem je jako interesantan, ali ako sam ja dobro shvatio problem imamo tabelu "destinaciju", ali nam nedostaje tabela iz koje radimo INSERT, pa te molim samo da ako je ovo tacno da nam das i podatke o "izvornoj" tabeli, ili si to mislio da se resi preko Stored Procedure koja ima 4 navedena parametra? Dakle, samo malo dodatnog pojasnjenja
[ bags @ 13.03.2007. 13:57 ] @
Glupo pitanje:

Mogu li se koristiti trigeri?
[ srki @ 13.03.2007. 14:28 ] @
Citat:
bags:Mogu li se koristiti trigeri?

Moze, koristi sta god hoces.

Citat:
goranvuc: @srki, problem je jako interesantan, ali ako sam ja dobro shvatio problem imamo tabelu "destinaciju", ali nam nedostaje tabela iz koje radimo INSERT, pa te molim samo da ako je ovo tacno da nam das i podatke o "izvornoj" tabeli, ili si to mislio da se resi preko Stored Procedure koja ima 4 navedena parametra? Dakle, samo malo dodatnog pojasnjenja ;)


Recimo da imas 4 parametra u stored procedure i treba da napises naredbu (po mogustvu standardni sql, ne pl/sql) koja ce da ti ubaci novi podatak u tabelu ako je u skladu sa kapacitetom restorana.

Jos objasnjenja za one koji nisu shvatili.
Neka u tabeli trenutno imamo ovo:
Code:

cstmr_id   start     end    br_musterija
--------  -------  ------ ------------
745        1:15pm     3pm       50
83           4pm   4:15pm       28
63           2pm      6pm      130


Tu vidimo da ni u jednom trenutku nema preko 200 gostiju u restoranu. Ako bismo hteli da bukiramo 60 gostiju od 2:30 do 5 onda to ne bi moglo da se desi jer bi od 4 do 4:15 bilo ukupno 28+130+60=218 gostiju a kapacitet je samo 200. To znaci da treba da odbijemo to bukiranje i da ga ne ubacimo u tabelu.

Mala pomoc: probajte da smislite kako biste algoritamski resili ovaj problem na najefikasniji nacin.
[ goranvuc @ 13.03.2007. 14:41 ] @
OK, znaci:
Citat:
srki: Recimo da imas 4 parametra u stored procedure i treba da napises naredbu (po mogustvu standardni sql, ne pl/sql) koja ce da ti ubaci novi podatak u tabelu ako je u skladu sa kapacitetom restorana.

... je zadatak, a ne:
Citat:
srki:Stoga nama treba query koji ce da u tabelu vip_restoran ubaci (p_customer_id, p_start_time, p_end_time, p_number) samo ako to kapacitet restorana dozvoljava (znaci ne sme da bude vise od 200 osoba u nekom trenutku).


Nisam se bezveze zakacio za ovo, jer kod ovog problema nije svejedno kada insertujes vise redova (I verzija zadatka, preko query), ili samo jedan red (II verzija, preko procedure).

A sad na posao
[ srki @ 13.03.2007. 15:17 ] @
Da, treba mi nesto kao INSERT WHEN condition THEN INTO vip_restoran VALUES (p_customer_id, p_start, p_end, p_broj) a fora je samo naci taj condition.
[ Zidar @ 13.03.2007. 19:01 ] @
Zadatak se svodi na pronalazenje ukupnog broja ljudi u bilo kom trenutku zadatog intervala (p_start, p_end)

Prohvaticu dizajn tabele ovakav kakav jeste, uz jednu dopunu: Zakazivanje je an svakih 15 miuta. ne mozete zakazati ud 1:43PM do 5:27PM, ali moze 1:45PM do 5:30PM. ovo nece uticati na opstost resenja, ali ce biti lakse da se shvati.

Onda cu da u igru uvedem tabelu koju sam nazvao DanPo15Minuta. tabela ima dve kolone: (Od,Do) i obe su DateTime tipa. Tabela ima 24*4 = 96, redova, po jedan za svakih 15 minuta u danu. Ovako pocinje:

Od Do
12:00:00 AM 12:15:00 AM
12:15:00 AM 12:30:00 AM
12:30:00 AM 12:45:00 AM
12:45:00 AM 1:00:00 AM
1:00:00 AM 1:15:00 AM
1:15:00 AM 1:30:00 AM
1:30:00 AM 1:45:00 AM
1:45:00 AM 2:00:00 AM
2:00:00 AM 2:15:00 AM

a ovako zavrsava:

Od Do
10:30:00 PM 10:45:00 PM
10:45:00 PM 11:00:00 PM
11:00:00 PM 11:15:00 PM
11:15:00 PM 11:30:00 PM
11:30:00 PM 11:45:00 PM
11:45:00 PM 12:00:00 PM

Onda cu tabelu vip_restoran da "explodiram", tako sto cu za svaki interval (strt_time, end_time) da prikazem sve cetvrtine sata (15 min) koji pripadaju tom intervalu. Interval od 2pm do 6pm prikazao bi se ovako:

vip_cutomer_id start_time Od Do end_time broj_musterija_koje_customer_dovodi
63 2:00:00 PM 3:15:00 PM 3:30:00 PM 6:00:00 PM 130
63 2:00:00 PM 2:00:00 PM 2:15:00 PM 6:00:00 PM 130
63 2:00:00 PM 2:15:00 PM 2:30:00 PM 6:00:00 PM 130
63 2:00:00 PM 2:30:00 PM 2:45:00 PM 6:00:00 PM 130
63 2:00:00 PM 3:00:00 PM 3:15:00 PM 6:00:00 PM 130
63 2:00:00 PM 5:45:00 PM 6:00:00 PM 6:00:00 PM 130
63 2:00:00 PM 3:30:00 PM 3:45:00 PM 6:00:00 PM 130
63 2:00:00 PM 3:45:00 PM 4:00:00 PM 6:00:00 PM 130
63 2:00:00 PM 5:00:00 PM 5:15:00 PM 6:00:00 PM 130
63 2:00:00 PM 5:30:00 PM 5:45:00 PM 6:00:00 PM 130
63 2:00:00 PM 2:45:00 PM 3:00:00 PM 6:00:00 PM 130
63 2:00:00 PM 5:15:00 PM 5:30:00 PM 6:00:00 PM 130
63 2:00:00 PM 4:45:00 PM 5:00:00 PM 6:00:00 PM 130
63 2:00:00 PM 4:30:00 PM 4:45:00 PM 6:00:00 PM 130
63 2:00:00 PM 4:15:00 PM 4:30:00 PM 6:00:00 PM 130
63 2:00:00 PM 4:00:00 PM 4:15:00 PM 6:00:00 PM 130

Ako ovako uradim za sve zadate intervale, mogu da uradim

GROUP BY Od, Do, SUM(broj_musterija_koje_customer_dovodi)

i to ce mo dati za svaki interval od 15 minuta koliko ljudi ima u restoranu.

Kveri koji pokazuje ukupan broj ljudi po intervalu od 15 minuta izgleda ovako (Access, ali mislim da ce raditi svuda):

CREATE VIEW vip_restoran_exp
AS
SELECT
DanPo15Minuta.Od
, DanPo15Minuta.Do
, Sum(vip_restoran.broj_musterija_koje_customer_dovodi) AS SumOfbroj_musterija_koje_customer_dovodi
FROM vip_restoran, DanPo15Minuta
WHERE (((DanPo15Minuta.Od)>=[start_time]) AND ((DanPo15Minuta.Do)<=[end_time]))
GROUP BY DanPo15Minuta.Od, DanPo15Minuta.Do;

Onda kreiram jos jednu pomocnu tabelu, vip_to_test, gde cu da drzim parametre. Identicna struktura kao vip_restoran, i samo jedna red dozvoljen. Unesem moje parametre pa onda tu tabelu takodje explodiram, na slican nacin:

CREATE VIEW vip_to_test_exp
AS
SELECT vip_to_test.Id, vip_to_test.vip_cutomer_id, vip_to_test.start_time, DanPo15Minuta.Od, DanPo15Minuta.Do, vip_to_test.end_time, vip_to_test.broj_musterija_koje_customer_dovodi
FROM vip_to_test, DanPo15Minuta
WHERE (((DanPo15Minuta.Od)>=[start_time]) AND ((DanPo15Minuta.Do)<=[end_time]));

Ovde mi ne treba GROUP BY posto imam tacno jedan red u tabeli vip_to_test.

Sada ova dva view-a JOINujem po paru (Od,Do) ovako:

SELECT
vip_restoran_exp.Od
, vip_restoran_exp.Do
, vip_restoran_exp.SumOfbroj_musterija_koje_customer_dovodi
, vip_to_test_exp.broj_musterija_koje_customer_dovodi
, [SumOfbroj_musterija_koje_customer_dovodi]+[broj_musterija_koje_customer_dovodi] AS Total
FROM vip_restoran_exp
INNER JOIN vip_to_test_exp
ON (vip_restoran_exp.Do = vip_to_test_exp.Do)
AND (vip_restoran_exp.Od = vip_to_test_exp.Od);

Ovaj SELECT mozeet da upotrebite da vidite da li ce dodavanje nove grupe premasiti dozvoljeni broj posetilaca.

Ako vam se interval od 15 minuta cini nedovoljno finim, napravite tabelu koja sadrzi svaki minut u danu, ili svaku sekundu cak i dbicete sta vam treba.

Komentar na datu tabelu vp_restoran: iz dizajna tabele sledi da se posete zakazuju u jednom danu, i da se restoran zatvara najkasnije u ponoc, 24:00.

Ako bi u igru uveli i datumski deo timestampa, kveriji bi se donekle zakomplikovali i to je sve. Ako uvedete cele datume u igru, mozete ovo resenje da primenite na hotele i rezervacije soba, sa pomocnom tabelom koja sadrzi sve dane u narednih 50 godina na primer.

Zakacio sam Access fajl, gde se vide kveriji i primer. Zavrsni kveri je vip_to_test_exp_show_total, a on se dobija od vip_restoran_exp i vip_to_test_exp. A njih dva smo objasnili u tekstu.
Postoji i kveri vip_restoran_exp_long, koji je samo demonstracija da se lakse shvati proces "exsplodiranja" vremenskih intervala.

Toliko od mene. Sigurno moze i elegantnije, ali je meni ovo najlakse da pratim i da objasnim.






[ negyxo @ 13.03.2007. 19:02 ] @
Kako ide ona statra "jedna slika hiljadu reci" :)



Objasnjenje:

Plave linije oznacavaju ono sto se vec nalazi u tabeli
Crvena oznacava ono sto hocemo da dodamo

start = pocetna vrednost plave linije
end = krajnja vrednost plave linije

insert start = pocetna vrednost crvene linije
insert end = krajnja vrednost crvene linije

Slika se gleda tako sto se za svaku vrednost uporedjuje pocetne i krajnje vrednosti (oni obojeni pravouganici)
Rezultat prva dva pravouganika se racuna sa OR pa se zatim taj rezultat sa AND racuna sa trecim pravouganikom. Mislim da je jasno. Nadam se da nisam pogresio :)

znaci uslov bi trebalo da bude
Code:

...
WHERE (start  > @insertStart OR end > @insertStart) AND start < @endInsert
[ negyxo @ 13.03.2007. 19:08 ] @
E, ovaj Zidar me uvek pretekne
[ srki @ 13.03.2007. 20:32 ] @
Zidar, ovo sa zakazivanjem sam uzeo kao primer, konkretno mi smo imali slucaj sa nekakvim donacijama gde nista nije smelo da bude preko 100% a donacije su mogle poceti od bilo kog trenutka vremena (kada korisnik klikne ok na jedno dugme u web browseru) tako da resenje treba da radi i kada mozes da zakazes u 2h:32m:56.42342s.
[ Zidar @ 13.03.2007. 20:51 ] @
Ako ides na cetvrtu decimalu sekunde, onda se verovatno predlozeni metod ne isplati. :-(
[ chachka @ 13.03.2007. 21:41 ] @
Testirano na PostgreSQL 8.1

Evo upit koji vraca raspoloziv broj slobodnih mesta:
Code:

SELECT p_max_number - COALESCE(MAX(i.maksimalan_broj_musterija), 0)
  FROM (SELECT vr1.start_time AS start_time,
               MIN(vr2.end_time) AS end_time,
               SUM(vr2.broj_musterija_koje_customer_dovodi) AS maksimalan_broj_musterija
          FROM vip_restoran AS vr1
               INNER JOIN
               vip_restoran AS vr2
                 ON (vr1.start_time, vr1.start_time) OVERLAPS (vr2.start_time, vr2.end_time)
         GROUP BY vr1.start_time
         UNION
        SELECT vr1.end_time AS start_time,
               MAX(vr2.end_time) AS end_time,
               SUM(vr2.broj_musterija_koje_customer_dovodi) AS maksimalan_broj_musterija
          FROM vip_restoran AS vr1
               INNER JOIN
               vip_restoran AS vr2
                 ON (vr1.end_time, vr1.end_time) OVERLAPS (vr2.start_time, vr2.end_time)
         GROUP BY vr1.end_time
       ) AS i
 WHERE (i.start_time, i.end_time) OVERLAPS (TIMESTAMP p_start_time, TIMESTAMP p_end_time)

U upit sam ubacio parametre:
p_max_number = maksimalan kapacitet restorana
p_start_time = startno vreme zeljene rezervacije
p_end_time = krajnje vreme zeljene rezervacije
Naravno pre izvrsavanja upita parametri se moraju zameniti konkretnim vrednostima

Upit vraca broj raspolozivih mesta u zadatom intervalu i sa zadatim kapacitetom. Ovaj upit se lako moze ubaciti u trigger ili stored procedure.

Upit se oslanja na standardnu operaciju sa vremenskim intervalima OVERLAPS, s tim da treba napomenuti da se po SQL standardu vremenski intervali uzimaju kao poluotvoreni skupovi, to jest start_time pripada intervalu, ali interval konvergira ka end_time.

Primer za parametre:
p_max_number = 200
p_start_time = '13.03.2007 22:04:21'
p_end_time = '13.03.2007 23:14:15.1400'
upit izgleda:
Code:

SELECT 200 - COALESCE(MAX(i.maksimalan_broj_musterija), 0)
  FROM (SELECT vr1.start_time AS start_time,
               MIN(vr2.end_time) AS end_time,
               SUM(vr2.broj_musterija_koje_customer_dovodi) AS maksimalan_broj_musterija
          FROM vip_restoran AS vr1
               INNER JOIN
               vip_restoran AS vr2
                 ON (vr1.start_time, vr1.start_time) OVERLAPS (vr2.start_time, vr2.end_time)
         GROUP BY vr1.start_time
         UNION
        SELECT vr1.end_time AS start_time,
               MAX(vr2.end_time) AS end_time,
               SUM(vr2.broj_musterija_koje_customer_dovodi) AS maksimalan_broj_musterija
          FROM vip_restoran AS vr1
               INNER JOIN
               vip_restoran AS vr2
                 ON (vr1.end_time, vr1.end_time) OVERLAPS (vr2.start_time, vr2.end_time)
         GROUP BY vr1.end_time
       ) AS i
 WHERE (i.start_time, i.end_time) OVERLAPS (TIMESTAMP '13.03.2007 22:04:21', TIMESTAMP '13.03.2007 23:14:15.1400')

[ srki @ 13.03.2007. 21:48 ] @
@negyxo: Izgleda da nisi lepo procitao zadatak. Za svaki taj pravougaonik imas razlicit broj osoba i onda u svakom trenutku moras da saberes te osobe iz svih pravougaonika koji prolaze kroz taj trenutak da bi izracunao koliko ti je popunjeno mesta u restoranu u tom trenutku.

@Zidar:
Pa da, ja sam ovo radio za neke finansijske transakcije kod kojih se obavezno cuva timestamp. Te transakcije ti daju neku nagradu u poenima i korisnik moze da izabere da odredjen procenat te nagrade da nekome i onda pre nego sto uradi transakciju na internetu on moze da klikne da hoce da radi donacije nekome i onda moramo makar sekunde da pamtimo. Posto donacije mogu da se stave da vaze i vise godina i da pocnu od odredjenog dana onda ne mozemo da idemo sekundu po sekundu da bi smo pokrili nekoliko godina unapred. Cak i da pamtimo po danima opet bi bilo sporo da gledamo za svaki dan unapred nekoliko godina jer se dosta korisnika kaci na bazu u isto vreme i za sve njih mi treba da racunamo da donacije od njih ne smeju da predju 100%.

[Ovu poruku je menjao srki dana 13.03.2007. u 23:38 GMT+1]
[ chachka @ 13.03.2007. 21:52 ] @
Citat:
Zidar: Komentar na datu tabelu vp_restoran: iz dizajna tabele sledi da se posete zakazuju u jednom danu, i da se restoran zatvara najkasnije u ponoc, 24:00.

Nije mi jasno. TIMESTAMP tip podataka podrazumeva datum + vreme. Da li to znaci da je Microsoft i ovde napravio neslanu salu sa nepridrzavanjem standarda?
[ srki @ 13.03.2007. 22:40 ] @
Ma ne, to sam ja lupio vrednosti, naravno da se pamti i dan i vreme. MSSQL nikad ni nisam koristio tako da ne znam kako je to tamo implementirano. Koristim Oracle.
[ srki @ 14.03.2007. 00:36 ] @
Citat:
chachka
upit izgleda:
Code:

SELECT 200 - COALESCE(MAX(i.maksimalan_broj_musterija), 0)
  FROM (SELECT vr1.start_time AS start_time,
               MIN(vr2.end_time) AS end_time,
               SUM(vr2.broj_musterija_koje_customer_dovodi) AS maksimalan_broj_musterija
          FROM vip_restoran AS vr1
               INNER JOIN
               vip_restoran AS vr2
                 ON (vr1.start_time, vr1.start_time) OVERLAPS (vr2.start_time, vr2.end_time)
         GROUP BY vr1.start_time
         UNION
        SELECT vr1.end_time AS start_time,
               MAX(vr2.end_time) AS end_time,
               SUM(vr2.broj_musterija_koje_customer_dovodi) AS maksimalan_broj_musterija
          FROM vip_restoran AS vr1
               INNER JOIN
               vip_restoran AS vr2
                 ON (vr1.end_time, vr1.end_time) OVERLAPS (vr2.start_time, vr2.end_time)
         GROUP BY vr1.end_time
       ) AS i
 WHERE (i.start_time, i.end_time) OVERLAPS (TIMESTAMP '13.03.2007 22:04:21', TIMESTAMP '13.03.2007 23:14:15.1400')

Ovo mi izgleda dobro ali mislim da treba umesto
Code:
...
UNION
SELECT vr1.end_time AS start_time,
       MAX(vr2.end_time) AS end_time,
...

da stoji
Code:
...
UNION
SELECT MAX(vr2.start_time) AS start_time,
       vr1.end_time AS end_time
...


A evo kako sam ja uradio. Selektovao sam sve start_time i end_time u jednu kolonu i sortirao po vremenu. U drugu kolonu sam stavio broj musterija kada upisujem start_time a negativan broj musterija kada upisujem end_time. Onda sam samo isao po redu i sabirao i gledao da taj running sum ne bude veci od 200 ni u jednom trenutku.

Code:

SELECT MAX(ukupan_broj_musterija)
FROM
(  SELECT  sum(broj_musterija_koje_customer_dovodi) over(order by vreme) as ukupan_broj_musterija
  FROM
  (
      SELECT start_time AS vreme, broj_musterija_koje_customer_dovodi
      FROM vip_restoran 
      UNION ALL
      SELECT end_time AS vreme , -broj_musterija_koje_customer_dovodi
      FROM vip_restoran
      UNION ALL
      SELECT p_start_time AS vreme, p_number
      FROM DUAL
      UNION ALL
      SELECT p_end_time AS vreme, -p_number
      FROM DUAL
   )
)

Ako je rezultat manji od 200 onda mozemo da ubacimo taj booking u schedule. Ovo resenje radi u svim bazama koje podrzavaju sql-2003 standard jedino treba promeniti from dual (Oracle) u odgovarajuci ekvivalent u drugim bazama.

[Ovu poruku je menjao srki dana 14.03.2007. u 02:12 GMT+1]
[ chachka @ 14.03.2007. 06:33 ] @
@srki: Interesantno resenje, mada si nam ga rano prezentovao. Jos se o ovoj mozgalici moglo diskutovati.

Sto se tice komentara za izmenu mog upita nisi u pravu. U sustini mog upita je takodje ideja da se pohvataju tacke u kojima se desavaju promene, sto se radi sa:
Code:

SELECT vr1.start_time AS start_time,
...
 UNION
SELECT vr1.end_time AS start_time,
...

Pored ovih tacaka mi je bilo bitno da uhvatim i zavrsetak poslednjeg perioda, sto je uradjeno sa MAX:
Code:

SELECT ...
       MAX(vr2.end_time)
...

Vremenski intervali koje sam dobio pokrivaju interval [MIN(start_time), MAX(end_time)), ali se medjusobno preklapaju. Cini mi se da ovo preklapanje ne utice na krajnji rezultat, pa se vise nisam ni trudio da dobijem intervale koji se ne preklapaju (a da zadrze potpuno pokrivanje intervala [MIN(start_time), MAX(end_time))).

Ono sto tvoja sugerisana izmena nije uspela da obezbedi je da dobijeni intervali potpuno pokriju interval [MIN(start_time), MAX(end_time)).
[ sasas @ 14.03.2007. 09:07 ] @
Evo da i ja probam, rešenje koje radi na MSSQL:

1. Ideja je u tome da u proizvoljnom trenutku možemo saznati broj slobodnih mesta, tako što napravimo sumu svih dolazaka i onda oduzmemo sumu svih odlazaka čije vreme je manje-jednako od traženog. Evo udf koji nam to radi:

Code:

ALTER FUNCTION GetTrenutnoStanje(@trenutak DATETIME) RETURNS INT
AS
BEGIN
    DECLARE @stanje AS INT

    SELECT @stanje = SUM(Promena) FROM 
    (
        SELECT COALESCE(SUM(broj_musterija_koje_customer_dovodi), 0) AS Promena
        FROM vip_restoran WHERE start_time <= @trenutak

        UNION ALL

        SELECT -COALESCE(SUM(broj_musterija_koje_customer_dovodi), 0) AS Promena
        FROM vip_restoran WHERE end_time <= @trenutak
    ) AS Promene

    RETURN @stanje

END


Sad određujemo koji su nam trenutci važni, odnosno u kojima proveravamo stanje u restoranu. Očigledno, trebamo proveriti koliko je ljudi u restoranu na početku traženog intervala, i koliko je na kraju. Dalje nas zanima svaki trenutak unutar traženog intervala.
SQL koji to radi:

Code:

SELECT DISTINCT start_time, dbo.GetTrenutnoStanje(start_time) FROM vip_restoran
WHERE start_time > @start AND start_time < @end --svi trenutci promene unutar traženog intervala

UNION ALL 

SELECT @start, dbo.GetTrenutnoStanje(@start) --početni trenutak

UNION ALL

SELECT @end, dbo.GetTrenutnoStanje(@end) -- krajnji trenutak


Dakle, dobili smo sve "zanimljive" trenutke kad se nešto dogodilo, i stanje u svakom od njih. Sad je potrebno samo proveriti da li su stanja dovoljno mala da dozvoljavaju unos nove grupe, i na osnovu toga izvšiti unos.

Još jedna stvar: ako je rešenje sporo, može se optimizovati na par načina:
1. Uvesti check-pointe, pa se ne mora računati suma za celu tabelu nego od zadnjeg poznatog stanja (na primer u restoranu smo sigurni da je stanje svaki dan u 5:00 = 0, pa nam je to check-point)
2. Kad izračunamo početno stanje, to možemo proslediti kao dodatni parametar funkciji, pa da ona računa samo promenu stanje a ne sve ispočetka.

ss.

[ negyxo @ 14.03.2007. 09:24 ] @
Citat:
srki: @negyxo: Izgleda da nisi lepo procitao zadatak. Za svaki taj pravougaonik imas razlicit broj osoba i onda u svakom trenutku moras da saberes te osobe iz svih pravougaonika koji prolaze kroz taj trenutak da bi izracunao koliko ti je popunjeno mesta u restoranu u tom trenutku.


Mozda nisam dobro procitao ali lepo jesam
Cekaj, zar tebi ne treba da u momentu kada hoce neko da ti dodje u restoran - da se prebroje gosti u tom momentu koji se nalze u restoranu i ako broj gostiju, zajedno sa gostima koji dolaze premasuje kapacitet restorana - onda se ovi novi gosti odbacuju?

Ako je tako onda ne vidim u cemu je problem sa onim resenjem koje sam dao (jedino sto izgleda naivno ).

Evo procedura pa probaj nad podacima ako meni ne verujes

Code:

CREATE PROCEDURE InsertMusterije
    @vip_customerID int,
    @start_time float,
    @end_time float,
    @br_musterija int,
    @max_musterija int
AS
    DECLARE @ukupnoMusterijaZaTrazeniPeriod int

    SELECT 
        @ukupnoMusterijaZaTrazeniPeriod  = SUM(br_musterija) 
    FROM 
        vip_restoran 
    WHERE 
        (start_time > @start_time OR end_time > @start_time) AND start_time < @end_time

    IF @ukupnoMusterijaZaTrazeniPeriod + @br_musterija <= @max_musterija    
        PRINT 'Insert'
    ELSE
        PRINT 'RaiseError(noSpace)'


Mala primedba u vezi procedure
Koristio sam float umesto timestamp, posto timestamp znaci nesto drugo na SQL Serveru.
[ sasas @ 14.03.2007. 10:06 ] @
@negyxo
Pogledaj ovu sliku iz attachmenta. Mislim da tvoje rešenje ne obuhvata taj slučaj koji sam zaokružio, doduše možda grešim. Meni izgleda da ti samo sabereš sve u datom intervalu, ali ne vodiš računa da se u međuvremenu prostor oslobađa, tj. ljudi odlaze i prave mesto za nove. Ili sam ja sve pogrešno razumeo :)


[ negyxo @ 14.03.2007. 10:17 ] @
Prvo probaj resenje. Inace najbolje je da nacrtas i da analiziras. Mozda je moja krivica sto nisam bolje nacrtao ali ako budem imao vremena nacrtacu josa preciznije
[ sasas @ 14.03.2007. 10:39 ] @
Ok, probao, analizirao, i opet mi nije jasno. Pretpostavimo:

1. svaki plavi kvadrat je 10 osoba.
2. crveni kvadrat je 20 osoba.
3. kapacitet restorana je 60 osoba.

Zelenom bojom na slici su označeni oni koji ispunjavaju tvoj uslov. Jesam li u pravu za to?

Njihova suma je 60. Dakle nema mesta za dodatnih 20.

Ali očima kad pogledaš u sliku je jasno da je zauzeto 40 mesta jer se oni koje sam ja dodao ne preklapaju, pa u svakom trenutku imaš maksimalno 40 ljudi, te možemo dodati još 20.

Šta propuštam?

ss.
[ srki @ 14.03.2007. 10:43 ] @
@negyxo
Ne radi ti resenje, a evo i zasto:
ti selektujes sumu svih musterija gde je (start_time > @start_time OR end_time > @start_time) AND start_time < @end_time ato je greska.
Zamisli da u tabeli vec imas nekoga ko je bukirao 100 musterija od 1-2 i drugog koje bukirao 100 musterija od 2-3 i ti sada hoces da napravis novu rezervaciju za 50 musterija od 1-3. Posto oba ona intervala imaju preklapanje sa 1-3 onda ces ti da saberes te cifre i dobices 200 i onda ce ispasti da ne mozes da napravis booking iako mozes.
[ negyxo @ 14.03.2007. 11:05 ] @
@srki
OK, jasno mi je sta hoces da kazes. Ja sam gledao samo slucaj preklapanja, nisam vodio racuna o broju gostiju. Inace resenje sam vec imao, gde sam imao slucaj da se trazi preklapanje vremena za odredjeni period pa sam pomislio da je ovo isti slucaj, ali ocigledno da sam se prevario posto broj gostiju je dodatni uslov koji se ne sme zanemariti.
[ srki @ 14.03.2007. 11:53 ] @
Citat:
chachka: @srki: Interesantno resenje, mada si nam ga rano prezentovao. Jos se o ovoj mozgalici moglo diskutovati.

U pravu si ali nema brige, imam ja jos dosta mozgalica pa cu sledeci put cekati da se vise razvije diskusija.

Citat:
Pored ovih tacaka mi je bilo bitno da uhvatim i zavrsetak poslednjeg perioda, sto je uradjeno sa MAX:
Code:

SELECT ...
       MAX(vr2.end_time)
...

Mislim da treba min a ne max. Zamisli da imas 3 rezervacije:
1) od 12 do 2, 50 osoba
2) od 12 do 4, 50 osoba
3) od 12 do 6, 50 osoba.

Ako ti hoces da napravis rezervaciju br. 4: od 5 do 6, 150 osoba trebalo bi da mozes ali ispasce da ne mozes.

Code:

SELECT vr1.end_time AS start_time,
MAX(vr2.end_time) AS end_time,
SUM(vr2.broj_musterija_koje_customer_dovodi) AS maksimalan_broj_musterija


Kada je vr1.end_time = 2 (od prvog intervala) ovo ce da ti selektuje ostale 2 i izracunace sumu 100 i postavice start_time na 2 a end_time na 6 (umesto na 4). Posle ce taj inteval da ti ima overlap sa intervalom 5-6 a tu je ta suma 100 umesto 50. Zato tamo moras da stavis MIN ili da i jedno i drugo promenis na ono ranije sto sam predlozio (cini mi se da radi, ne mogu sada da probam jer nemam nikakvu bazu ovde)
[ goranvuc @ 14.03.2007. 12:40 ] @
Evo i moj prilog temi. Bila mi je zelja da u "glupom" Accessu (kako ga mnogi zovu) bez namere da bilo sta dokazujem, tj. sa zeljom da na DBMS-u koji ima najmanje mogucnosti resim problem. Takodje sam probao da stvar skroz drugacije postavim, pa sam posao od pretpostavke da nam u stvari treba odgovor na pitanje: Da li postoji neki momenat u nekom intervalu koji ima timeline u preseku sa novim intervalom koji zelimo da bukiramo, a da je u tom momentu broj gostiju + novi broj gostiju > 200 npr.

U nastavku dajem parametrizovani view koji za parametre [OdVremena], [DoVremena] i [BrojNovihGostiju] daje ukupan broj intervala koji su "prebukirani" - naravno, taj broj treba da je 0 ako zelimo da bukiramo odredjeni broj gostiju u odredjenom intervalu.

Dakle Access 2003:
Code:

SELECT Count(*) AS BrojIntervalaUKomSePrekoracujeBrojGostiju 
FROM 
(SELECT start_time, end_time, 
       (SELECT SUM(broj_musterija_koje_customer_dovodi)
        FROM vip_restoran AS Podintervali 
WHERE 
((Podintervali.start_time <= [OdVremena] And Podintervali.end_time > [OdVremena]) OR 
(Podintervali.start_time > [OdVremena] And Podintervali.start_time < [DoVremena])) 
AND 
((Podintervali.start_time <= Intervali.start_time And Podintervali.end_time > Intervali.start_time) OR 
(Podintervali.start_time > Intervali.start_time And Podintervali.start_time < Intervali.end_time))
) AS BrojPrisutnihUIntervalu 
FROM 
(SELECT DISTINCT start_time, end_time
FROM vip_restoran 
WHERE 
(start_time <= [OdVremena] And end_time > [OdVremena]) OR 
(start_time > [OdVremena] And start_time < [DoVremena])
) AS Intervali 
) AS MaksimumGostijuPoIntervalima 
WHERE (BrojPrisutnihUIntervalu + [BrojNovihGostiju]) > 200

Pokusao sam da struktuiram koliko je to moguce.

Uzeta je u obzir i mogucnost da postoje dva ista intervala, a mozete primetiti da u ovom resenju nije koristen nikakav Max, Min i sl.

[Ovu poruku je menjao goranvuc dana 14.03.2007. u 14:06 GMT+1]
[ negyxo @ 14.03.2007. 13:04 ] @
Evo od mene jedna sa kursorima, znam da nije neko elegantno resenje ali gde postoji mogucnost rad sa kursorima, mislim da ce ovo brzo da radi.

Code:

ALTER FUNCTION [dbo].[CanInsert](@start_time float, @end_time float,    @br_musterija int, @max_musterija int)
    RETURNS bit
AS
BEGIN
    DECLARE columnCursor CURSOR READ_ONLY FOR    
    SELECT
        start_time as startEnd, br_musterija, 1 AS direction
    FROM        
        (SELECT * FROM vip_restoran 
         WHERE (start_time > @start_time OR end_time > @start_time) AND start_time < @end_time)
    AS T 

    UNION ALL

    SELECT
        end_time AS startEnd, br_musterija, 0 AS direction
    FROM        
        (SELECT * FROM vip_restoran 
         WHERE (start_time > @start_time OR end_time > @start_time) AND start_time < @end_time)
    AS T 
    
    ORDER BY startEnd, direction
    
    DECLARE @ukupanBrojMusterija int,
            @startEnd float,
            @musterija int,
            @direction tinyint,
            @canInsert bit

    SET @ukupanBrojMusterija = 0    
    SET @canInsert = 1

    OPEN columnCursor
    FETCH NEXT FROM columnCursor INTO @startEnd, @musterija, @direction
    WHILE (@@fetch_status <> -1)
    BEGIN
        IF (@@fetch_status <> -2)
        BEGIN    
            IF @direction = 1
                SET @ukupanBrojMusterija = @ukupanBrojMusterija + @musterija
            ELSE
                SET @ukupanBrojMusterija = @ukupanBrojMusterija - @musterija

            IF @ukupanBrojMusterija + @br_musterija > @max_musterija
            BEGIN
                SET @canInsert = 0
                BREAK
            END
        END
        FETCH NEXT FROM columnCursor INTO @startEnd, @musterija, @direction
    END
    
    CLOSE columnCursor
    DEALLOCATE columnCursor

    RETURN @canInsert
END


E ako sam i sad pogresio, vise se ne javljam
[ chachka @ 14.03.2007. 13:58 ] @
Srki, u pravu si da moje resenje nije ispravno. A tema se rasplamsala bez obzira na tvoje elegantno resenje.

Mrzelo me je da odredujem tacan kraj perioda jer sam mislio da nema uticaja na konacan rezultat, ali sam se prevario.

Da bih istrajao u svojoj ideji upotrebe OVERLAPS operacije, ipak moram odrediti skup perioda ciji je presek prazan skup, a unija skup [MIN(start_time), MAX(end_time)).

To dodatno komplikuje upit, koji sada izgleda:
Code:

SELECT p_max_number - COALESCE(MAX(i.maksimalan_broj_musterija), 0)
  FROM (SELECT i3.start_time AS start_time,
               i3.end_time AS end_time,
               COALESCE(SUM(vr.broj_musterija_koje_customer_dovodi), 0) AS maksimalan_broj_musterija
          FROM (SELECT MAX(i2.point_in_time) AS start_time, i1.point_in_time AS end_time
                  FROM (SELECT start_time AS point_in_time
                          FROM vip_restoran
                         UNION
                        SELECT end_time AS point_in_time
                          FROM vip_restoran
                       ) AS i1
                       INNER JOIN
                       (SELECT start_time AS point_in_time
                          FROM vip_restoran
                         UNION
                        SELECT end_time AS point_in_time
                          FROM vip_restoran
                       ) AS i2
                       ON i1.point_in_time > i2.point_in_time
                 GROUP BY i1.point_in_time
               ) AS i3
               LEFT OUTER JOIN
               vip_restoran AS vr
               ON (i3.start_time, i3.end_time) OVERLAPS (vr.start_time, vr.end_time)
         GROUP BY i3.start_time, i3.end_time
       ) AS i
 WHERE (i.start_time, i.end_time) OVERLAPS (TIMESTAMP p_start_time, TIMESTAMP p_end_time)

Ovaj upit je proveren na PostgreSQL 8.1, koji ne podrzava OVER.

Kako izgleda Srkijev upit bez upotrebe OVER-a, to jest sta taj OVER u pozadini radi?
[ goranvuc @ 14.03.2007. 14:22 ] @
Koliko vidim, ovde vec ima gomila razlicitih resenja, sto je super, ali ko to proverava osim samih autora? Ja sam probao sve moguce varijante test podataka za svoje resenje (koje je usput receno univerzalno, tj. prolazi u vecini sistema, naravno, mislim na sam SQL izraz, a da se ulazni parametri obradjuju od sistema do sistema) i uvek mi vraca ispravne podatke, tako da sam spreman da nagradim simbolicnom novcanom nagradom od 1000 dinara prvog korisnika koji nadje skup podataka za koje moj upit ne vraca ispravan rezultat, a cifra nije losa s obzirom da moze da se zaradi za par minuta.

Koga vredja cifra ne mora da ucestvuje.
[ negyxo @ 14.03.2007. 21:05 ] @
Ja sam ovu funkciju sto sam postovao testirao i mislim da radi ispravni, sa algoritamskog aspekta trebalo bi da je uredu. Sad ono sto mi je zao - je sto nam srki nije ostavio vise vremena, u zurbi da resim nisam ni citao tudja resenja ali sada kada sam pogledao vidim da je srkijevo resenje dosta slicno, ili bolje receno moje resenje dostas slicno srkijevom. E sad, problem na SQL Serveru je sto OVER funkcija ne postoji za agregatne f-je nego samo za ranking f-je. Iskreno ne znam sta radi ta f-ja ali s obzirom da sam pravio resenje preko kursora pretpopstavljam da sumira po koracima.
[ srki @ 15.03.2007. 02:21 ] @
goranvuc, je l' si siguran da ovo tvoje radi?

Probaj ovo:
rezervacija 1) od 2 do 7, 100 gostiju
rezervacija 2) od 3 do 4, 60 gostiju
rezervacija 3) od 5 do 6, 60 gostiju

Mi hocemo da napravimo novu rezervaciju
Nova rezervacija: od 1 do 8, 40 gostiju

Tu rezervaciju mozemo da napravimo ali mislim da tvoj kod vraca da postoji interval u kome ne mozemo to da napravimo.
[ goranvuc @ 15.03.2007. 06:33 ] @
@srki, svaka cast!

Evo ja sad gledam, izgleda da je jutro uvek "pametnije" od veceri. Hvala ti puno na trudu, u mom resenju postoji jedan nepokriven slucaj: kada podinterval (interval koji se delimicno preseca sa novim intervalom) sadrzi i sam podintervale koji medjusobno nemaju presek, sto nas dovodi do mogucnosti da taj slucaj postoji i u "dubljim nivoima" tj. da se prenosi na podintervale od podintervala, tako da bi resenje u mom slucaju moralo da bude rekurzivno.

Hvala ti na trudu, dao si i ostalima primer test podataka koje moraju da savladaju. Konkurs zavrsen, salji mi na PP adresu na koju da saljem simblicnu nagradu, a ja idem "da se pokrijem s usima"

Pozdrav!
[ srki @ 15.03.2007. 06:43 ] @
Nije mi bilo tesko da nadjem primer koji ne radi jer sam vec zbog tog problema na poslu napravio mali milion test primera (da bih dokazao da algoritmi kolega ne rade ) i da bih posle smislio ono jednostavno resenje
Petsto dinara zadrzi za sebe (zbog truda) a ostalih 500 dinara daj u dobrotvorne svrhe ili pokloni nekom prijatelju/rodjaku kome treba.
Druga opcija je da sve zadrzis za sebe ali da odes u zavod za transfuziju i das krv. Hvala
[ goranvuc @ 15.03.2007. 07:41 ] @
Krv svakako dajem dvaput godisnje, imas onda pice kad svratis u Srbiju (sad tek videh odakle se javljas).

Pozdrav!

Vidim da si prilicno pripremljen postavio zadatak, a nas si pustio da se "mozgolomimo"
[ srki @ 15.03.2007. 10:37 ] @
Znaci castis pivom za 6 meseci, valjda cu imati vremena da svratim do Novog Sada mada mi je krivo sto ne mogu ranije da dodjem zbog Exita, to pre nisam propustao.

Inace ovaj problem je jako cest i resenje je vrlo korisno. Npr. kod prodavanja karata za voz, autobus ili avion, tu imamo umesto vremena redne brojeve stanica i onda kada npr. avion leti od Aucklanda do Londona a slece u Melburnu, Singapuru, Dubaiju i Frankfurtu, neko hoce da uzme kartu od Melburna do Singapura, drugi hoce od Melburna do Dubaija, neko hoce od Singapura do Frankfurta i sl. Tu bi mogao da se primeni ovaj algoritam jer znamo kapacitet aviona i onda tako preletimo kroz ostale rezervacije da bismo videli da li mozemo da napravimo rezervaciju za odredjenu relaciju. Mada ovo je ipak korisnije za vozove koji putuje kroz nekoliko zemalja i obicno staju u svim vecim i nekim manjim gradovima.

[Ovu poruku je menjao srki dana 15.03.2007. u 12:51 GMT+1]
[ srki @ 15.03.2007. 21:15 ] @
Da, red bi bio da napisem resenje bez upotrebe analitickih funkcija jer nemaju sve baze tu mogucnost.

Code:

SELECT MAX(ukupan_broj_musterija)
FROM
(  SELECT  sum(broj_musterija_koje_customer_dovodi) as ukupan_broj_musterija
  FROM
  (
      SELECT start_time AS vreme1, broj_musterija_koje_customer_dovodi
      FROM vip_restoran 
      UNION ALL
      SELECT end_time AS vreme1 , -broj_musterija_koje_customer_dovodi
      FROM vip_restoran
      UNION ALL
      SELECT p_start_time AS vreme1, p_number
      FROM DUAL
      UNION ALL
      SELECT p_end_time AS vreme1, -p_number
      FROM DUAL
   ),
  (
      SELECT start_time AS vreme2
      FROM vip_restoran 
      UNION ALL
      SELECT end_time AS vreme2
      FROM vip_restoran
      UNION ALL
      SELECT p_start_time AS vreme2
      FROM DUAL
      UNION ALL
      SELECT p_end_time AS vreme2
      FROM DUAL
   )
   WHERE vreme1<=vreme2
   GROUP BY vreme2
)


Nazalost ovo resenje je dosta sporije od resenja koje se dobija koriscenjem analitickih funkcija. MS SQL 2005 podrzava SUM() OVER() sto je ANSI dodatak na sql-99 tako da steta sto ostale baze (osim ORACLE) to ne podrzavaju.

[Ovu poruku je menjao srki dana 17.03.2007. u 00:45 GMT+1]
[ chachka @ 17.03.2007. 00:03 ] @
Standing ovations!!!
[ srki @ 17.03.2007. 07:43 ] @
Dovoljno je bilo samo da se u google-u otkuca: sql running total. Prva 3 sajta nude resenje bez analitickih funkcija. Inace bio je jedan rad u kome je dokazano da analiticke funkcije ne omogucavaju nista novo i sve sto moze da se uradi sa analitickim funkcijama moze i bez njih. Analiticke funkcije samo olaksavaju i ubrzavaju i pisanje i izvrsavanje naredbi ali ne nude nista novo.