Pomoc - Szukaj - Użytkownicy - Kalendarz
Pełna wersja: Problem z pobraniem ścieżki w drzewie kategorii
Forum PHP.pl > Forum > Bazy danych > PostgreSQL
phpion
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
Sedziwoj
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/sta...ql-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).
phpion
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.
Sedziwoj
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ł
phpion
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
Sedziwoj
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.
phpion
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ć...
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.