2015-08-03 2 views
1

J'ai deux tables: l'événement et l'emplacementComment créer une fonction paginable dans PostgreSQL

CREATE TABLE location 
(
    location_id bigint NOT NULL, 
    version bigint NOT NULL, 
    active boolean NOT NULL, 
    created timestamp without time zone NOT NULL, 
    latitude double precision NOT NULL, 
    longitude double precision NOT NULL, 
    updated timestamp without time zone, 
    CONSTRAINT location_pkey PRIMARY KEY (location_id) 
) 

CREATE TABLE event 
(
    event_id bigint NOT NULL, 
    version bigint NOT NULL, 
    active boolean NOT NULL, 
    created timestamp without time zone NOT NULL, 
    end_date date, 
    entry_fee numeric(19,2), 
    location_id bigint NOT NULL, 
    organizer_id bigint NOT NULL, 
    start_date date NOT NULL, 
    timetable_id bigint, 
    updated timestamp without time zone, 
    CONSTRAINT event_pkey PRIMARY KEY (event_id), 
    CONSTRAINT fk_organizer FOREIGN KEY (organizer_id) 
     REFERENCES "user" (user_id) MATCH SIMPLE 
     ON UPDATE NO ACTION ON DELETE NO ACTION, 
    CONSTRAINT fk_timetable FOREIGN KEY (timetable_id) 
     REFERENCES timetable (timetable_id) MATCH SIMPLE 
     ON UPDATE NO ACTION ON DELETE NO ACTION, 
    CONSTRAINT fk_location FOREIGN KEY (location_id) 
     REFERENCES location (location_id) MATCH SIMPLE 
     ON UPDATE NO ACTION ON DELETE NO ACTION 
) 

D'autres tableaux sont de moindre importance à aucune sorte qu'ils ne seront pas affichées (sauf demande explicite).

Et pour ces tables, en utilisant cube et earthdistance extensions pgsql J'ai créé la fonction suivante pour trouver tous les event_ids dans un certain rayon d'un certain point.

CREATE OR REPLACE FUNCTION eventidswithinradius(
    lat double precision, 
    lng double precision, 
    radius double precision) 
    RETURNS SETOF bigint AS 
$BODY$ 
BEGIN 
    RETURN QUERY SELECT event.event_id 
    FROM event 
    INNER JOIN location ON location.location_id = event.location_id 
    WHERE earth_box(ll_to_earth(lat, lng), radius) @> ll_to_earth(location.latitude, location.longitude); 
END; 
$BODY$ 

Et cela fonctionne comme prévu. Maintenant, je souhaite le rendre paginable, et je suis coincé sur la façon d'obtenir toutes les valeurs nécessaires (la table avec le contenu paginé et le nombre total).

Jusqu'à présent, j'ai créé ceci:

CREATE OR REPLACE FUNCTION pagedeventidswithinradius(
    IN lat double precision, 
    IN lng double precision, 
    IN radius double precision, 
    IN page_size integer, 
    IN page_offset integer) 
    RETURNS TABLE(total_size integer , event_id bigint) AS 
$BODY$ 
DECLARE total integer; 
BEGIN 
    SELECT COUNT(location.*) INTO total FROM location WHERE earth_box(ll_to_earth(lat, lng), radius) @> ll_to_earth(location.latitude, location.longitude); 

    RETURN QUERY SELECT total, event.event_id as event_id 
    FROM event 
    INNER JOIN location ON location.location_id = event.location_id 
    WHERE earth_box(ll_to_earth(lat, lng), radius) @> ll_to_earth(location.latitude, location.longitude) 
    ORDER BY event_id 
    LIMIT page_size OFFSET page_offset; 
END; 
$BODY$ 

Ici le nombre est appelé une seule fois et stocké dans une variable puisque je suppose que si je plaçais COUNT dans la requête de retour lui-même, il serait appelé pour chaque ligne . Ce type de travaux, mais il est difficile d'analyser sur le back-end puisque le résultat est sous la forme de (count, event_id), également le nombre est inutilement répété sur toutes les lignes de résultat. J'espérais pouvoir simplement ajouter total comme un paramètre OUT et avoir la fonction retourner la table et remplir la variable OUT avec le nombre total, mais il semble que ce n'est pas autorisé. Je peux toujours faire en sorte que le décompte soit une fonction distincte, mais je me demandais s'il y avait une meilleure façon d'aborder cette question?

+0

Pourquoi voulez-vous à la page cela? Puisque vous ne renvoyez qu'un seul 'bigint' par ligne, la consommation de ressources ne poserait pas de problème ici. – Patrick

+0

Oui, cependant, ces bigints représentent des identifiants qui seront utilisés pour extraire des données complètes en utilisant ORM. L'ORM ne peut pas appeler fonctions/sp, il s'agit donc d'une solution de contournement pour récupérer les ID paginés, puis utiliser ORM pour obtenir ces éléments correctement mappés. – MrPlow

Répondre

1

Non, il n'y a pas vraiment une meilleure option. Vous voulez deux types de quantités différents, vous avez donc besoin de deux requêtes. Vous pouvez améliorer sur votre fonction, cependant:

CREATE FUNCTION eventidswithinradius(lat float8, long float8, radius float8) RETURNS SETOF bigint AS $BODY$ 
    SELECT event.event_id 
    FROM event 
    JOIN location l USING (location_id) 
    WHERE earth_box(ll_to_earth(lat, lng), radius) @> ll_to_earth(l.latitude, l.longitude); 
$BODY$ LANGUAGE sql STRICT; 

En fonction LANGUAGE sql il est plus efficace que en fonction PL/pgSQL, plus vous pouvez faire votre recherche de personnes à l'extérieur:

SELECT * 
FROM eventidswithinradius(121.056, 14.582, 3000) 
LIMIT 15 OFFSET 1; 

interne le planificateur de requêtes résoudra l'appel de la fonction à sa requête sous-jacente et appliquera la pagination directement à ce niveau.

obtenir le total avec l'évidence:

SELECT count(id) 
FROM eventidswithinradius(121.056, 14.582, 3000); 
+0

Cela fonctionne, bien que la fonction manque la déclaration 'RETURNS'. Merci de m'avoir montré l'option 'LANGUAGE sql' ainsi que' USING' au lieu de 'ON' pour les jointures. – MrPlow