sql >> Base de Datos >  >> RDS >> Oracle

Oracle SQL Date a Long y viceversa

Está perdiendo demasiada precisión en su conversión para poder volver al otro lado. Puedes acercarte usando marcas de tiempo en lugar de fechas.

En primer lugar, su consulta inicial estaba perdiendo el componente de tiempo por completo:

select to_char(date '1970-01-01'
  + (1432550197431912935 - power(2, 60))/power(2, 44), 'YYYY-MM-DD HH24:MI:SS')
from dual;

2013-07-09 01:13:19

... pero incluso con eso, volver a convertir ha perdido demasiado:

select ((to_date('2013-07-09 01:13:19','YYYY-MM-DD HH24:MI:SS')
  - date '1970-01-01') * power(2, 44)) + power(2, 60) from dual;

1432550197477589405

Que está más cerca que el 1432549301782839296 que obtuviste, pero todavía está bastante lejos.

Parte del problema es la precisión de DATE , que es sólo a la segunda. Si usa TIMESTAMP en cambio, puedes acercarte bastante; puedes ver que el valor a tener es supuestamente muy preciso:

select timestamp '1970-01-01 00:00:00'
  + numtodsinterval((1432550197431912935 - power(2, 60))/power(2, 44), 'DAY')
from dual;

2013-07-09 01:13:18.775670462

Volver a convertir eso es complicado porque la aritmética de marca de tiempo da resultados de intervalo, que luego tienes que manipular para volver a un número, primero como el número original de días:

select extract(day from int_val)
  + extract(hour from int_val) / 24
  + extract(minute from int_val) / (24 * 60)
  + extract(second from int_val) / (24 * 60 * 60)
from (
select to_timestamp('2013-07-09 01.13.18.775670462', 'YYYY-MM-DD HH24:MI:SS.FF9')
  - timestamp '1970-01-01 00:00:00' as int_val from dual);

15895.0509117554451620370370370370370371

... y luego con tu manipulación de poder:

select ((extract(day from int_val)
    + extract(hour from int_val) / 24
    + extract(minute from int_val) / (24 * 60)
    + extract(second from int_val) / (24 * 60 * 60))
  * power(2, 44)) + power(2, 60)
as x
from (
select to_timestamp('2013-07-09 01.13.18.775670462', 'YYYY-MM-DD HH24:MI:SS.FF9')
  - timestamp '1970-01-01 00:00:00' as int_val from dual);

1432550197431912935.09988554676148148148

Lo cual está bastante cerca. Puede truncarlo o redondearlo al número entero más cercano.

Con solo mirar sus números y la manipulación de poder en cada sentido, se muestra que parece estar dentro de la precisión que Oracle puede manejar:

select (1432550197431912935 - power(2, 60)) / power(2, 44)
from dual;

15895.050911755445156359201064333319664

select (15895.050911755445156359201064333319664 * power(2, 44)) + power(2, 60)
from dual;

1432550197431912935.000...

Incluso con una marca de tiempo, pierde algo de eso, ya que el primer valor supera el límite de segundos fraccionarios de 9 dígitos. La parte que representa las fracciones de segundo, una vez que haya contabilizado las 15895 horas, etc., es .0000089776673785814232865555418862 de un día, que es .77567046150943497195839881896768 segundos; la marca de tiempo lo redondea a .775670462 . Así que nunca va a ser perfecto.

Eso también lleva a uno a preguntarse cómo se genera el número original; parece poco probable que en realidad represente un tiempo hasta esa precisión extrema, ya que está por debajo de yoctosegundos . No está muy claro si la 'precisión' es en realidad un artefacto de su manipulación basada en potencias de 2, pero de todos modos no parece muy útil. Es más común usar la fecha de época de estilo Unix, contando segundos o, a veces, milisegundos desde la fecha de época que está usando de todos modos, si tiene que almacenarse como un número. Este diseño es... interesante.