2016-07-28 38 views
1

加入大表的表mydat具有围绕48.3M记录与定义:与主键

┌────────────────┬──────────────┬───────────┐ 
│  Column  │  Type  │ Modifiers │ 
├────────────────┼──────────────┼───────────┤ 
│ id    │ bigint  │ not null │ 
│ dt    │ integer  │ not null │ 
│ data   │ real   │   │ 
└────────────────┴──────────────┴───────────┘ 
Indexes: 
    "mydat_pkey" PRIMARY KEY, btree (id, dt) 

对于每个对象所确定id,大约有40记录所表示由dt时间字段。我们的目标是检查连续记录之间的变化模式,并且实现是将每个记录与其下一个记录相连,每个记录基于每个id基于dt。查询如下:

SELECT * 
    FROM mydat AS dat1 
    JOIN mydat AS dat2 
    ON dat1.id = dat2.id 
    AND dat1.dt = dat2.dt - 1; 

查询计划如下。 合并加入被使用,并且它永远运行。另外,我们可以看到行数427198811严重高估。似乎postgresql没有考虑到(id,dt)的唯一性。

┌───────────────────────────────────────────────────────────────────────────────────────────────┐ 
│           QUERY PLAN           │ 
├───────────────────────────────────────────────────────────────────────────────────────────────┤ 
│ Merge Join (cost=19919125.46..25466155.03 rows=247144681 width=222)       │ 
│ Merge Cond: ((dat1.id = dat2.id) AND (dat1.dt = ((dat2.dt - 1))))       │ 
│ -> Sort (cost=9959562.73..10080389.92 rows=48330876 width=111)       │ 
│   Sort Key: dat1.id, dat1.dt               │ 
│   -> Seq Scan on mydat dat1 (cost=0.00..982694.76 rows=48330876 width=111)   │ 
│ -> Materialize (cost=9959562.73..10201217.11 rows=48330876 width=111)      │ 
│   -> Sort (cost=9959562.73..10080389.92 rows=48330876 width=111)      │ 
│    Sort Key: dat2.id, ((dat2.dt - 1))            │ 
│    -> Seq Scan on mydat dat2 (cost=0.00..982694.76 rows=48330876 width=111)  │ 
└───────────────────────────────────────────────────────────────────────────────────────────────┘ 

出于好奇,这里是与自身幼稚加盟mydat

SELECT * 
    FROM mydat AS dat1 
    JOIN mydat AS dat2 
    ON dat1.id = dat2.id 
    AND dat1.dt = dat2.dt; 

的查询计划是相似的:

┌───────────────────────────────────────────────────────────────────────────────────────────────┐ 
│           QUERY PLAN           │ 
├───────────────────────────────────────────────────────────────────────────────────────────────┤ 
│ Merge Join (cost=19919125.46..27878413.41 rows=427198811 width=222)       │ 
│ Merge Cond: ((dat1.id = dat2.id) AND (dat1.dt = dat2.dt))         │ 
│ -> Sort (cost=9959562.73..10080389.92 rows=48330876 width=111)       │ 
│   Sort Key: dat1.id, dat1.dt               │ 
│   -> Seq Scan on act_2003q1 dat1 (cost=0.00..982694.76 rows=48330876 width=111)  │ 
│ -> Materialize (cost=9959562.73..10201217.11 rows=48330876 width=111)      │ 
│   -> Sort (cost=9959562.73..10080389.92 rows=48330876 width=111)      │ 
│    Sort Key: dat2.id, dat2.dt              │ 
│    -> Seq Scan on act_2003q1 dat2 (cost=0.00..982694.76 rows=48330876 width=111) │ 
└───────────────────────────────────────────────────────────────────────────────────────────────┘ 

这样的查询计划我迷惑不解。在这里,我的问题是这些用例的最佳实践是什么?谢谢。

上述查询在Windows上的Postgresql 9.5.3和Linux上的9.4.6上测试:结果类似。


鉴于@欧文的建议下,在滞后窗函数进行了测试,结果比初始合并连接方式要好得多:511524ms完成查询。正如Erwin指出的那样,查询与原始查询不完全相同。特别是如果在dt字段中存在间隙,那么一些记录将是不希望的。

这是一个例子,我发现表分区是有益的,因为我使用的数据集比上面给出的例子大。问题的底线是postgresql使用磁盘对所有记录进行排序,并且两个查询都不使用索引。

+0

而不是'select * ...'尝试'选择id,dt ...' – Hogan

+0

@Hogan,刚试过,没有什么区别。仍将合并与排序合并。 –

+0

'我的目标是加入每条记录和每个记录的下一个记录'。这不是一个目标,那是一个实现细节。你的实际目标是什么?并且请*总是*包含您的Postgres版本。 –

回答

0

我不知道PostgreSQL的那么好,但是这将是魔术在DB2和MSSQL

WITH get_em as 
(SELECT id, dt 
    FROM mydat AS dat1 
    JOIN mydat AS dat2 
    ON dat1.id = dat2.id 
    AND dat1.dt = dat2.dt - 1 
) 
select mydat.* 
from mydat 
join get_em on ON mydat.id = get_em.id AND mydat.dt = get_em.dt 
+0

感谢您的回答!我刚刚尝试了代码,但它不起作用。 –

+0

@opensrc - 好的,如果你不告诉我更多,我不能帮忙。 – Hogan

2

如果实际的目标是有data每个结果行中的前值,然后用window function

SELECT *, lag(data) OVER (PARTITION BY id ORDER BY dt) AS last_data 
FROM mydat; 

区别:没有前辈的行仍包括在内。你可能会也可能不想要。要排除,使用子查询:

SELECT * 
FROM (
    SELECT *, lag(data) OVER (PARTITION BY id ORDER BY dt) AS last_data 
    FROM mydat 
    ) t 
WHERE last_data IS NOT NULL; 

残存极端情况是这样的:如果data可以为空,我们不能告诉一个真正的NULL值和“未发现”的区别。因此,使用不同的不可能默认值一样表现出“未找到”的情况:

SELECT * 
FROM (
    SELECT *, lag(data, 1, '-infinity') OVER (PARTITION BY id ORDER BY dt) AS last_data 
    FROM mydat 
    ) t 
WHERE last_data IS DISTINCT FROM '-infinity'; 

每个查询只需要一个顺序扫描

+0

感谢您的回答。我会做一个彻底的测试,然后报告回来。 –