2015-11-17 17 views
7

Potrzebuję generować unikalne liczby losowe w Postgresql o stałej długości 13 cyfr. Znalazłem podobny thread, gdzie użyto sekwencji zaszyfrowanej przy użyciu "pseudo_encrypt", ale zwrócona liczba nie miała stałej długości.Generowanie unikatowych liczb losowych w Postgresql o ustalonej długości

Więc, co jest mi potrzebne: uzyskać zaszyfrowaną losową sekwencję o stałej długości 13 cyfr, w których wartość MIN 0000000000001 i max wartości jest 9999999999999.

jest to możliwe? Jeśli start z zerami z przodu nie jest możliwy to nie jest duży problem (myślę), mogę ustawić je programowo podczas odczytu z db, ale byłoby wspaniale, gdyby Postgresql mógł zrobić to sam.

- EDIT -

Po sobie sprawę kilka przydatnych rzeczy muszę zmienić pytanie, aby lepiej wyjaśnić, co potrzebne:

muszę generować unikatowe numery losowe (bigint) w PostgreSQL z stała maksymalna długość 13 cyfr. Właściwie próbuję użyć funkcji pseudo_encrypt (64-bitowej), ale zwrócona liczba oczywiście nie ma stałej maksymalnej długości 13, w przypadku 32-bitowym maksymalna długość wynosi 10 cyfr (int), a dla 64-bitowej 19 (bigint).

Jak uzyskać zaszyfrowaną losową sekwencję o ustalonej maksymalnej długości 13 cyfr, gdzie minimalna wartość wynosi 1, a maksymalna to 9999999999999?

Czy można zmienić funkcję 64-bitowego pseudo_ecrypt, aby uzyskać ten wynik? A jeśli nie jest to możliwe, istnieją inne metody uzyskania unikalnej sekwencji z tymi wymaganiami?

funkcja pseudolosowa szyfrowanie (64-bitowe)

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

Możesz sformatować zwróconą wartość z tej funkcji, aby mieć stałą długość: 'to_char (pseudo_encrypt (nextval ('seq') :: int), '0000000000000')' –

+0

A co z sekwencją? Jak mam to ustawić? – MattC

+0

To samo, co w pytaniu, z którym się łączyłeś. Upewnij się, że masz maksymalną wartość, która nie przekracza 13 cyfr. –

Odpowiedz

11

skomplikowany istniejącą funkcję dla N < 64 bitów wartości

jest stosunkowo prosty do dostrojenia wariant bigint zmniejszenie wyjście 2^N wartości, gdzie N jest parzysty i mniejszy niż 64.

Aby uzyskać 13 cyfr dziesiętnych, należy rozważyć maksymalną wartość N, dla której 2^N ma 13 cyfr. To jest N = 42, z 2^42=4398046511104.

Algorytm działa poprzez podzielenie wartości wejściowej na dwie połówki z równą liczbą bitów i przepuszczenie ich przez sieć Feistel, w zasadzie XOR'ing z wynikiem funkcji round i zamianą połówek w każdej iteracji.

Jeśli na każdym etapie procesu każda połówka jest ograniczona do 21 bitów, to wynik połączenia obu połówek gwarantuje, że nie przekroczy 42 bity.

Więc oto moja proponowany wariant:

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; 

Wejście musi być mniejsza niż (2^42)-1, w przeciwnym razie będzie kolidować wyjścia, jak pseudo_encrypt42(x) = pseudo_encrypt42(x mod 2^42).

Co można zrobić w sprawie brakujących numerów między 2^42 a 10^13?

2^42 - 10^13 = 5601953488896 to dużo brakujących numerów. Nie wiem, jak pomóc w jednym przejściu z siecią Feistel. Jednym rozwiązaniem, które może być akceptowalne, jest wygenerowanie kolejnego zestawu unikatowych wartości w 0..M i dodanie do nich 2^42, więc nie ma ryzyka kolizji.

Ten inny zestaw można uzyskać za pomocą tej samej funkcji, po dodaniu przesunięcia. 4398046511104 + pseudo_encrypt42(x) ma gwarantowane wartości od 4398046511104 i 2*4398046511104 = 8796093022208, dzięki czemu jest bliżej celu. Tę samą technikę można zastosować w kilku innych zakresach, niekoniecznie o tej samej wielkości.

Jednak to rozwiązanie degraduje losową wyglądające zachowanie, jak zamiast pojedynczego zakresu wyjściowego, gdzie każda liczba może wynosić od 0 i X, można dostać N odrębne zakresy wyjściowe X/N liczb. Dzięki kilku różnym partycjom łatwo jest zgadnąć, w której partycji będzie wyjście, ale nie w jakiej wartości wewnątrz partycji.

+1

To rozwiązuje mój problem, dziękuję, Daniel! Jest to bardzo szczegółowa odpowiedź, jestem pewien, że będzie to bardzo przydatne także dla innych programistów! – MattC

+1

Myślę, że w twoim przypadku dane wejściowe muszą być mniejsze niż '(2^21)', aby uniknąć kolizji, ponieważ używasz 'VALUE >> (64-21)' dla lewej części (to tak, jakby wziąć 21 bitów od lewej). W tym przypadku dla wartości większych niż "2^21" i mniej niż coś, nadal otrzymasz zero jako lewą część, ale prawa część zacznie znowu od zera. Aby to naprawić użyłem przesunięcia "WARTOŚĆ >> 21" dla lewej części (to tak, jakby zrobić następne 21 bitów po pierwszych 21 bitach), wtedy twoje ograniczenie '(2^42) stało się ważne. – mopdobopot

+0

@mopdobopot: masz rację, dobry połów. Naprawiłem kod w odpowiedzi. –

Powiązane problemy