1

Temat: pobieranie danych dla jednego rekordu z innego wczesniejszego rekordu

problem przedstawia sie nastepujaco.
mam dwie tabele:
- jedna przechowujaca szczegolowe dane nt kolejnych dat
- druga przchowujaca szczegolowe dane sprzedanego produktu w tym m.in. date sprzedazy tego produktu

napisalem taki sql aby pobrac dane sprzedazy zagregowane na konkretny dzien

SELECT sum(b.saldo) as saldo, day_of_year,week_of_year,month_of_year,quarter,year, count(b.id) as count, de.year as join_year, de.month_of_year as join_month_of_year
                            FROM data.balance as b
                            INNER JOIN date de ON b.valid_from_id = de.id AND de.day_of_week != 6 AND de.day_of_week != 7
                            where b.ghost = false    AND ( b.valid_from_id >= (SELECT public.get_date_data_id('2011-01-01')) AND b.valid_from_id <= (SELECT public.get_date_data_id('2011-01-31')) ) AND product_3_id IN ( 59,58,60 ) AND b.product_group_id IN ( 3 )   
                            GROUP BY day_of_year,week_of_year,month_of_year,quarter,year ,  de.month_of_year , de.year
                            order by day_of_year

i dziala to prawidlowo.
teraz jednak potrzebuje pobrac ta sama sprzedaz tyle ze ma byc to wykonane tak by np sprzedaz z 3.01 byla przypisana do daty 4.01, sprzedaz z 4.01, przypisana do daty 5,01, sprzedaz z 5.01 przypisana do daty 6.01 itd...
napisalem wiec sql ktory teoretycznie to wykonuje:

SELECT sum(b.saldo) as saldo_prev, day_of_year,week_of_year,month_of_year,quarter,year,
         count(b.id) as count_prev, de.year as join_year, de.month_of_year as join_month_of_year
        FROM data.balance as b
        INNER JOIN (SELECT day_of_year,week_of_year,month_of_year,quarter,year FROM date where date >= '2011-01-01' AND date <= '2011-01-31' group by day_of_year,week_of_year,month_of_year,quarter,year) as de ON  (SELECT day_of_year from date where id = b.valid_from_id) =  de.day_of_year - 1
        where b.ghost = false   AND ( b.valid_from_id >= (SELECT public.get_date_data_id((SELECT public.get_date_prev('2011-01-01', 'day', 'from'))::text)) AND b.valid_from_id <= (SELECT public.get_date_data_id((SELECT public.get_date_prev('2011-01-31', 'day','to'))::TEXT)) ) AND product_3_id IN ( 59,58,60 ) AND b.product_group_id IN ( 3 )
        GROUP BY day_of_year,week_of_year,month_of_year,quarter,year ,  de.month_of_year , de.year
        order by day_of_year

teoretycznie, gdyz z racji tego ze w sobote, i niedz nie ma sprzedazy sql zwraca w niektorych miejscach bledne dane. np nie zwrca wcale sprzedazy z 10,01 gdzie powinien zwrocic sprzedaz z 7.01, za to ta wlasnie sprzedaz zwraca 8.01 kiedy to zadnej sprzedazy byc nie ma prawa., podobnie dzieje sie z kolejnymi roznicami gdzie sprzedaz piatkowa powinna pojawic sie w poniedzialek a sql zwraca mi ja w sobote.

znajdzie sie tutaj ktos na tyle madry aby mi pomoc rozgrysc ten problem?

2

Odp: pobieranie danych dla jednego rekordu z innego wczesniejszego rekordu

SELECT
    sum(saldo) AS saldo,
    extract(doy from valid_from_date) AS day_of_year,
    extract(week from valid_from_date) AS week_of_year,
    extract(month from valid_from_date) AS month_of_year,
    extract(quarter from valid_from_date) AS quarter,
    extract(year from valid_from_date) AS year,    
    count(id) AS count
FROM
    balance
WHERE
    (valid_from_date BETWEEN '2011-01-01' AND '2011-01-31') AND
    extract(dow from valid_from_date) NOT IN (0, 6) AND -- vide dokumentacja
    ghost = false AND
    product_3_id IN (59, 58, 60) AND
    product_group_id IN (3)
GROUP BY
    valid_from_date
ORDER BY
    day_of_year;

Ostatnio edytowany przez gszpetkowski (2011-08-10 14:10:02)

3

Odp: pobieranie danych dla jednego rekordu z innego wczesniejszego rekordu

4

Odp: pobieranie danych dla jednego rekordu z innego wczesniejszego rekordu

CREATE OR REPLACE FUNCTION getNextBusinessDay(date)
RETURNS date AS $$
    SELECT
        CASE date_part('isodow', $1)
            WHEN 5 THEN CAST ($1 + interval '3 days' AS date) -- piątek
            WHEN 6 THEN CAST ($1 + interval '2 days' AS date) -- sobota
            ELSE CAST ($1 + interval '1 days' AS date) -- pozostałe
        END;
$$ LANGUAGE SQL;

Ostatnio edytowany przez gszpetkowski (2011-08-10 15:32:03)

5

Odp: pobieranie danych dla jednego rekordu z innego wczesniejszego rekordu

odlozylem temat na bok ale przyszla w koncu pora ogarnac go i tak...
troche sie nameczylem zeby przepisac swoj skrypt na to co zaproponowales i... DZIALA smile

mam tylko jeszcze male "ale".  twoja funkcja getNaextBusinessDay() nie oblsluguje swiat. napisalem wiec mala funkcje

CREATE OR REPLACE FUNCTION isdayfree(date)
  RETURNS boolean AS
$BODY$
begin    

if exists (select distinct free_day from days_free where date_part('day',free_day)=date_part('day', $1) 
    and date_part('month',free_day)=date_part('month', $1) and date_part('year',free_day)=date_part('year', $1)) 
then return true;
else return False;
end if;

end;
$BODY$
LANGUAGE plpgsql VOLATILE
COST 100;

ktora sprawdza czy dany dzien jest sietem wolnym od pracy czy tez nie. mam jednak problem z zaimplementowaniem tego do twojej funkcji tak by dzialalo poprawnie. probowalem ja modyfikowac na dwa sposoby:

CREATE OR REPLACE FUNCTION getnextbusinessday(date)
  RETURNS date AS
$BODY$
    SELECT
        CASE 
            WHEN date_part('isodow', $1)=5 THEN CAST ($1 + interval '3 days' AS date) -- piątek
            WHEN date_part('isodow', $1)=6 or isDayFree($1)=true THEN CAST ($1 + interval '2 days' AS date) -- sobota
            when date_part('isodow', $1) not in (5,6) and isDayFree($1)=false then CAST ($1 + interval '1 days' AS date) -- pozostałe
        END;
$BODY$
  LANGUAGE sql VOLATILE
  COST 100;

lub

CREATE OR REPLACE FUNCTION getnextbusinessday(date)
  RETURNS date AS
$BODY$begin

if not isDayFree($1) 
then 
return 
        CASE date_part('isodow', $1)
        WHEN 5 THEN CAST ($1 + interval '3 days' AS date) -- piątek
        WHEN 6 THEN CAST ($1 + interval '2 days' AS date) -- sobota
        ELSE CAST ($1 + interval '1 days' AS date) -- pozostałe
        END;
else return  CAST ($1 + interval '2 days' AS date);
end if;

end; $BODY$
  LANGUAGE plpgsql VOLATILE
  COST 100;

obie te modyfikacje dzialaja z tym ze w wyniku dostaje:

saldo_prev | day_of_year | week_of_year | month_of_year | quarter |  year
-----------+-------------+--------------+---------------+---------+---------
55396551   |            4|             1|              1|        1|    2011
18768179   |            5|             1|              1|        1|    2011
18799097   |            6|             1|              1|        1|    2011
18801235   |            8|             1|              1|        1|    2011
18865999   |           10|             2|              1|        1|    2011

a jak wiadomo to 06.01 a nie 07.01 byl swietem wolnym od pracyw zwiazku z czym dla 07.01 wartoscia salda jest oryginalna wartosc z 05.01, a 06.01 i 08.01 nie powinien sie pojawic

EDIT: po dokladniejszych testach musze stwirdzic ze twoj pomysl dziala nie do konca poprawnie, bo o ile jesli dane agreguje dziennie to jest wszystko w porzatku (pomijajac juz nawet te swieta) to w przypadku gdy chce dane agragowac tygodniowo, miesiecznie, kwartalnie lub rocznie jest problem. agregacje czasowe sa tworzone w php i wrzucane do sql za pomoca zmiennych ktore generuja dopowiednie fragmenty SELECTA. i o ile poradzielm sobie z uwzglednieniem twoich propozycji w wylistowaniu kolum i grup w tym selekcie o tyle nie wiem jak zmienic twoja funkcje tak by przesuwala daty o wlasciwe interwaly hmm

bardziej obrazowo mowiac.
w agregacie tygodniowej: saldem 2 tygodni powinno byc saldo pierwszego, saldem trzeciego tgodnia saldo drugiego itd
w agregacie miesieczne: saldem stycznia bedzie saldo grudnia, saldem lutego bedzie saldo stycznia, saldem marca bedzie saldo lutego id
podobnie rzecz bedzie sie miala dla agregaty kwartalnej czy rocznej

wszystko analogicznie jak w przypadku agregaty dziennej o ktorej dotad dyskutowalismy.

Ostatnio edytowany przez norris_85 (2011-08-24 13:39:01)

6

Odp: pobieranie danych dla jednego rekordu z innego wczesniejszego rekordu

Proponowałbym trochę inaczej rozpisać te dwie funkcje tj.:

/* Zwraca prawdę jeżeli dzień jest wolny od pracy,
   a fałsz w przeciwnym wypadku */
CREATE OR REPLACE FUNCTION isdayfree(date)
RETURNS boolean AS $BODY$
BEGIN   
    IF EXISTS (SELECT free_day FROM days_free WHERE free_day = $1) -- zakładając, że free_day ma typ date
        OR date_part('isodow', $1) IN (6, 7)
    THEN
        RETURN true;
    ELSE
        RETURN False;
    END IF;
END;
$BODY$
LANGUAGE plpgsql;

/* Zwraca następny dzień biznesowy */
CREATE OR REPLACE FUNCTION getNextBusinessDay(date)
RETURNS date AS $BODY$
DECLARE
    resultDate date := $1;
BEGIN
    resultDate := resultDate + 1;
    WHILE isdayfree(resultDate) = true LOOP
        resultDate := resultDate + 1;
    END LOOP;
    RETURN resultDate;
END;
$BODY$
LANGUAGE plpgsql;

Teraz w funkcji getNextBusinessDay znajduje się zwykła pętla typu DO-WHILE, która zatrzymuje się w momencie znalezienia następnego dnia biznesowego (opiera się na funkcji isdayfree, dlatego zupełnie jej nie interesuje w jaki sposób dzień jest ustalany jako wolny od pracy).

Uproszczone zapytanie zmienia się w jednym miejscu tj. date_part('isodow', valid_from_date) NOT IN (6, 7) na isdayfree(valid_from_date) = false:

SELECT
    sum(saldo) AS saldo,
    date_part('doy', next_day) AS day_of_year,
    date_part('week', next_day) AS week_of_year,
    date_part('month', next_day) AS month_of_year,
    date_part('quarter', next_day) AS quarter,
    date_part('year', next_day) AS year,    
    count(id) AS count
FROM 
    balance a INNER JOIN (SELECT id, getNextBusinessDay(valid_from_date) AS next_day FROM balance) b
    USING (id)
WHERE
    (next_day BETWEEN '2011-01-01' AND '2011-01-31') AND
    isdayfree(valid_from_date) = false AND
    ghost = false AND
    product_3_id IN (59, 58, 60) AND
    product_group_id IN (3)
GROUP BY
    next_day
ORDER BY
    day_of_year;

Dla wcześniej rozpisanego zestawu wynik zapytania to:

 saldo | day_of_year | week_of_year | month_of_year | quarter | year | count
-------+-------------+--------------+---------------+---------+------+-------
  2600 |           4 |            1 |             1 |       1 | 2011 |     3
   400 |          10 |            2 |             1 |       1 | 2011 |     1
(2 rows)

Jak widać dzień 6 stycznia został pominięty w zestawieniu. Powinno to także działać poprawnie dla innych zestawów. Swoją drogą myślałem, że może w jakiś sposób lokalizacja posiada informacje o świętach (nie trzeba by wtedy robić dedykowanej tabeli), ale najwyraźniej nie ma czegoś takiego i trzeba to robić samodzielnie.

Ostatnio edytowany przez gszpetkowski (2011-08-24 13:43:28)

7

Odp: pobieranie danych dla jednego rekordu z innego wczesniejszego rekordu

no niestety, jedyne rozwiazania dot swiat na jakie ja sie natknalem to wlasnie tabela albo rozne funkcje w php kombinujace z datami. dla moich potrzeb bardziej optymalna wydaje sie byc tabela gdyz cale zapyanie jest generowane do sql

btw najwyrazniej podczas gdy ty pisales odp ja pisalem edit do wczesniejszego posta tongue moglbys sie do niego ustosunkowac? bo nawet po ostatnich poprawkach testy pokazaly ze niestety to nie dziala dla wyzszych agregat czasowych niz dzienna