Teradata

如何在最近的前一天加入?

  • January 9, 2020

想像一下,我有一個包含歷史價格數據的數據庫和一個包含日期的第二個表

在此處輸入圖像描述

每次價格變化時,都會在價格歷史表中輸入一個新行。

我怎樣才能(有效地)連接這兩個表,以便我可以獲得每個客戶當天必須支付的價格?

我需要 teradata 的語法,謝謝!

恐怕我不能保證我的答案 100% 兼容 Teradata,因為據我所知,沒有可用的 Teradata 小提琴。但是,我使用了 PostgreSQL,它是更符合標準的系統之一,並且在我的回答中沒有使用 PostgreSQL 特定的構造(見小提琴here),所以我認為我們很高興!

LAG and LEAD我瀏覽了有關 Window 函式 ( ) 和 Common Table Expressions ( s)的 Teradata 文件,CTE並將其與 PostgreSQL 的語法 ( Windowfunctions, CTEs) 進行了比較,這一切看起來都相當普通。

我做的第一件事是CREATE填充表格。順便說一句,由於此連結中列出的原因,您應該避免在 StackExchange 上使用圖像。為 DDL 和 DML 使用文本,或者更好的是,提供一個小提琴 - 如果您找不到 Teradata (我看過但找不到),請使用 PostgreSQL 之類的東西,它應該可以讓您大部分時間到達那裡。

另一個小注意事項,使用 . 命名欄位不是一個好主意SQL keywords。自由使用_(下劃線)字元在這裡有很大幫助。

CREATE TABLE price
(
 item_id TEXT NOT NULL,
 item_price SMALLINT NOT NULL,
 change_date DATE NOT NULL
);

INSERT INTO price 
VALUES 
('A', 249, '2014-09-01'),
('B', 129, '2014-09-01'),
('A', 299, '2014-08-25'),
('B', 139, '2014-09-25');

CREATE TABLE purchase
(
 customer_id SMALLINT NOT NULL,
 item_id TEXT NOT NULL,
 purchase_date DATE NOT NULL
);

INSERT INTO purchase
VALUES
(1, 'A', '2014-08-27'), (2, 'A', '2014-09-02');

然後我執行了以下查詢 - 我留下了最終計算不需要的無關欄位,以便可以遵循我的思維過程 - 使用LAGandLEAD可能很棘手,而且很容易混淆你應該比較的欄位哪一個,所以我的政策是將它們全部保留,讓 OP(原始海報 - 即您)決定您認為哪些有用並想要保留以及您希望消除哪些。

WITH pr_cte AS
(
 SELECT 
   p.item_id, 
   LAG(p.item_price, 1) 
     OVER (PARTITION BY p.item_id ORDER BY p.change_date) AS lag_price,
   p.item_price,
   LEAD(p.item_price, 1) 
     OVER (PARTITION BY p.item_id ORDER BY p.change_date) AS lead_price,
   LAG(change_date, 1, '2000-01-01') 
     OVER (PARTITION BY p.item_id ORDER BY p.change_date) AS lag_date,
   p.change_date,
   LEAD(p.change_date, 1, '2038-01-01') 
     OVER (PARTITION BY p.item_id ORDER BY p.change_date) AS lead_date
 FROM price p
 -- ORDER BY p.item_id, p.change_date -- not allowed in Teradata according to @dnoeth
)
SELECT * FROM pr_cte;

如果您沒有帶有CTEs 的版本,則始終可以使用子查詢。

結果:

item_id     lag_price   item_price  lead_price       lag_date  change_date  lead_date
     A                        299          249    2000-01-01   2014-08-25  2014-09-01
     A           299          249                 2014-08-25   2014-09-01  2038-01-01
     B                        129          139    2000-01-01   2014-09-01  2014-09-25
     B           129          139                 2014-09-01   2014-09-25  2038-01-01

請注意預設值的使用,2000-01-01並且2038-01-01- 我隱含地假設price表中指定日期之前的價格回到 2000 年,之後的價格將持續到 2038 年,屆時*nix time將爆發!顯然,您可以將這些更改為更適合您的案例的值。再次根據您的案例,我已讓您提供預設價格。

然後,我跑了:

WITH pr_cte AS
(
 SELECT 
   p.item_id, 
   LAG(p.item_price, 1) 
     OVER (PARTITION BY p.item_id ORDER BY p.change_date) AS lag_price,
   p.item_price,
   LEAD(p.item_price, 1) 
     OVER (PARTITION BY p.item_id ORDER BY p.change_date) AS lead_price,
   LAG(change_date, 1, '2000-01-01') 
     OVER (PARTITION BY p.item_id ORDER BY p.change_date) AS lag_date,
   p.change_date,
   LEAD(p.change_date, 1, '2038-01-01') 
     OVER (PARTITION BY p.item_id ORDER BY p.change_date) AS lead_date
 FROM price p
 -- ORDER BY p.item_id, p.change_date -- see above and comment by @dnoeth
)
SELECT 
 pr.item_id, pr.item_price,
 pur.customer_id, pur.item_id, pur.purchase_date
FROM pr_cte pr
JOIN purchase pur 
 ON pr.item_id = pur.item_id
 AND pur.purchase_date >= pr.change_date
 AND pur.purchase_date < pr.lead_date;

結果(再次,按照您認為合適的方式剔除欄位):

item_id     item_price  customer_id     item_id     purchase_date
     A            299            1           A        2014-08-27
     A            249            2           A        2014-09-02

這是,AFAICT,正確答案。您可能想用我可能忽略的邊緣情況進行測試 - 如果有問題,請回复我。+1 提出一個有趣且具有挑戰性的問題,歡迎來到論壇!

根據實際數據/索引,這種方法可能更有效:

WITH cte AS 
( -- combine both tables into one
  SELECT DISTINCT
     item_id
    ,purchase_date
    -- to be filled in the next step
    ,Cast(NULL AS DECIMAL(10,2)) AS item_price
  FROM purchase
  UNION ALL
  SELECT
     item_id
    ,change_date
    ,item_price
  FROM price
)
SELECT
  item_id
 ,purchase_date
 -- fill the NULLs in the purchase rows with the latest price
 ,Last_Value(item_price IGNORE NULLS)
  Over (PARTITION BY item_id
        ORDER BY purchase_date, item_price NULLS LAST) AS item_price
FROM cte
-- only return rows from the purchase table
QUALIFY cte.item_price IS NULL

當然,這僅返回價格,要獲取客戶資訊,您需要將其包裝到另一個 CTE/派生表中並重新加入購買表。

引用自:https://dba.stackexchange.com/questions/256821