【问题标题】:Select users who did a purchase after a search where some conditions apply in PostgreSQL?选择在 PostgreSQL 中适用某些条件的搜索后购买的用户?
【发布时间】:2013-09-17 19:01:42
【问题描述】:

我有这种情况:用户在网站上进行搜索,其中一些人购买了。在购买条件为真且搜索会话 = 购买会话

的情况下,您如何选择 18 岁以上进行了搜索而没有以下购买的用户

这会选择具有搜索事件且年龄超过 18 岁的用户。

select DISTINCT ON (id) id, email, users.age, events.type, events.createdon 
from users 
   LEFT JOIN events ON events.user = users.users 
where events.type='search' 
  and age>18 
  and events.condition is true

如何添加他们之前在同一会话中进行过搜索事件的条件?

事件的表结构:

-----------------------------------------------------
| user |   event   |    date    | condition | session |
------------------------------------------------------|
|  1   |  search   | 08-10-2013 |   true    |    A    |
|  1   |  search   | 08-10-2013 |   true    |    A    |
|  2   |  search   | 08-10-2013 |   false   |    B    |
|  2   | purchase  | 09-10-2013 |   false   |    A    |
|  2   |  search   | 09-10-2013 |   true    |    C    |
|  1   | purchase  | 09-10-2013 |   true    |    A    |
|  3   |  search   | 09-10-2013 |   false   |    D    |
|  2   |  search   | 10-10-2013 |   true    |    H    |
|  4   |  search   | 10-10-2013 |   false   |    E    |
|  4   |  search   | 10-10-2013 |   false   |    E    |
|  3   |  search   | 11-10-2013 |   true    |    D    |
|  2   |  other    | 11-10-2013 |   true    |    H    |
|  1   |  search   | 11-10-2013 |   true    |    F    |
|  1   | purchase  | 12-10-2013 |   true    |    F    |
|  3   | purchase  | 12-10-2013 |   false   |    D    |
|  4   |  search   | 12-10-2013 |   true    |    G    |    
|  2   |  other    | 12-10-2013 |   true    |    A    |   
-----------------------------------------------------

而用户表是:

------------------------
| user | email  | age  |
------------------------
|  1   |   a    |  22  |
|  2   |   b    |  34  |
|  3   |   c    |  15  |
|  4   |   d    |  44  |
|  5   |   e    |  39  |
------------------------

结果应该是 2 和 4:

1    a   -> NO because did purchase with condition=true
2    b   -> YES because did search, did purchase BUT condition=false
3    c   -> NO because did search, did purchase with condition=false BUT age<18
4    d   -> YES because did search and no purchase 

谢谢,我刚从 Mongo 开始使用 Postgres,而且好多了!

更新:例如更正的结果

【问题讨论】:

  • 不相关,但是:使用distinct on 而不按顺序不是一个好主意。
  • 能否添加预期结果?
  • 是的,结果应该是 1 和 3。2 不正确,因为购买的看到与搜索中的会话不同
  • 对不起,只有 1 个是正确的,因为 3 个年龄是 15 岁
  • 您写道:search without a following purchase,但在您的结果中,第一个用户在同一会话中有搜索和购买操作。对吗?

标签: sql postgresql


【解决方案1】:

如果您只需要来自users 表的数据:

select
    u.*
from users as u
where
    u.age > 18 and
    exists (
        select *
        from events as e1
        where 
            e1.user = u.user and e1.condition is true and
            e1.event = 'search' and
            not exists (
                select *
                from events as e2
                where
                    e2.user = u.user and e2.condition is true and
                    e2.event = 'purchase' and
                    e2.session = e1.session and e2.date > e1.date
            )
    )

如果也可以这样做:

select
    u.*
from users as u
where
    u.age > 18 and
    exists (
        select *           
        from (
            select
                max(case when e.event = 'search' then e.date end) as search_m_date,
                max(case when e.event = 'purchase' then e.date end) as purchase_m_date
            from events as e
            where e.user = u.user and e.condition is true
            group by e.session
        ) as a
        where
            a.search_m_date is not null and
            (a.purchase_m_date is null or a.search_m_date > a.purchase_m_date)
    )

但其实我更喜欢第一个

sql fiddle demo

【讨论】:

  • ...和exists 应该改成not exists。
  • 无论如何更改了查询 :) 前一个可以与 not exists 一起使用,但我们必须检查用户是否确实进行了搜索(或者用户 5 将在结果中)
  • 我使用了第一个,它可以工作。有没有办法优化数百万行的查询? 5M上大概需要6-7秒,才刚刚开始。
  • 你必须建立适当的索引来优化它
【解决方案2】:

这是逐步构建完整查询的一种方法:

  1. 获取所有搜索事件:

    SELECT *
    FROM events AS s
    WHERE event = 'search'
      AND condition IS TRUE
    
  2. 过滤掉成功购买事件之后的那些:

    SELECT *
    FROM
      events AS s
    LEFT JOIN
      events AS p
      ON  s.user = p.user
      AND s.session = p.session
      AND p.event = 'purchase'
      AND p.condition IS TRUE
      AND p.date > s.date  -- this assumes that `date` stores both date and time
    WHERE s.event = 'search'
      AND s.condition IS TRUE
      AND p.event IS NULL
    
  3. 从users表中获取上一个结果集中返回ID且年龄大于18岁的用户:

    SELECT *
    FROM users
    WHERE age > 18
      AND user IN (
        SELECT
          s.user
        FROM
          events AS s
        LEFT JOIN
          events AS p
          ON  s.user = p.user
          AND s.session = p.session
          AND p.event = 'purchase'
          AND p.condition IS TRUE
          AND p.date > s.date
        WHERE s.event = 'search'
          AND s.condition IS TRUE
          AND p.event IS NULL
      )
    ;
    

这应该会给您想要的结果,尽管不一定以最有效的方式。您可以使用反连接,在此查询中使用LEFT JOIN + WHERE IS NULL 技术实现,用NOT EXISTS 重写它,看看它是否表现更好。此外,您可以尝试用等效的 EXISTS 重写 IN 谓词。 (基本上,@Roman Pekar 的第一个解决方案可以看作是建议的两次重写的结果。)

【讨论】:

  • 感谢您的回复。实际上,我从第 3 点开始使用您的 LEFT JOIN 示例改进了我的查询。查询时间并没有降低 55% :)
猜你喜欢
  • 2015-09-26
  • 1970-01-01
  • 2023-03-11
  • 2022-10-12
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-05-19
相关资源
最近更新 更多