PostgreSQL : 결 측값 보간
타임 스탬프와 값이있는 PostgreSQL 테이블이 있습니다.
"위도"에서 누락 된 값을 보간하고 싶습니다.
"lat"아래의 값은 데이텀 위의 조석 높이입니다. 목적을 위해 두 개의 알려진 값 사이의 선형 결 측값을 보간하는 것이 좋습니다.
PostgreSQL에서 가장 좋은 방법은 무엇입니까?

20200825 수정
QGIS 필드 계산기를 사용하여이 문제를 다른 방식으로 해결했습니다. 이 방법의 문제점 : 시간이 오래 걸리고 프로세스가 클라이언트 측에서 실행되며 서버에서 직접 실행하고 싶습니다.
단계적으로 내 워크 플로는 다음과 같습니다.
- 기록 된 "위도"값 사이의 간격은 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
- "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 분 지난 것입니다.
- 마지막으로 기록 된 "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
- 데이터베이스에서 사용하지 않는 열 삭제
동일한 작업을 수행하려면 PostgreSQL에서 어떤 문 / 스크립트를 사용해야합니까?
답변
나는 (부분) 해결책이 있습니다-내가 한 일은 다음과 같습니다 ( 여기에서 사용 가능한 바이올린 참조 ).
보간에 사용한 알고리즘은
시퀀스가 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()
NULL
OFFSET
저는이 솔루션에별로 만족스럽지 않습니다. NULL
s 수에 대한 하드 코딩이 필요 하며 이러한 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;