Received: from malur.postgresql.org ([217.196.149.56]) by arkaria.postgresql.org with esmtps (TLS1.3) tls TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (Exim 4.94.2) (envelope-from ) id 1ubfj7-003nD6-Hc for pgsql-performance@arkaria.postgresql.org; Tue, 15 Jul 2025 13:29:45 +0000 Received: from localhost ([127.0.0.1] helo=malur.postgresql.org) by malur.postgresql.org with esmtp (Exim 4.94.2) (envelope-from ) id 1ubfj5-0026Xe-7a for pgsql-performance@arkaria.postgresql.org; Tue, 15 Jul 2025 13:29:43 +0000 Received: from magus.postgresql.org ([2a02:c0:301:0:ffff::29]) by malur.postgresql.org with esmtps (TLS1.3) tls TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (Exim 4.94.2) (envelope-from ) id 1ubfj4-0026XW-TL for pgsql-performance@lists.postgresql.org; Tue, 15 Jul 2025 13:29:43 +0000 Received: from mail-yb1-xb2f.google.com ([2607:f8b0:4864:20::b2f]) by magus.postgresql.org with esmtps (TLS1.3) tls TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 (Exim 4.96) (envelope-from ) id 1ubfj2-007smt-35 for pgsql-performance@postgresql.org; Tue, 15 Jul 2025 13:29:42 +0000 Received: by mail-yb1-xb2f.google.com with SMTP id 3f1490d57ef6-e818a572828so3749961276.1 for ; Tue, 15 Jul 2025 06:29:40 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1752586178; x=1753190978; darn=postgresql.org; h=to:subject:message-id:date:from:mime-version:from:to:cc:subject :date:message-id:reply-to; bh=w/3ISGKfb8QXexKnRe1Ec/KnLSInxTFln0dtLnOSUak=; b=MHPlitQT43qEjCSEpmWdrcFGIoHIeH7f8exlKOur+J+yToVq9SKcOoFzjPnNmktej9 RyN0iTQ1H8g1ZtUXWH8AWJF89vADYzKZBBePYsl7Ez5TcHCqYkONPYMYimljAdnskWMA V79y7JIblwyJVTZ+wqPY5OIrS251Hw0pIKOjoITdz2BdJ6vnasyELSAe43PxG0FRP6hl YdZeJSqzzyomCe4tNtoKxpPojAOp4ei8FrtZNQc7uV/5+bkSlWOrTQRQb+FvTfIgNlA/ 8vdCjZXrVCVyfpm524vb1W0SKtLTrRQSiFbsKOkR+6xJtuwNw/vtDKjA/73MRMidZZnz vyJw== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1752586178; x=1753190978; h=to:subject:message-id:date:from:mime-version:x-gm-message-state :from:to:cc:subject:date:message-id:reply-to; bh=w/3ISGKfb8QXexKnRe1Ec/KnLSInxTFln0dtLnOSUak=; b=L4kJhlsot1jFBTe/ZILwYeCzRWelKGfc5ES1u3UGyB33PLNR+PI3N3045zHPEDXh4a VuQr9c4HJHyWHDcrUlWaLURnJzIcYsr7rAFsHrvQdnJ5/7TcKOWJ7Ltz75b3CcK7b1UB VnSyuM5cVAOPyg7YiUm/riErLs7MtnxzyUrumKwrqq5ufHxQWr+/AMsMPNlpRMEfB6E2 NKjs9GBwYJ7QErgqYsiO4eKmms3qhsfBoQVQz8R9IKDFlqX6bmYOnJ6SZp5KWb3Sd2ay jMf0Sz88JL4h/2SDiTMyFDkWV9321KhJZJWF9FsKHiegunudO2ZQP5qCqYHzJlMV+OtW O8iA== X-Gm-Message-State: AOJu0YxYjIwVZmxubqImsv3SyqfPkvcZ2cGHvZzvDSA7DbYhEJIWQ2rc W6+mO52s1lN/E7I6pL7ULVQ0LoutCIktlBzVVBV0u4GpIFShfO5b6F8Y+Yx1s136n45tIn0LZFS n1JxZCuF+QuBcxym6IVNNQH2m1BvjLBdh+hUn X-Gm-Gg: ASbGncv1OgRXSnfdl3ooQaR9IrdHcnIyGQKikDeJwIOqr8Ke/SvGfXi4dTlGe/GeTRd OoDEzExII5rrxWxEMjjfdsF7itUG1GRL3s1xCVvVGXMcBrUnBKn9VcEHn9IXSmCC3JMBd2Aeo8m L3CWu0BMWrt+1CD46AvhQg7zUMcRoK4ZCNT9ltAcqDWU5RtF1QUZ8NN+CbQTSlEEh8NTk2S2pwX uA6l5P5UgClwnpJ/M5I X-Google-Smtp-Source: AGHT+IFo93OiUpsDr1EUUsSc9IXl1geiGHf13nU1wmyi9185niwp+60ANIoyXvYzmS79tIgp3G0u3Wc9pA4CYeGqwOE= X-Received: by 2002:a05:690c:9b10:b0:710:f39f:a2bc with SMTP id 00721157ae682-717d5b7496amr231785067b3.8.1752586178102; Tue, 15 Jul 2025 06:29:38 -0700 (PDT) MIME-Version: 1.0 From: Clemens Eisserer Date: Tue, 15 Jul 2025 15:29:27 +0200 X-Gm-Features: Ac12FXzrWwaWwhbZdXhj4lgolr_AALACyWYIKWeLPiyFXCKOCcnRPuGJ3j18R-Y Message-ID: Subject: Any way to get nested loop index joins on CTEs? To: pgsql-performance@postgresql.org Content-Type: text/plain; charset="UTF-8" List-Id: List-Help: List-Subscribe: List-Post: List-Owner: List-Archive: Archived-At: Precedence: bulk Hi, I am using generate_series + a join to group time-series data into buckets, which works well as long as I do this only for one aggregation hierarchy: The index on the timestamp of the table with the actual time-series data is used for a nested loop index join. However with more aggregation levels (one aggregation stage consuming the output of the previous one) chained together using CTEs, there is no index available and postgresql falls back to (no-index) nested loop joins. Example, with the actual access to the data-table replaced, as there is no index it triggers the nested loop join immediatly (despite MATERIALIZED + ORDER BY): WITH series1h AS MATERIALIZED (SELECT generate_series AS ts FROM generate_series('1990-01-01 00:00', '1990-12-31 23:59', INTERVAL '1 hour') ORDER BY ts), series15m AS MATERIALIZED (SELECT generate_series AS ts FROM generate_series('1990-01-01 00:00', '1990-12-31 23:59', INTERVAL '15 minutes') ORDER BY ts) SELECT count(*) FROM (SELECT h1.ts, count(*) FROM series1h h1 JOIN series15m m15 ON (m15.ts > (h1.ts - INTERVAL '1 hour') AND m15.ts <= h1.ts ) GROUP BY h1.ts ORDER BY h1.ts); date_bin would allow to join on equality, however according to the docs it doesn't support months/years: The stride interval must be greater than zero and cannot contain units of month or larger. For now I am using temporary tables which can be indexed, are there ways to avoid them? Thanks, Clemens