我有一個查詢,它從特定時間范圍內的不同表中收集資訊。
目前,我分別為每個用戶和每個日期范圍發出請求,但我想一次在所有時間范圍內運行它,其中時間范圍是 user_opened_account_at 和 user_closed_account_at 之間的每 7 天,每個用戶都不同。
在一個查詢中是否有任何正確的方法可以做到這一點?
示例:
我想看到的結果:

詢問:
SELECT
usr.id as user_id,
usr."onboardedAt" as user_opened_account_at,
usr."closedAt" as user_closed_account_at,
'2021-01-01' as start_range_date,
'2021-01-08' as end_range_date,
tx.tx_count as tx_count,
last_user_action.action as last_user_action
FROM "Users" usr
LEFT JOIN (
SELECT
"userId",
COUNT("id") as "tx_count"
FROM "Transactions"
WHERE "createdAt" >= '2021-01-01' AND "createdAt" < '2021-01-08'
GROUP BY "userId"
) tx ON usr.id = tx."userId"
LEFT JOIN (
SELECT "userId", "action"
FROM "UserActions"
WHERE "createdAt" >= '2021-01-01' AND "createdAt" < '2021-01-08'
ORDER BY "createdAt" DESC
LIMIT 1
) last_user_action ON usr.id = last_user_action."userId"
WHERE usr.id = 1
ORDER BY user_id, start_range_date
架構:
CREATE TABLE "Users" (
id bigserial PRIMARY KEY,
"onboardedAt" timestamp with time zone,
"closedAt" timestamp with time zone
);
CREATE TABLE "Transactions" (
id bigserial PRIMARY KEY,
"userId" bigint,
"createdAt" timestamp with time zone,
amount numeric(20,8) NOT NULL DEFAULT 0
);
CREATE TABLE "UserActions" (
id bigserial PRIMARY KEY,
"userId" bigint,
"createdAt" timestamp with time zone,
action character varying(255) NOT NULL
);
INSERT INTO "Users" ("onboardedAt", "closedAt") VALUES
( '2021-01-01', '2021-02-01' ),
( '2021-01-01', '2021-02-01' ),
( '2021-01-01', '2021-02-01' ),
( '2021-02-01', '2021-03-01' ),
( '2021-02-01', '2021-03-01' );
INSERT INTO "Transactions" ("userId", "createdAt", "amount") VALUES
( 1, '2021-01-02', 100 ),
( 1, '2021-01-08', -100 ),
( 1, '2021-01-15', -200 ),
( 1, '2021-01-22', 200 ),
( 2, '2021-01-02', -100 ),
( 2, '2021-01-02', 100 ),
( 2, '2021-01-15', -200 ),
( 2, '2021-01-16', 200 ),
( 3, '2021-01-02', 100 ),
( 3, '2021-01-08', -100 ),
( 3, '2021-01-15', -200 ),
( 3, '2021-01-22', 200 ),
( 4, '2021-02-02', 50 ),
( 4, '2021-02-08', -100 ),
( 4, '2021-02-15', -200 ),
( 4, '2021-02-22', 200 ),
( 5, '2021-02-02', 200 ),
( 5, '2021-02-08', -400 ),
( 5, '2021-02-15', -600 ),
( 5, '2021-02-22', 200 );
INSERT INTO "UserActions" ("userId", "createdAt", "action") VALUES
( 1, '2021-01-01', 'PLAY' ),
( 1, '2021-01-01', 'PLAY' ),
( 1, '2021-01-02', 'DEPOSIT' ),
( 1, '2021-01-08', 'DEPOSIT' ),
( 1, '2021-01-09', 'PLAY' ),
( 1, '2021-01-15', 'PLAY' ),
( 1, '2021-01-22', 'PLAY' ),
( 2, '2021-01-01', 'PLAY' ),
( 2, '2021-01-01', 'PLAY' ),
( 2, '2021-01-02', 'DEPOSIT' ),
( 2, '2021-01-08', 'DEPOSIT' ),
( 2, '2021-01-09', 'PLAY' ),
( 2, '2021-01-15', 'PLAY' ),
( 2, '2021-01-22', 'PLAY' ),
( 3, '2021-01-01', 'PLAY' ),
( 3, '2021-01-01', 'PLAY' ),
( 3, '2021-01-02', 'DEPOSIT' ),
( 3, '2021-01-08', 'DEPOSIT' ),
( 3, '2021-01-09', 'PLAY' ),
( 3, '2021-01-15', 'PLAY' ),
( 3, '2021-01-22', 'PLAY' ),
( 4, '2021-02-01', 'DEPOSIT' ),
( 4, '2021-02-01', 'PLAY' ),
( 4, '2021-02-02', 'DEPOSIT' ),
( 4, '2021-02-08', 'DEPOSIT' ),
( 4, '2021-02-09', 'PLAY' ),
( 4, '2021-02-15', 'PLAY' ),
( 4, '2021-02-22', 'PLAY' ),
( 5, '2021-02-01', 'DEPOSIT' ),
( 5, '2021-02-01', 'PLAY' ),
( 5, '2021-02-02', 'PLAY' ),
( 5, '2021-02-08', 'PLAY' ),
( 5, '2021-02-09', 'PLAY' ),
( 5, '2021-02-15', 'DEPOSIT' ),
( 5, '2021-02-22', 'PLAY' );
uj5u.com熱心網友回復:
當然。您必須使用LATERAL 連接,以便您可以在 generate_series() 表運算式中使用左表(用戶)中的列值,但除此之外,它主要是您所期望的。一些簡化的 SQL 顯示了以下重要部分,如果您想要完整作業的代碼,請添加帶有示例資料的 dbfiddle 鏈接。
SELECT u.user_id, week_start, count(t.transactions) tx_count
from users AS u
CROSS JOIN LATERAL generate_series(u.onboarded_at, u.account_closed_at, interval '1 week')
AS week_start
LEFT JOIN transactions AS t
ON t.created_at >= week_start AND
AND t.created_at < (week_start interval '1 week')
GROUP BY 1, 2;
請注意,這仍然主要是一個美化的 for 回圈服務器端,但這幾乎總是比代碼中往返資料庫的 for 回圈的性能要高得多。
uj5u.com熱心網友回復:
從星期一開始所有的星期,這將(有效地)做到這一點:
SELECT id AS user_id, u."onboardedAt", u."closedAt"
, week_start, COALESCE(t.tx_count, 0) AS tx_count, a.last_user_action
FROM "Users" u
CROSS JOIN generate_series(date_trunc('week', u."onboardedAt"), u."closedAt", interval '1 week') AS week_start
LEFT JOIN (
SELECT "userId" AS id, date_trunc('week', t."createdAt") AS week_start, count(*) AS tx_count
FROM "Transactions" t
GROUP BY 1, 2
) t USING (id, week_start)
LEFT JOIN (
SELECT DISTINCT ON (1, 2)
"userId" AS id, date_trunc('week', a."createdAt") AS week_start, action AS last_user_action
FROM "UserActions" a
ORDER BY 1, 2, "createdAt" DESC
) a USING (id, week_start)
ORDER BY id, week_start;
db<>在這里擺弄
使用標準周使一切變得更加簡單。我們可以在加入之前在“許多”表中進行聚合,這樣更簡單也更便宜。否則,多個連接可能會很快出錯。看:
- 兩個 SQL LEFT JOINS 產生不正確的結果
標準周也可以更輕松地比較資料。(請注意,每個用戶的第一周和最后一周可以被截斷(跨越更少的天數)。但這在任何情況下都適用于每個用戶的最后一周。)
該LATERAL關鍵字被自動認為在加入集合到一組回傳功能:
CROSS JOIN generate_series(...)
看:
- LATERAL JOIN 和 PostgreSQL 中的子查詢有什么區別?
使用DISTINCT ON以獲得last_user_action每個用戶。看:
- 選擇每個 GROUP BY 組中的第一行?
我建議用戶使用合法的小寫識別符號,因此不需要雙引號。使用 Postgres 讓您的生活更輕松。
轉載請註明出處,本文鏈接:https://www.uj5u.com/ruanti/348283.html
