如何在最近的前一天加入?
想像一下,我有一個包含歷史價格數據的數據庫和一個包含日期的第二個表
每次價格變化時,都會在價格歷史表中輸入一個新行。
我怎樣才能(有效地)連接這兩個表,以便我可以獲得每個客戶當天必須支付的價格?
我需要 teradata 的語法,謝謝!
恐怕我不能保證我的答案 100% 兼容 Teradata,因為據我所知,沒有可用的 Teradata 小提琴。但是,我使用了 PostgreSQL,它是更符合標準的系統之一,並且在我的回答中沒有使用 PostgreSQL 特定的構造(見小提琴
here
),所以我認為我們很高興!
LAG and LEAD
我瀏覽了有關 Window 函式 ( ) 和 Common Table Expressions ( s)的 Teradata 文件,CTE
並將其與 PostgreSQL 的語法 (Window
functions,CTE
s) 進行了比較,這一切看起來都相當普通。我做的第一件事是
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');
然後我執行了以下查詢 - 我留下了最終計算不需要的無關欄位,以便可以遵循我的思維過程 - 使用
LAG
andLEAD
可能很棘手,而且很容易混淆你應該比較的欄位哪一個,所以我的政策是將它們全部保留,讓 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;
如果您沒有帶有
CTE
s 的版本,則始終可以使用子查詢。結果:
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/派生表中並重新加入購買表。