Ik heb een dilemma voor een tabel waarbij de volgorde uitmaakt, elke rij heeft een precieze plek in de tabel. Hoe kan je die rijen zo goed mogelijk onderhouden en bijwerken? (Herorderen van de tabel?). Je wil namelijk natuurlijk het liefst de kolom 'order' (integer), op unique zetten, en het liefst dat de order precies van 0 tot x loopt zonder gaten er tussen, maar dit maakt mutaties in een tabel een lastige zaak.

Ik heb het nu redelijk werkend met procedures.

language_get_next_ordinal(): de volgende ordinal in een table. (wellicht enigzins gevaarlijk bij hoge volume inserts, maar dit is niet aan de orde).

language_move(id, x): Verplaats record met id id naar positie x

language_reorder(): Haal gaps uit de tabel. (Bijvoorbeeld na een delete of na een update).



CREATE FUNCTION language_get_next_ordinal() RETURNS integer
    AS $$
 DECLARE
    p_next_ordinal integer;
 BEGIN
 	SELECT count(id)
 	  INTO p_next_ordinal
 	FROM vdven.language;
 	RETURN p_next_ordinal;
 END;
$$
    LANGUAGE plpgsql;



CREATE FUNCTION language_move(f_id integer, f_new_pos integer) RETURNS integer
    AS $$
 DECLARE
     p_rec record;
     p_old_pos integer;
     p_max_pos integer;
     p_done integer := 0;
 BEGIN
 	SELECT ordinal
 	INTO p_old_pos
 	FROM vdven.language
 	WHERE id = f_id;
 	
 	SELECT COUNT(id)-1 
 	INTO p_max_pos
 	FROM vdven.language;
 	
 	IF p_old_pos = f_new_pos THEN
		RETURN 0;
	END IF;

 	IF p_max_pos < f_new_pos THEN
 		RETURN -1;
 	END IF;
	
 	UPDATE vdven.language
 	SET ordinal = p_max_pos+1
 	WHERE id = f_id;
 	
 	IF p_old_pos > f_new_pos THEN
	    FOR p_rec IN 
	    		SELECT id,ordinal 
	    		FROM vdven.language 
	    		WHERE ordinal >= f_new_pos  
	    		AND ordinal < p_old_pos
	    		ORDER BY ordinal DESC
	    		LOOP
			UPDATE vdven.language 
			SET ordinal = ordinal+1
			WHERE id = p_rec.id;
			p_done := p_done+1;
	    END LOOP;
	ELSEIF p_old_pos < f_new_pos THEN
	    FOR p_rec IN SELECT id,ordinal FROM vdven.language WHERE ordinal <= f_new_pos  AND ordinal > p_old_pos ORDER BY ordinal ASC LOOP
			UPDATE vdven.language 
			SET ordinal = ordinal-1
			WHERE id = p_rec.id;
			p_done := p_done+1;
	    END LOOP;
	ELSE
		RETURN -1;
	END IF;
	
    UPDATE vdven.language
    	SET ordinal = f_new_pos
    	WHERE id = f_id;
    RETURN p_done;
END;
$$
    LANGUAGE plpgsql;


CREATE FUNCTION language_reorder() RETURNS integer
    AS $$
 DECLARE
     p_rec record;
     p_int integer := 0;
     p_done integer :=  0;
 BEGIN
    FOR p_rec IN SELECT id,ordinal FROM vdven.language ORDER BY ordinal ASC LOOP
		IF p_int <> p_rec.ordinal THEN
			UPDATE vdven.language 
			SET ordinal = p_int
			WHERE id = p_rec.id;
			p_done := p_done+1;
		END IF;
		p_int := p_int+1;
    END LOOP;
    RETURN p_done;
END;
$$
    LANGUAGE plpgsql;


Dit is een realtief complexe oplossing voor een simpel probleem, weet iemand in postgres of sql wellicht een simpelere oplossing?
Ja hoor, kan prima, je moet alleen zorgen voor wat extra foutafhandeling in de sp omdat er meer fout kan gaan. Uurtje werk en je kunt er jaren plezier aan beleven.

Reageren