2015-11-17 5 views
7

Ho bisogno di generare numeri casuali univoci in Postgresql con una lunghezza fissa di 13 cifre. Ho trovato un simile thread in cui è stata utilizzata una sequenza crittografata utilizzando "pseudo_encrypt", ma il numero restituito non era con una lunghezza fissa.Genera numeri casuali univoci in Postgresql con lunghezza fissa

Allora, che cosa ho bisogno è: ottenere una sequenza casuale cifrata con una lunghezza fissa di 13 cifre in cui il valore minimo è 0.000.000,000001 millions e un valore massimo è 9999999999999.

è possibile? Se non è possibile iniziare con gli zeri in primo piano non è un grosso problema (credo), posso impostarli a livello di codice durante la lettura dal db, ma sarebbe bello se Postgresql potesse farlo da solo.

- EDIT -

Dopo aver realizzato alcune cose utili devo cambiare la questione al fine di spiegare meglio che cosa ho bisogno:

ho bisogno per generare numeri casuali unici (bigint) in PostgreSQL con una lunghezza massima fissa di 13 cifre. In realtà sto cercando di usare la funzione pseudo_encrypt (64 bit), ma il numero restituito ovviamente non è con una lunghezza massima fissa di 13, nel caso a 32 bit la lunghezza massima è 10 cifre (int), e per il 64 bit è 19 (bigint).

Quindi, come ottenere una sequenza casuale crittografata con una lunghezza massima fissa di 13 cifre, dove il valore minimo è 1 e il valore massimo è 9999999999999?

È possibile modificare la funzione pseudo_ecrypt a 64 bit per ottenere questo risultato? O se non è possibile, ci sono altri metodi per ottenere una sequenza unica con questi requisiti?

funzione pseudocasuale Encrypt (64 bit)

CREATE OR REPLACE FUNCTION pseudo_encrypt(VALUE bigint) returns bigint AS $$ 
DECLARE 
l1 bigint; 
l2 bigint; 
r1 bigint; 
r2 bigint; 
i int:=0; 
BEGIN 
l1:= (VALUE >> 32) & 4294967295::bigint; 
r1:= VALUE & 4294967295; 
WHILE i < 3 LOOP 
    l2 := r1; 
    r2 := l1 # ((((1366.0 * r1 + 150889) % 714025)/714025.0) * 32767*32767)::int; 
    l1 := l2; 
    r1 := r2; 
    i := i + 1; 
END LOOP; 
RETURN ((l1::bigint << 32) + r1); 
END; 
$$ LANGUAGE plpgsql strict immutable; 
+1

È possibile formattare il valore restituito da tale funzione per avere una lunghezza fissa: 'to_char (pseudo_encrypt (nextval ('seq') :: int), '0000000000000')' –

+0

E la sequenza? Come devo impostarlo? – MattC

+0

Lo stesso della domanda a cui ti sei collegato. Assicurati solo di avere un valore massimo che non superi le 13 cifre. –

risposta

11

Ottimizzare la funzione esistente per N < 64 bit valori

È relativamente semplice modificare la variante bigint per ridurre l'output Valori 2^N, dove N è pari e inferiore a 64.

Per ottenere 13 cifre decimali, considerare il massimo N per il quale 2^N ha 13 cifre. Questo è N = 42, con 2^42=4398046511104.

L'algoritmo funziona suddividendo il valore di input in due metà con un numero uguale di bit e facendoli scorrere attraverso la rete di Feistel, essenzialmente XOR'ing con il risultato della funzione di arrotondamento e scambiando le metà a ogni iterazione.

Se in ogni fase del processo, ciascuna metà è limitata a 21 bit, il risultato che unisce entrambe le metà è garantito non superiore a 42 bit.

Quindi, ecco la mia variante proposta:

CREATE OR REPLACE FUNCTION pseudo_encrypt42(VALUE bigint) returns bigint 
AS $$ 
DECLARE 
    l1 bigint; 
    l2 bigint; 
    r1 bigint; 
    r2 bigint; 
    i int:=0; 
    b21 int:=(1<<21)-1; -- 21 bits mask for a half-number => 42 bits total 
BEGIN 
    l1:= VALUE >> 21; 
    r1:= VALUE & b21; 
    WHILE i < 3 LOOP 
    l2 := r1; 
    r2 := l1 # (((((1366*r1+150889)%714025)/714025.0)*32767*32767)::int & b21); 
    l1 := l2; 
    r1 := r2; 
    i := i + 1; 
    END LOOP; 
    RETURN ((l1::bigint << 21) + r1); 
END; 
$$ LANGUAGE plpgsql strict immutable; 

l'ingresso deve essere inferiore a (2^42)-1, altrimenti le uscite si scontreranno, come pseudo_encrypt42(x) = pseudo_encrypt42(x mod 2^42).

Cosa si può fare per i numeri mancanti tra 2^42 e 10^13?

2^42 - 10^13 = 5601953488896 quindi è un bel po 'di numeri mancanti. Non so come aiutarlo in un singolo passaggio con la rete Feistel. Una soluzione alternativa che potrebbe essere accettabile, tuttavia, è generare un altro insieme di valori univoci in 0..M e aggiungere 2^42 a questi, quindi non c'è rischio di collisione.

Questo altro set potrebbe essere ottenuto con la stessa funzione, solo con l'offset aggiunto. 4398046511104 + pseudo_encrypt42(x) è garantito tra 4398046511104 e 2*4398046511104 = 8796093022208 valori univoci in modo che sia più vicino all'obiettivo. La stessa tecnica potrebbe essere applicata con diversi altri intervalli, anche se non necessariamente della stessa dimensione.

Tuttavia questa soluzione degrada il comportamento casuale dall'aspetto, come invece di avere un campo di uscita singola in cui ogni numero può essere compreso tra 0 e X, otterreste N campi di uscita distinte di X/N numeri. Con diverse partizioni distinte come questa, è facile indovinare in quale partizione sarà l'output, ma non quale valore all'interno della partizione.

+1

Questo risolve il mio problema, grazie Daniel! È una risposta molto dettagliata, sono sicuro che sarà molto utile anche per altri sviluppatori! – MattC

+1

Penso che nel tuo caso l'input deve essere inferiore a '(2^21)' per evitare collisioni, perché stai usando 'VALUE >> (64-21) 'per la parte sinistra (è come prendere 21 bit da sinistra). In questo caso per valori superiori a '2^21' e inferiori a qualcosa, si otterrà comunque zero come parte sinistra, ma la parte destra inizierà nuovamente da zero. Per risolvere il problema ho usato lo spostamento "VALUE >> 21" per la parte sinistra (è come prendere i 21 bit successivi dopo i primi 21 bit), quindi la restrizione '(2^42)' è diventata valida. – mopdobopot

+0

@mopdobopot: hai ragione, buona cattura. Ho risolto il codice nella risposta. –