Drukowana wersja tematu

Kliknij tu, aby zobaczyć temat w orginalnym formacie

Forum PHP.pl _ PostgreSQL _ Problem z pobraniem ścieżki w drzewie kategorii

Napisany przez: phpion 24.07.2008, 08:42:12

Witam,
mam struktruę tabeli:
p_category: id, parent_id, name
czyli w zasadzie standardzik. Chciałbym napisać funkcję, która zwróci mi ścieżkę od danej kategorii do korzenia. Mam coś takiego:

  1. CREATE OR REPLACE FUNCTION f_get_category_path(category_id integer, shop_id integer)
  2. RETURNS p_category AS $BODY$DECLARE
  3. r p_category%ROWTYPE;
  4. BEGIN SELECT * INTO r FROM p_category WHERE id=$1;
  5.  
  6. IF (r.parent_id > 0) THEN
  7. -- SELECT * FROM f_get_category_path(r.parent_id, $2);
  8. END IF;
  9.  
  10. RETURN r;
  11. END;$BODY$
  12. LANGUAGE 'plpgsql' VOLATILE
  13. COST 100;

Mam problem z rekurencyjnym wywołaniem tej funkcji. Jeśli odkomentuję linijkę w warunku otrzymuję:
Kod
ERROR:  query has no destination for result data
HINT:  If you want to discard the results of a SELECT, use PERFORM instead.
CONTEXT:  PL/pgSQL function "f_get_category_path" line 7 at SQL statement
SQL statement "SELECT * FROM f_get_category_path( $1 ,  $2 )"
PL/pgSQL function "f_get_category_path" line 7 at SQL statement

Jak to poprawnie zapisać?

pion

Napisany przez: Sedziwoj 24.07.2008, 12:59:40

Jak dobrze pamiętam, to PG nie lubi jak mus się wstawia SQL z parametrami... spróbuj coś w tym stylu:

  1. EXECUTE 'SELECT * FROM f_get_category_path('||r.parent_id||', '||$2||');'

Ogólnie o PREPARE:
http://www.postgresql.org/docs/current/static/sql-prepare.html

Do tego moim zadaniem powinieneś użyć LOOP, będzie to moim zdaniem lepsze rozwiązanie, skoro jedynie będzie jedna gałąź rekurencji do tego z jednym elementem do zapamiętania.
Trzeba zakładać, że nie pojawi się błąd pętli, bo to od razu zabije bazę (nie zależnie czy algorytm będzie rekurencyjny czy iteracyjny).

Napisany przez: phpion 24.07.2008, 13:58:26

Dzięki za odpowiedź. Teraz ciało funkcji wygląda w ten sposób:

  1. DECLARE
  2. r p_category%ROWTYPE;
  3. BEGIN SELECT * INTO r FROM p_category WHERE id=$1;
  4.  
  5. IF (r.parent_id IS NOT NULL AND r.parent_id > 0) THEN
  6. EXECUTE 'SELECT * FROM f_get_category_path('||r.parent_id||', '||$2||');';
  7. END IF;
  8.  
  9. RETURN r;
  10. END;

Wywołanie np.
  1. SELECT * FROM f_get_category_path(7, 1);

powinno zwrócić (wg moich zamierzeń) 3 rekordy, natomiast zwraca tylko jeden (ten o id=7 czyli parametrze przekazanym w funkcji). Wygląda jakby albo nie wchodził w ogóle do warunku (co jest mało prawdopodobne bo na pewno jest spełniony) albo nie wyświetlał rezultatów kolejny wywołań. Jeśli mógłbyś mi pomóc w tej kwestii byłbym bardzo wdzięczny.

Napisany przez: Sedziwoj 24.07.2008, 15:18:27

Może nie za piękne... Ważne w deklaracji "SETOF "

  1. CREATE OR REPLACE FUNCTION f_get_category_path(category_id integer, shop_id integer)
  2. RETURNS SETOF p_category AS $BODY$
  3. DECLARE
  4. r p_category%ROWTYPE;
  5. aid integer;
  6. BEGIN IF category_id IS NULL THEN RETURN; END IF;
  7. aid := category_id;
  8. LOOP
  9. FOR r IN SELECT * FROM p_category WHERE id = aid
  10. LOOP
  11. RETURN NEXT r;
  12. END LOOP;
  13. EXIT WHEN NOT r.parent_id > 0; -- Nie wiem czy masz null, czy może błędnie 0 w tym polu jak nie ma rodzica.
  14. aid := r.parent_id;
  15. END LOOP;
  16. RETURN;
  17. END;
  18. $BODY$
  19. LANGUAGE 'plpgsql' VOLATILE;


EDIT: parę poprawek
EDIT2: poprawiam to co napisał phpion, aby był poprawny przykład, jakby ktoś sszukał

Napisany przez: phpion 24.07.2008, 16:30:16

Działa wyśmienicie. Musiałem poprawić tylko jedną linijkę:

  1. FOR r IN SELECT * FROM p_category WHERE id = aid

bo wywalało błędy. Naprawdę ogromne dzięki! smile.gif

PS: tworzę "abstrakcyjną" kategorię ROOT o id=0 i parent_id=NULL, natomiast później wszystkie kategorie główne mają ustawione parent_id=0

Napisany przez: Sedziwoj 24.07.2008, 17:02:22

Ja zazwyczaj miałem las, Ty chcesz mieć drzewo, to już nie nazwę błędem biggrin.gif Po prostu inna koncepcja (a mi się przypomniało jak było w bazie -1 jak nie było powiązania... i tylko dla tego wspomniałem)
Ciągle mam wrażenie że da się to lepiej napisać, ale bym musiał się tym pobawić, do tej pory nie miałem potrzeby robienia takich funkcji.

Napisany przez: phpion 15.12.2008, 11:58:40

Odświeżam temat: mam problem z modyfikacją wyżej podanego rozwiązania. Aktualnie mam tabele:

category: id, parent_id, position
locale: id, code
category_i18n: category_id, locale_id, name

i próbuję pobrać ścieżkę kategorii czyli np. Podkategoria -> Kategoria. Stworzyłem widok:

  1. CREATE OR REPLACE VIEW shp_view_category AS SELECT a.category_id, b.parent_id, a.name, c.code AS locale_code
  2. FROM shp_category_i18n a
  3. LEFT JOIN shp_category b ON b.id = a.category_id
  4. LEFT JOIN shp_locale c ON c.id = a.locale_id;

oraz zmodyfikowałem funkcję tak by (teoretycznie) działała poprawnie:
  1. CREATE OR REPLACE FUNCTION f_get_category_path(category_id integer, locale character varying)
  2. RETURNS SETOF shp_view_category AS $BODY$DECLARE
  3. r shp_view_category%ROWTYPE;
  4. aid integer;
  5. BEGIN IF $2 IS NULL THEN
  6. RETURN;
  7. END IF;
  8.  
  9. aid := $2;
  10.  
  11. LOOP
  12. FOR r IN SELECT * FROM shp_view_category WHERE category_id=aid AND locale_code=$2
  13. LOOP
  14. RETURN NEXT r;
  15. END LOOP;
  16.  
  17. EXIT WHEN r.parent_id=0 OR r.parent_id IS NULL;
  18.  
  19. aid := r.parent_id;
  20. END LOOP;
  21.  
  22. RETURN;
  23. END;$BODY$
  24. LANGUAGE 'plpgsql' VOLATILE
  25. COST 100
  26. ROWS 1000;

Wywłouję ją:
  1. SELECT * FROM f_get_category_path(12, 'pl_PL');

Nie działa ona jednak poprawnie. W wyniku otrzymuję wszystkie rekordy z widoku, które posiadają tłumaczenia dla kategorii 12 w wersji 'pl_PL', a nie tylko te wchodzące w skład ścieżki. Co robię źle :|

Proszę o pomoc,
pion

PS: wiem, że lepiej to zrobić z użyciem RECORD a nie tworzyć specjalnie po to widok ale na razie chciałbym się uporać z tym aby to po prostu zaczęło działać...

Powered by Invision Power Board (http://www.invisionboard.com)
© Invision Power Services (http://www.invisionpower.com)