2016-07-28 3 views
1

Ich verstehe, warum der Differenzoperator für Bereiche fehlschlägt, wenn das Ergebnis zwei getrennte Bereiche wäre. Aber ich bin mir nicht sicher, was die Problemumgehung ist.Finden Sie Zahlen, die in keinem Bereich enthalten sind

Ein einfaches konzeptuelles Beispiel wäre, wenn ich einen Kalender führen würde und für jede Besprechung eine Aufzeichnung hätte, mit einem Zeitstempelbereich, der die Zeit der Besprechung speichert; Was wäre eine einfache Möglichkeit, eine Liste von Zeiten zu erstellen, in denen die Person an einem bestimmten Tag frei war?

Es scheint wie eine einfache, grundlegende Sache mit Bereichen zu tun, aber ich kann nicht mit einer Möglichkeit, es zu tun, die nicht umständlich kompliziert ist.

+0

[Bearbeiten] Ihre Frage ein und fügen Sie einige Beispieldaten und die erwartete Ausgabe auf der Grundlage dieser Daten. _Formatted_ Text bitte, [keine Screenshots] (http://meta.stackoverflow.com/questions/285551/why-may-i-not-upload-images-of-code-on-so-when-asking-a- Frage/285557 # 285557) –

+0

Beide Antworten haben Beispiele, die ziemlich gut zu dem passen, was ich vorhatte, daher hilft es mir wahrscheinlich nicht, an dieser Stelle ein Beispiel hinzuzufügen. Aber ich schätze den Vorschlag und werde das bei zukünftigen Fragen tun. –

Antwort

1
create or replace function range_exclude(anyelement, anyelement) returns anyarray as $$ 
declare 
    r1 text; 
    r2 text; 
begin 
    -- Check input parameters 
    if not pg_typeof($1) in ('numrange'::regtype, 'int8range'::regtype, 'daterange'::regtype, 'tstzrange'::regtype) then 
    raise exception 'Function accepts only range types but got % type.', pg_typeof($1); 
    end if; 

    -- If result is single element 
    if ($1 &< $2 or $1 &> $2) then 
    return array[$1 - $2]; 
    end if; 

    -- Else build array of two intervals 
    if lower_inc($1) then r1 := '['; else r1 := '('; end if; 
    r1 := r1 || lower($1) || ',' || lower($2); 
    if lower_inc($2) then r1 := r1 || ')'; else r1 := r1 || ']'; end if; 

    if upper_inc($2) then r2 := '('; else r2 := '['; end if; 
    r2 := r2 || upper($2) || ',' || upper($1); 
    if upper_inc($1) then r2 := r2 || ']'; else r2 := r2 || ')'; end if; 
    return array[r1, r2]; 
end $$ immutable language plpgsql; 

create or replace function range_exclude(anyelement, anyarray) returns anyarray as $$ 
declare 
    i int; 
    j int; 
begin 
    -- Check input parameters 
    if not pg_typeof($1) in ('numrange'::regtype, 'int8range'::regtype, 'daterange'::regtype, 'tstzrange'::regtype) then 
    raise exception 'Function accepts only range types but got % type.', pg_typeof($1); 
    end if; 

    if array_length($2,1) is null then 
    return array[$1]; 
    end if; 

    $0 := range_exclude($1,$2[array_lower($2,1)]); 
    for i in array_lower($2,1) + 1 .. array_upper($2,1) loop 
    select array(select x from (select unnest(range_exclude(x,$2[i])) from unnest($0) as t(x)) as t(x) where not isempty(x)) into $0; 
    end loop; 
    return $0; 
end $$ immutable language plpgsql; 

select range_exclude(numrange(8,17), array[numrange(10,11), numrange(13,20)]); 

Tests:

select range_exclude(numrange(1,10), numrange(5,6)); 
select range_exclude(numrange(8,17), array[numrange(10,11), numrange(13,15)]); 

Ergebnisse:

{"[1,5)","[6,10)"} 
{"[8,10)","[11,13)","[15,17)"} 

Und gleich für Zeitstempel:

select range_exclude(
    tstzrange('2016-07-28 8:00','2016-07-28 17:00'), 
    array[ 
    tstzrange('2016-07-28 10:00','2016-07-28 11:00'), 
    tstzrange('2016-07-28 13:00','2016-07-28 15:00')]); 

Ergebnis:

{"[\"2016-07-28 08:00:00+03\",\"2016-07-28 10:00:00+03\")","[\"2016-07-28 11:00:00+03\",\"2016-07-28 13:00:00+03\")","[\"2016-07-28 15:00:00+03\",\"2016-07-28 17:00:00+03\")"} 
+0

Sieht perfekt aus, vielen Dank. Ich habe noch nicht viel getan, um Funktionen zu erstellen, das hilft also sehr - sowohl, weil es meine Frage beantwortet, als auch, weil es ein Modell sein wird, um das nächste Mal, wenn ich es brauche, zu arbeiten. Ich schätze die Hilfe sehr. –

1

Wenn die Bereiche disjoint Verwendung der Fensterfunktion lead() oder lag() sind:

create table plan (duration tsrange); 
insert into plan values 
('[2015-01-01 10:00:00, 2015-01-01 12:00:00)'), 
('[2015-01-01 14:00:00, 2015-01-01 16:00:00)'), 
('[2015-01-01 18:00:00, 2015-01-01 20:00:00)'); 

select tsrange(upper(duration), lower(lead(duration) over (order by duration))) free_time 
from plan; 

        free_time     
----------------------------------------------- 
["2015-01-01 12:00:00","2015-01-01 14:00:00") 
["2015-01-01 16:00:00","2015-01-01 18:00:00") 
["2015-01-01 20:00:00",) 
(3 rows)  
+0

Yup, das wird bei den meisten Dingen funktionieren. Es sieht jedoch so aus, als ob es Probleme mit überlappenden Bereichen gibt - wenn ich das Beispiel ändere, so dass das zweite Meeting von 13:00 bis 16:00 ist, gibt es einen Fehler, da die Ausgabe ein Bereich mit einer unteren Grenze ist, die höher ist als die obere gebunden. Ich denke nicht, dass das ein Problem für meine Daten ist, also ist dies eine nette, einfache Antwort für mein spezifisches Problem - also danke. –

+0

Ich habe Ihre Frage streng beantwortet. Es ist offensichtlich, dass die Abfrage für überlappende Bereiche einige Änderungen benötigt. – klin

+0

Einverstanden, es ist eine gute Antwort und ich gab ihm eine positive Bewertung. Es ist nicht deine Schuld, dass du nicht jedes Szenario abgedeckt hast, das in meinem Kopf war, aber nicht in meiner Frage dargelegt wurde. –