需要帮助包裹头部连接

时间:2016-11-18 14:06:20

标签: sql sql-server

我有一个帮助人们销售东西的服务数据库。如果他们未能交付销售,他们将受到处罚。我试图提取每个用户在应用特定惩罚时所拥有的活动列表的数量。

我有相当于下表(和相关字段):

  1. user(id)
  2. 列出(id,user_id,status)
  3. 交易(listing_id,seller_id)
  4. listing_history(id,listing_status,date_created)
  5. 惩罚(id,transaction_id,user_id,date_created)
  6. 每次修改商家信息时,listing_history表都会保存一个条目,并保存一份新商品的新状态记录。

    我的目标是以一个带有字段的结果表结束:penalty_id,以及惩罚用户在应用惩罚时所拥有的有效列表数。

    到目前为止,我有以下内容:

    SELECT s1.penalty_id, 
      COUNT(s1.record_id) 'active_listings'
    FROM  (
      SELECT penalty.id AS 'penalty_id',
        listing_history.id AS 'record_id',
      FROM user
        JOIN penalty ON penalty.user_id = user.id
        JOIN transaction ON transaction.id = penalty.transaction_id
        JOIN listing_history ON listing_history.listing_id = listing.id
      WHERE listing_history.date_created < penalty.date_created
        AND listing_history.status = 0
    ) s1
    GROUP BY s1.penalty_id
    

    Status = 0表示列表处于活动状态(或者列表在创建记录时处于活动状态)。我得到的结果与我的预期相似,但我担心我可能会遗漏某些东西,或者可能会错误地加入JOIN。这有你的认可吗? (除了明显不使用别名之外,为了清晰度问题)。

1 个答案:

答案 0 :(得分:2)

更新 - 由于对此答案的评论表明更改表格结构不是一个选项,以下是您可以使用现有结构的一些查询的更多详细信息。

请注意,在修改逻辑之前,我对查询进行了一些更改。

  • 正如viki888指出的那样,listing.id引用了一个问题;我已经取代了它。
  • 原始查询中不需要子查询;我已将其简化了。

因此原始查询被重写为

SELECT penalty.id AS 'penalty_id'
     , COUNT(listing_history.id) 'active_listings'
  FROM      user
       JOIN penalty
         ON penalty.user_id = user.id
       JOIN transaction
         ON transaction.id = penalty.transaction_id
       JOIN listing_history 
         ON listing_history.listing_id = transaction.listing_id
 WHERE listing_history.date_created < penalty.date_created
   AND listing_history.status = 0
 GROUP BY penalty.id

在我看来,现在最自然的方式是编写更正的时间轴约束,其中NOT EXISTS条件会过滤除给定listing_history以外的所有id条记录。 。这需要考虑一些边缘情况:

  • 两个列表历史记录是否具有相同的创建日期?如果是这样,你如何决定先发生了什么?
  • 如果在惩罚的同一天创建了一个列表历史记录,这被视为首先发生?

如果created_date确实是一个时间戳,那么这可能并不重要(如果有的话);如果真的是约会,那可能是一个更大的问题。由于您的原始查询要求在处罚之前创建列表历史记录,因此我将继续使用该格式;但是如何处理两个具有匹配状态的历史记录具有相同日期的情况仍然不明确。您可能需要调整日期比较以获得所需的行为。

SELECT penalty.id AS 'penalty_id'
     , COUNT(DISTINCT listing_history.id) 'active_listings'
  FROM      user
       JOIN penalty
         ON penalty.user_id = user.id
       JOIN transaction
         ON transaction.id = penalty.transaction_id
       JOIN listing_history 
         ON listing_history.listing_id = transaction.listing_id
 WHERE listing_history.date_created < penalty.date_created
   AND listing_history.status = 0
   AND NOT EXISTS (SELECT 1
                     FROM listing_history h2
                    WHERE listing_history.date_created < h2.date_created
                      AND h2.date_created < penalty.date_created
                      AND h2.id = listing_history.id)
 GROUP BY penalty.id

请注意,我已从COUNT(...)切换到COUNT(DISTINCT ...);这有助于处理可能计算同一列表的两个活动记录的边缘情况。

如果您将日期比较更改为使用<=而非< - 或者等效地,如果您使用BETWEEN来组合日期比较 - 那么您想要将AND h2.status != 0(或AND h2.status <> 0(取决于您的数据库)添加到子查询中,以便两个并发的ACTIVE记录不会相互抵消。

有几种等效的方法来编写它,不幸的是它的查询类型并不总是与数据库查询优化器配合使用,因此可能需要进行一些试验和错误才能使其在大数据量下运行良好。希望能够充分了解预期的逻辑,如果需要,您可以找到一些等价物。您可以考虑使用NOT IN代替NOT EXISTS;或者你可以使用外部联接到LISTING_HISTORY的第二个实例......可能有其他人我没有想到手。

我不知道我们能够在一般性陈述上签字,即查询是否正确&#34;正确&#34;。如果某个问题是关于某个查询是否会在特定情况下包含/排除某个记录(或者为什么会这样做/没有,或者如何对其进行修改以使其赢得&t; /将会),那么那些可能会得到更完整的答案。

我可以说有几个可能的问题:

唯一明显的逻辑问题与时间线管理有关,这会给SQL带来很多麻烦。问题是,虽然您的查询表明该列表在惩罚创建日期之前的某个时间点处于活动状态,但它并未证明该列表在惩罚创建日期仍然处于活动状态。考虑

PENALTY
id             transaction    date
1              10             2016-02-01

TRANSACTION
id             listing_id
10             100

LISTING_HISTORY
listing_id     status         date
100            0              2016-01-01
100            1              2016-01-15

连接将创建一个记录,惩罚1的计数将包括列出100,即使在创建惩罚之前其状态已更改为0以外的其他值。

使用现有的表结构修复很难 - 但并非不可能。您可以添加NOT EXISTS条件,查找与ID匹配的另一条LISTING_HISTORY记录,以及第一个LISTING_HISTORY日期和PENALTY日期之间的日期。

将结束日期添加到LISTING_HISTORY日期会更有效,但根据数据的维护方式,这可能不会那么容易。

第二个潜在问题COUNT(RECORD_ID)。这可能不符合您的意思 - COUNT(x)可能直观地看起来应该这样做,COUNT(DISTINCT RECORD_ID)实际上是做什么的。如上所述,如果连接产生两个具有相同LISTING_HISTORY.ID值的匹配 - 即列表在惩罚之前的两个不同时间变为活动 - 列表将被计数两次。