PostgreSQL : 결 측값 보간

Aug 19 2020

타임 스탬프와 값이있는 PostgreSQL 테이블이 있습니다.
"위도"에서 누락 된 값을 보간하고 싶습니다.

"lat"아래의 값은 데이텀 위의 조석 높이입니다. 목적을 위해 두 개의 알려진 값 사이의 선형 결 측값을 보간하는 것이 좋습니다.

PostgreSQL에서 가장 좋은 방법은 무엇입니까?

20200825 수정

QGIS 필드 계산기를 사용하여이 문제를 다른 방식으로 해결했습니다. 이 방법의 문제점 : 시간이 오래 걸리고 프로세스가 클라이언트 측에서 실행되며 서버에서 직접 실행하고 싶습니다.

단계적으로 내 워크 플로는 다음과 같습니다.

  1. 기록 된 "위도"값 사이의 간격은 10 분입니다. 두 기록 값 사이의 분당 증분을 계산하여 기록 된 "위도"값의 "tidal_step"이라는 추가 열에 저장했습니다. (나는 타임 스탬프를 열에 "epoch"로도 저장했습니다)

QGIS에서 :

tidal_step =
-- the lat value @ the epoch, 10 minutes or 600000 miliseconds from the current epoch: 
(attribute(get_feature('werkset','epoch',("epoch"+'600000')),'lat') -
-- the lat value @ the current
attribute(get_feature('werkset','epoch',"epoch"),'lat'))
/10

결과 예제 이미지의 처음 두 값 : (4.95-5.07) / 10 = -0.012

  1. "lat"값이 기록 된 마지막 기록 인스턴스를 지나서 보간 할 "lat"값의 분을 결정하고 "min_past_rec"열에 저장했습니다.

QGIS에서 :

left(
right("timestamp",8) --this takes the timestamp and goes 8 charakters from the right
,1) -- this takes the string from the previous right( and goes 1 character left

예에서 첫 번째 값의 경우 : 2019-01-01 00:15:15는 '5'를 반환합니다. 이것은 마지막으로 기록 된 값보다 5 분 지난 것입니다.

  1. 마지막으로 기록 된 "lat"값에 ( "min_past_rec"* "tidal_step")을 추가하여 누락 된 값을 보간하고 "lat_interpolated"라는 열에 저장했습니다.

QGIS에서

CASE
WHEN "lat" = NULL 
THEN
-- minutes pas the last recorded instance:
("min_past_rec" *
-- the "tidal_step" at the last recorded "lat"-value:
(attribute(get_feature('werkset','epoch',
("epoch" - --the epoch of the "lat" value to be interpolated minus:

left(right("timestamp",8),1) * 600000 -- = the amount of minutes after the last recorded instance.
+ left(right("timestamp",6),2) * 1000) --  and the amount of seconds after the last recorded instance.
),'tidal_step')) +

-- the last recorded "lat"-value

(attribute(get_feature('werkset','epoch',("epoch" - left(right("timestamp",8),1) * 600000 + left(right("timestamp",6),2) * 1000)),'lat'))

예제의 데이터 :

2019-01-01 00:17:33 :

"lat_interpolated" = "min_past_rec" * "tidal_step" + "lat" = 
7*-0.012 + 4.95 = 4.866
  1. 데이터베이스에서 사용하지 않는 열 삭제

동일한 작업을 수행하려면 PostgreSQL에서 어떤 문 / 스크립트를 사용해야합니까?

답변

1 Vérace Aug 19 2020 at 09:58

나는 (부분) 해결책이 있습니다-내가 한 일은 다음과 같습니다 ( 여기에서 사용 가능한 바이올린 참조 ).

보간에 사용한 알고리즘은

  • 시퀀스가 1 NULL인 경우 위 값과 아래 값의 평균을 취하십시오.

  • 2 NULL초의 시퀀스 , 상위 할당 값은 그 위에있는 두 레코드의 평균이고 아래쪽 할당 된 값은 아래 두 레코드의 평균입니다.

이를 위해 다음을 수행했습니다.

테이블 만들기 :

CREATE TABLE data 
(
  s SERIAL PRIMARY KEY, 
  t TIMESTAMP, 
  lat NUMERIC
);

샘플 데이터로 채 웁니다.

INSERT INTO data (t, lat)
VALUES
('2019-01-01 00:00:00', 5.07),
('2019-01-01 01:00:00', 4.60),
('2019-01-01 02:00:00', NULL),
('2019-01-01 03:00:00', NULL),
('2019-01-01 04:00:00', 4.7),
('2019-01-01 05:00:00', 4.20),
('2019-01-01 06:00:00', NULL),
('2019-01-01 07:00:00', 4.98),
('2019-01-01 08:00:00', 4.50);

레코드 3 & 4 및 7은 NULL.

그런 다음 첫 번째 쿼리를 실행했습니다.

WITH cte1 AS
(
  SELECT d1.s,
    d1.t AS t1, d1.lat AS l1,
    LAG(d1.lat, 2)  OVER (ORDER BY t ASC) AS lag_t1_2,
    LAG(d1.lat, 1)  OVER (ORDER BY t ASC) AS lag_t1,
    LEAD(d1.lat, 1) OVER (ORDER BY t ASC) AS lead_t1,
    LEAD(d1.lat, 2) OVER (ORDER BY t ASC) AS lead_t1_2
  FROM data d1
),
cte2 AS
(
  SELECT 
    d2.t AS t2, d2.lat AS l2, 
    LAG(d2.lat, 1) OVER(ORDER BY t DESC) AS lag_t2,
    LEAD(d2.lat, 1) OVER(ORDER BY t DESC) AS lead_t2
  FROM data d2
),
cte3 AS
(
  SELECT t1.s,
    t1.t1,  t1.lag_t1_2, t1.lag_t1, t2.lag_t2, t1.l1, t2.l2, 
    t1.lead_t1, t2.lead_t2, t1.lead_t1_2
  FROM cte1 t1
  JOIN cte2 t2
  ON t1.t1 = t2.t2
)
SELECT * FROM cte3;

결과 (공백은 NULL바이올린에서 훨씬 더 명확함을 의미 합니다) :

s   t1  lag_t1_2    lag_t1  lag_t2  l1  l2  lead_t1     lead_t2     lead_t1_2
1   2019-01-01 00:00:00             4.60    5.07    5.07    4.60        
2   2019-01-01 01:00:00         5.07        4.60    4.60        5.07    
3   2019-01-01 02:00:00     5.07    4.60                    4.60    4.7
4   2019-01-01 03:00:00     4.60        4.7             4.7         4.20
5   2019-01-01 04:00:00             4.20    4.7     4.7     4.20        
6   2019-01-01 05:00:00         4.7         4.20    4.20        4.7     4.98
7   2019-01-01 06:00:00     4.7     4.20    4.98            4.98    4.20    4.50
8   2019-01-01 07:00:00     4.20        4.50    4.98    4.98    4.50        
9   2019-01-01 08:00:00         4.98        4.50    4.50        4.98 

LAG()및 LEAD()창 기능 ( documentation) 의 사용에 유의하십시오 . 나는 그것들을 같은 테이블에서 사용했지만 다르게 정렬했습니다.

OFFSET옵션을 사용하면 원래 단일 lat열에서 누락 된 NULL값에 값을 할당하는 데 매우 유용한 "생성 된"데이터 열이 6 개 더 있습니다 . 퍼즐의 마지막 (부분) 조각이 아래에 나와 있습니다 (전체 SQL 쿼리는이 게시물의 맨 아래에 있으며 바이올린에도 있습니다).

cte4 AS
(
  SELECT t1.s,
  t1.l1 AS lat,
    CASE 
      WHEN (t1.l1 IS NOT NULL) THEN t1.l1
      WHEN (t1.l1 IS NULL) AND (t1.l2) IS NULL AND (t1.lag_t1 IS NOT NULL)
        AND (t1.lag_t2 IS NOT NULL) THEN ROUND((t1.lag_t1 + t1.lag_t2)/2, 2) 
      WHEN (t1.lag_t2 IS NULL) AND (t1.l1 IS NULL) AND (t1.l2 IS NULL) 
        AND (t1.lead_t1 IS NULL) THEN ROUND((t1.lag_t1 + t1.lag_t1_2)/2, 2)
      WHEN (t1.l1 IS NULL) AND (t1.l2 IS NULL)  AND (t1.lag_t1 IS NULL)
        AND (t1.lead_t2 IS NULL) THEN ROUND((t1.lead_t1 + t1.lead_t1_2)/2, 2)
      ELSE 0
    END AS final_val
  FROM cte3 t1
)
SELECT s, lat, final_val FROM cte4;

최종 결과:

s    lat    final_val
1   5.07         5.07
2   4.60         4.60
3   NULL         4.84
4   NULL         4.45
5   4.7           4.7
6   4.20         4.20
7   NULL         4.59
8   4.98         4.98
9   4.50         4.50

따라서 레코드 7에 대해 계산 된 값은 레코드 6 및 8의 평균이고 레코드 3은 레코드 1 및 2의 평균이고 레코드 4에 할당 된 값은 5 및 6의 평균임을 알 수 있습니다. 및 기능 에 대한 OFFSET옵션 사용 . 3 초의 시퀀스를 얻으면 3 의 시퀀스 를 사용해야합니다 .LAG()LEAD()NULLOFFSET

저는이 솔루션에별로 만족스럽지 않습니다. NULLs 수에 대한 하드 코딩이 필요 하며 이러한 CASE명령문은 훨씬 더 복잡하고 끔찍해집니다. 이상적으로 RECURSIVE CTE는 일종의 해결책이 필요하지만 나는 HTH!

============================== 전체 쿼리 ================= =======

WITH cte1 AS
(
  SELECT d1.s,
    d1.t AS t1, d1.lat AS l1,
    LAG(d1.lat, 2)  OVER (ORDER BY t ASC) AS lag_t1_2,
    LAG(d1.lat, 1)  OVER (ORDER BY t ASC) AS lag_t1,
    LEAD(d1.lat, 1) OVER (ORDER BY t ASC) AS lead_t1,
    LEAD(d1.lat, 2) OVER (ORDER BY t ASC) AS lead_t1_2
  FROM data d1
),
cte2 AS
(
  SELECT 
    d2.t AS t2, d2.lat AS l2, 
    LAG(d2.lat, 1) OVER(ORDER BY t DESC) AS lag_t2,
    LEAD(d2.lat, 1) OVER(ORDER BY t DESC) AS lead_t2
  FROM data d2
),
cte3 AS
(
  SELECT t1.s,
    t1.t1,  t1.lag_t1_2, t1.lag_t1, t2.lag_t2, t1.l1, t2.l2, 
    t1.lead_t1, t2.lead_t2, t1.lead_t1_2
  FROM cte1 t1
  JOIN cte2 t2
  ON t1.t1 = t2.t2
),
cte4 AS
(
  SELECT t1.s,
  t1.l1 AS lat,
    CASE 
      WHEN (t1.l1 IS NOT NULL) THEN t1.l1
      WHEN (t1.l1 IS NULL) AND (t1.l2) IS NULL AND (t1.lag_t1 IS NOT NULL)
        AND (t1.lag_t2 IS NOT NULL) THEN ROUND((t1.lag_t1 + t1.lag_t2)/2, 2) 
      WHEN (t1.lag_t2 IS NULL) AND (t1.l1 IS NULL) AND (t1.l2 IS NULL) 
        AND (t1.lead_t1 IS NULL) THEN ROUND((t1.lag_t1 + t1.lag_t1_2)/2, 2)
      WHEN (t1.l1 IS NULL) AND (t1.l2 IS NULL)  AND (t1.lag_t1 IS NULL)
        AND (t1.lead_t2 IS NULL) THEN ROUND((t1.lead_t1 + t1.lead_t1_2)/2, 2)
      ELSE 0
    END AS final_val,
    t1.lead_t1_2
  FROM cte3 t1
)
SELECT s, lat, final_val, lead_t1_2 FROM cte4;