查找涉及STI和父子关系的“重复”记录的解决方案

时间:2011-09-25 00:48:56

标签: mysql sql ruby-on-rails ruby algorithm

我有一个名为Buyable的基于STI的模型,有两个模型Basket和Item。这里可购买的关注属性是:

  • shop_week_id
  • LOCATION_ID
  • PARENT_ID

篮子和物品之间存在亲子关系。对于篮子,parent_id总是为零,但是通过引用唯一的篮子ID,项目可以属于篮子。因此,篮子有很多物品,而物品属于一个篮子。

我需要一个篮子模型的方法:

如果表中有任何其他具有相同数量和类型的项目的篮子,则返回true。当项目共享相同的shop_week_id和location_id时,它们被认为是相同的类型。

例如:

给出一个带有2个项目的篮子(uid = 7):

项目#1

  • id = 3
  • shop_week_id = 13
  • location_id = 103
  • parent_id = 7

项目#2

  • id = 4
  • shop_week_id = 13
  • location_id = 204
  • parent_id = 7

如果表中有任何其他篮子包含正好2个项目,则返回true,其中一个项目的shop_week_id = 13,location_id = 103,另一个项目的shop_week_id = 13,location_id = 204.否则返回false。 / p>

你会如何解决这个问题?这不用说,但我正在寻找一种非常有效的解决方案。

3 个答案:

答案 0 :(得分:3)

以下SQL似乎可以解决这个问题

big_query = "
  SELECT EXISTS (
    SELECT 1
    FROM buyables b1
      JOIN buyables b2
        ON b1.shop_week_id = b2.shop_week_id
        AND b1.location_id = b2.location_id
    WHERE
      b1.parent_id != %1$d
      AND b2.parent_id = %1$d
      AND b1.type = 'Item'
      AND b2.type = 'Item'
    GROUP BY b1.parent_id
    HAVING COUNT(*) = ( SELECT COUNT(*) FROM buyables WHERE parent_id = %1$d AND type = 'Item' )
  )
"

使用ActiveRecord,您可以使用select_value获得此结果:

class Basket < Buyable
  def has_duplicate
    !!connection.select_value( big_query % id )
  end
end

我对性能不太确定

答案 1 :(得分:1)

如果你想尽可能提高效率,你应该考虑创建一个将篮子内容编码为单个字符串或blob的哈希,添加一个包含哈希的新列(每次篮子内容都需要更新)通过应用程序或使用触发器进行更改,并比较哈希值以确定可能的相等性。然后,您可能需要按顺序执行进一步的比较(如上所述)

你应该使用什么哈希?如果您知道篮子的大小有限,并且所讨论的ID是有界整数,那么您应该能够散列到足以测试相等性的字符串。例如,您可以对每个shop_week和location进行base64编码,使用不在base64中的分隔符(如“|”)连接,然后与其他篮子项连接。在新的哈希键上构建索引,并且比较将很快。

答案 2 :(得分:0)

为了澄清我的查询,以及对“可购买”表的表列的某种模糊描述,“Parent_ID”是有问题的篮子。 “Shop_Week_ID”是要比较的篮子的考虑因素...不要比较第1周到第2周到第3周的篮子。#ID列似乎是表格中的顺序ID,但不是实际ID要比较的项目... Location_ID似乎是常见的“项目”。在该场景中,假设购物车,Location_ID = 103 =“Computer”,Location_ID = 204 =“Television”(仅用于我对数据的解释)。如果这是不正确的,可能需要进行微调,除了原始海报显示一个说明列表...十几个数据条目,以显示正确的相关性。

所以,现在,我的查询..我正在做一个STRAIGHT_JOIN,所以它按照我列出的顺序加入。

别名“MainBasket”的第一个查询专门用于查询有问题的篮子中有多少项目,因此不需要为每个可能匹配的篮子重新加入/查询。没有“ON”子句,因为这将是单个记录,因此没有笛卡尔影响,因为我希望在最终结果中将此COUNT(*)值应用于每条记录。

NEXT查询是找到一个DISTINCT OTHER篮子,在与同一父母相同的一周内至少有一个“Location_ID”(项目)...这可能会导致其他篮子的条目数比1个,相同或更多篮。但是如果有100个篮子,但是只有18个篮子至少有1个与原始篮子中的1个项目匹配的条目,那么你只需要大大减少最终比较的篮子数量(SameWeekSimilar别名结果)。

最后是再次加入可购买的桌子,但基于SameWeekSimilar的加入,但仅限于每个“其他”篮子有一个紧密匹配...没有具体项目,只是在篮子里。用于获取SameWeekSimilar的查询已在同一周内进行了预先限定,并且至少有一个匹配的项目来自原始购物篮,但具体排除了原始购物篮,因此它与自身不相符。

通过基于SameWeekSimilar.NextBasket在外层做一个组,我们可以获得该篮子的实际项目数。由于简单的笛卡尔连接到MainBasket,我们只需获取原始数量。

最后,HAVING子句。由于这是在“COUNT(*)”之后应用的,因此我们知道“其他”篮子中有多少项目,以及“主要”篮子中有多少项目。因此,HAVING子句仅包括计数相同的子句。

如果你想测试以确保我所描述的内容,请对你的表运行,但不要包含HAVING子句。你会看到哪些都是可能的......然后重新添加HAVING子句,看看哪些匹配相同的数... ...

select STRAIGHT_JOIN
      SameWeekSimilar.NextBasket,
      count(*) NextBasketCount,
      MainBasket.OrigCount
   from 
      ( select count(*) OrigCount
           from Buyable B1
           where B1.Parent_ID = 7 ) MainBasket

      JOIN

      ( select DISTINCT
              B2.Parent_ID as NextBasket
           from
              Buyable B1
                 JOIN Buyable B2
                    ON B1.Parent_ID != B2.Parent_ID
                   AND B1.Shop_Week_ID = B2.Shop_Week_ID
                   AND B1.Location_ID = B2.Location_ID
           where
              B1.Parent_ID = 7 ) SameWeekSimilar

       Join Buyable B1
          on SameWeekSimilar.NextBasket = B1.Parent_ID

    group by
       SameWeekSimilar.NextBasket

    having
       MainBasket.OrigCount = NextBasketCount