【问题标题】:Limit join to one row将连接限制为一行
【发布时间】:2012-09-20 22:49:11
【问题描述】:

我有以下疑问:

SELECT sum((select count(*) as itemCount) * "SalesOrderItems"."price") as amount, 'rma' as     
    "creditType", "Clients"."company" as "client", "Clients".id as "ClientId", "Rmas".* 
FROM "Rmas" JOIN "EsnsRmas" on("EsnsRmas"."RmaId" = "Rmas"."id") 
    JOIN "Esns" on ("Esns".id = "EsnsRmas"."EsnId") 
    JOIN "EsnsSalesOrderItems" on("EsnsSalesOrderItems"."EsnId" = "Esns"."id" ) 
    JOIN "SalesOrderItems" on("SalesOrderItems"."id" = "EsnsSalesOrderItems"."SalesOrderItemId") 
    JOIN "Clients" on("Clients"."id" = "Rmas"."ClientId" )
WHERE "Rmas"."credited"=false AND "Rmas"."verifyStatus" IS NOT null 
GROUP BY "Clients".id, "Rmas".id;

问题是表"EsnsSalesOrderItems" 可以在不同的条目中具有相同的EsnId。我想将查询限制为仅提取"EsnsSalesOrderItems" 中具有相同"EsnId" 的最后一个条目。

“最后一个”条目是指以下内容:

表格中最后出现的那个"EsnsSalesOrderItems"。因此,例如,如果 "EsnsSalesOrderItems" 有两个条目分别为 "EsnId" = 6"createdAt" = '2012-06-19''2012-07-19' 它应该只给我来自 '2012-07-19' 的条目。

【问题讨论】:

    标签: sql postgresql join greatest-n-per-group sql-limit


    【解决方案1】:

    尝试在您的 ON 子句中使用子查询。一个抽象的例子:

    SELECT 
        *
    FROM table1
    JOIN table2 ON table2.id = (
        SELECT id FROM table2 WHERE table2.table1_id = table1.id LIMIT 1
    )
    WHERE 
        ...
    

    【讨论】:

    • LIMIT 1 总共限制为一行。但是我们需要每个"EsnId" 一行。
    • @Erwin Brandstetter:正确,他错过了 LATERAL 关键字。如果他写的是 INNER JOIN LATERAL 而不仅仅是 JOIN,它会起作用(在 PG >= 9.3 上)。但他也应该为 LIMIT 1 添加一个订单。
    【解决方案2】:

    类似:

    join (
      select "EsnId", 
             row_number() over (partition by "EsnId" order by "createdAt" desc) as rn
      from "EsnsSalesOrderItems"
    ) t ON t."EsnId" = "Esns"."id" and rn = 1
    

    这将根据creation_date 列从"EsnsSalesOrderItems" 中选择最新的“EsnId"”。由于您没有发布表的结构,我不得不“发明”一个列名。您可以使用任何列,允许您在适合您的行上定义顺序。

    但请记住,“最后一行”的概念仅在您指定订单或行时才有效。这样的表没有排序,查询的结果也没有除非您指定order by

    【讨论】:

    • 我尝试了这两种方法并进行了如下修改: join ( select "EsnId", "SalesOrderItemId", row_number() over (order by "createdAt" desc) as rn from "EsnsSalesOrderItems" ) t ON t."EsnId" = "Esns"."id" and rn = 1 我需要 "SalesOrderItemId" 所以我可以将它与下一个连接一起使用,因为它会抛出缺少的 "EsnsSalesOrderItems" FROM 子句错误。结果是空的,尽管如此。与下面答案中的查询相同。我是不是做错了什么?
    • 没有EsnsSalesOrderItems "table" 了。它现在称为t。您可以将别名 t 更改为 EsnsSalesOrderItems 或在整个查询中直接使用 t
    • 我的查询与上面相同,除了我用以下内容替换了两个连接语句:join (select "EsnId", "SalesOrderItemId", row_number() over (order by "createdAt" desc) as rn从 "EsnsSalesOrderItems" ) t ON t."EsnId" = "Esns"."id" and rn = 1 JOIN "SalesOrderItems" on("SalesOrderItems"."id" = t."SalesOrderItemId")
    • LIMIT 1 将限制为一行。但是每个"EsnId" 需要一行。因此,带有row_number() 的第一个版本需要PARTITION BY "EsnId" 才能工作。
    • @ErwinBrandstetter:好点。感谢您指出这一点。
    【解决方案3】:
    SELECT (count(*) * sum(s."price")) AS amount
         , 'rma'       AS "creditType"
         , c."company" AS "client"
         , c.id        AS "ClientId"
         , r.* 
    FROM   "Rmas"            r
    JOIN   "EsnsRmas"        er ON er."RmaId" = r."id"
    JOIN   "Esns"            e  ON e.id = er."EsnId"
    JOIN  (
       SELECT DISTINCT ON ("EsnId") *
       FROM   "EsnsSalesOrderItems"
       ORDER  BY "EsnId", "createdAt" DESC
       )                     es ON es."EsnId" = e."id"
    JOIN   "SalesOrderItems" s  ON s."id" = es."SalesOrderItemId"
    JOIN   "Clients"         c  ON c."id" = r."ClientId"
    WHERE  r."credited" = FALSE
    AND    r."verifyStatus" IS NOT NULL 
    GROUP  BY c.id, r.id;
    

    您在问题中的查询与另一个聚合相比具有非法聚合:

    sum((select count(*) as itemCount) * "SalesOrderItems"."price") as amount
    

    简化并转换为合法语法:

    (count(*) * sum(s."price")) AS amount
    

    但是你真的想乘以每组的数量吗?

    我使用 DISTINCT ON"EsnsSalesOrderItems" 中检索每个组的单行。详细解释:

    我还添加了表别名和格式,以使查询更易于人眼解析。如果您可以avoid camel case you could get rid of all the double quotes 使视图蒙上一层阴影。

    【讨论】:

    • 我不是在寻找最后一个 esn(即具有最高 id 的那个)。我正在寻找具有特定“EsnId”的最后一个“EsnsSalesOrderItems”条目
    • @user1175817:当然有一个特定的"EsnId",但是在具有这个特定ID的那些中,你会选择哪个?定义“最后一个”。
    • 最后出现在“EsnsSalesOrderItems”表中的那个。因此,例如,如果“EsnsSalesOrderItems”有两个条目,EsnId=6 和 createdAt = 6-19-2012 和 7-19-2012,它应该只给我 2012 年 7 月 19 日的条目
    • @user1175817:我在更新的答案中使用了它。此信息应包含在问题中以使其完整。
    • @user1175817:是的,我想我现在明白了。将定义“最后一行”的“createdAt”的信息添加到上面的问题中以使其清楚。
    【解决方案4】:

    死灵法,因为答案已经过时。
    利用 PG 9.3

    中引入的 LATERAL 关键字

    左 |对 |内连接 LATERAL

    我会用一个例子来解释:
    假设您有一个“联系人”表。
    现在联系人拥有单位部门。
    他们可以在一个时间点有一个 OU,但在 N 个时间点有 N 个 OU。

    现在,如果您必须在某个时间段(不是报告日期,而是日期范围)查询联系人和 OU,如果您只执行左加入。
    因此,要显示 OU,您只需为每个联系人加入第一个 OU(其中首先是任意标准 - 例如,当取最后一个值时,这只是排序时第一个值的另一种说法按日期降序排列)。

    在 SQL-server 中,您将使用交叉应用(或者更确切地说是 OUTER APPLY,因为我们需要左连接),这将在它必须连接的每一行上调用一个表值函数。

    SELECT * FROM T_Contacts 
    
    --LEFT JOIN T_MAP_Contacts_Ref_OrganisationalUnit ON MAP_CTCOU_CT_UID = T_Contacts.CT_UID AND MAP_CTCOU_SoftDeleteStatus = 1 
    --WHERE T_MAP_Contacts_Ref_OrganisationalUnit.MAP_CTCOU_UID IS NULL -- 989
    
    -- CROSS APPLY -- = INNER JOIN 
    OUTER APPLY    -- = LEFT JOIN 
    (
        SELECT TOP 1 
             --MAP_CTCOU_UID    
             MAP_CTCOU_CT_UID   
            ,MAP_CTCOU_COU_UID  
            ,MAP_CTCOU_DateFrom 
            ,MAP_CTCOU_DateTo   
       FROM T_MAP_Contacts_Ref_OrganisationalUnit 
       WHERE MAP_CTCOU_SoftDeleteStatus = 1 
       AND MAP_CTCOU_CT_UID = T_Contacts.CT_UID 
    
        /*  
        AND 
        ( 
            (@in_DateFrom <= T_MAP_Contacts_Ref_OrganisationalUnit.MAP_KTKOE_DateTo) 
            AND 
            (@in_DateTo >= T_MAP_Contacts_Ref_OrganisationalUnit.MAP_KTKOE_DateFrom) 
        ) 
        */
       ORDER BY MAP_CTCOU_DateFrom 
    ) AS FirstOE 
    

    在 PostgreSQL 中,从 9.3 版开始,您也可以这样做 - 只需使用 LATERAL 关键字即可:

    SELECT * FROM T_Contacts 
    
    --LEFT JOIN T_MAP_Contacts_Ref_OrganisationalUnit ON MAP_CTCOU_CT_UID = T_Contacts.CT_UID AND MAP_CTCOU_SoftDeleteStatus = 1 
    --WHERE T_MAP_Contacts_Ref_OrganisationalUnit.MAP_CTCOU_UID IS NULL -- 989
    
    
    LEFT JOIN LATERAL 
    (
        SELECT 
             --MAP_CTCOU_UID    
             MAP_CTCOU_CT_UID   
            ,MAP_CTCOU_COU_UID  
            ,MAP_CTCOU_DateFrom 
            ,MAP_CTCOU_DateTo   
       FROM T_MAP_Contacts_Ref_OrganisationalUnit 
       WHERE MAP_CTCOU_SoftDeleteStatus = 1 
       AND MAP_CTCOU_CT_UID = T_Contacts.CT_UID 
    
        /*  
        AND 
        ( 
            (__in_DateFrom <= T_MAP_Contacts_Ref_OrganisationalUnit.MAP_KTKOE_DateTo) 
            AND 
            (__in_DateTo >= T_MAP_Contacts_Ref_OrganisationalUnit.MAP_KTKOE_DateFrom) 
        ) 
        */
       ORDER BY MAP_CTCOU_DateFrom 
       LIMIT 1 
    ) AS FirstOE 
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2010-10-04
      • 2018-03-09
      • 1970-01-01
      • 2012-07-08
      • 2011-01-21
      • 2013-06-15
      • 1970-01-01
      相关资源
      最近更新 更多