SQLite - 获取已排序/筛选查询的特定行索引

时间:2012-09-07 17:52:04

标签: sqlite

我正在创建一个缓存系统,使用已排序/已过滤的查询从SQLite数据库表中获取数据并显示它。我从中拉出的表可能非常大,当然,我需要通过在任何给定时间保留内存中的最大行数来最小化对内存的影响。通过使用LIMITOFFSET仅加载我需要的记录并根据需要更新缓存,可以轻松完成此操作。实现这一点很简单。我遇到的问题是确定插入到特定查询中的新记录的插入索引的位置,以便我可以适当地更新我的UI。是否有捷径可寻?到目前为止,我的想法是:

  1. 转储整个缓存,重新计算查询结果(不保证将包含新行),刷新缓存并刷新整个UI。我希望很明显为什么这不是很理想。
  2. 使用我自己的算法来确定新行是否包含在当前查询中,是否包含在当前缓存结果中以及当它在当前缓存范围内时应插入哪个索引。这种方法最大的缺点是它的复杂性以及我自己的排序/过滤算法与SQLite不匹配的风险。
  3. 当然,我想要的是能够询问SQLite:给定'查询A'什么是'行B'的索引,而不加载整个查询结果。但是,到目前为止,我还没有找到办法做到这一点。

    我觉得这不重要但是这一切都发生在iOS设备上,使用的是Objective-c编程语言。

    更多信息

    查询和后续缓存基于用户输入。基本上,用户可以重新排序和过滤(或搜索)以改变他们看到的结果。我在简单地重新创建插入缓存(以及实际编辑)时的沉默是为了提供“更流畅”的UI体验。

    我应该指出,我现在倾向于选择“2”。我通过加载表中的所有记录并使用我自己的算法在内存中执行排序/过滤来创建自己的缓存/索引系统。确定特定记录是否和/或特定记录在缓存中的大部分代码已经存在,所以我稍微倾向于使用它。危险在于具有与基础查询不匹配的缓存。如果我在缓存中包含查询不会返回的记录,我将遇到麻烦并可能崩溃。

3 个答案:

答案 0 :(得分:1)

通常,如果存在基础数据更改,您希望缓存无效。我认为放弃它并重新开始将是您最简单,可维护的解决方案。除非你有充分的理由,否则我会推荐它。

您可以编写另一个只返回行数的查询(例如下面的代码),以查看您的缓存是否应该无效。这样可以在不更改时保存重新创建缓存。

SELECT name,address FROM people WHERE area_code=970;
SELECT COUNT(rowid) FROM people WHERE area_code=970;

您需要从sqlite获取有关何时缓存无效的信息,这需要对查询和/或索引的工作方式有一些相当深入的了解。我会说这是相当高的耦合。

否则,您想知道它在排序方面的插入位置。您可能会在排序字段上键入每个页面。删除大于插入/删除字段的任何内容。每当你改变排序时,你都会丢弃所有东西。

如果您使用的是C ++,那么下面的内容将是一个开始。我意识到你没有做C ++,但希望很明显我正在尝试做什么。

struct Person {
  std::string name;
  std::string addr;
};

struct Page {
  std::string key;
  std::vector<Person> persons;
  struct Less {
    bool operator()(const Page &lhs, const Page &rhs) const {
      return lhs.key.compare(rhs.key) < 0;
    }
  };
};

typedef std::set<Page, Page::Less> pages_t;
pages_t pages;

void insert(const Person &person) {
  if (sql_insert(person)) {
    pages_t::iterator drop_cache_start = pages.lower_bound(person);
    //... drop this page and everything after it
  }
}

你必须做一些争论才能让key的不同数据类型很好地工作,但它可能。

理论上,您可以将页面从页面中删除,并仅使用对象本身。但数据库不再“拥有”数据。如果您只填充数据库中的页面,那么您对数据一致性的担忧就会减少。

这可能有点偏离主题,你没有重新实施views你呢?它本身不缓存,但不清楚这是否是您项目的要求。

答案 1 :(得分:1)

您不需要记录号码。

将有序字段的值保存在LIMITed查询结果的第一个和最后一个记录中。 然后,您可以使用这些来检查新记录是否属于此范围。

换句话说,假设您按Name字段排序,并且原始查询是这样的:

SELECT Name, ...
  FROM mytab
  WHERE some_conditions
  ORDER BY Name
  LIMIT x OFFSET y

然后尝试使用类似的查询来获取新记录:

SELECT 1
  FROM mytab
  WHERE some_conditions
    AND PrimaryKey = LastInsertedValue
    AND Name BETWEEN CachedMin AND CachedMax

同样,要在插入新记录之前(或之后)找出新记录,请在插入记录之后直接启动并使用限制为1,如下所示:

SELECT Name
  FROM mytab
  WHERE some_conditions
    AND Name > MyInsertedName
    AND Name BETWEEN CachedMin AND CachedMax
  ORDER BY Name
  LIMIT 1

这不会给你一个数字;您仍然需要检查返回的名称在缓存中的位置。

答案 2 :(得分:0)

我提出的解决方案并不简单,但它目前运作良好。我意识到查询语句中的记录索引也是它之前所有记录的Count。我需要做的是将查询中的所有ORDER语句“转换”为一系列WHERE语句,这些语句只返回前面的记录并记录这些记录。它听起来比它听起来更棘手(或者可能不是......听起来很棘手)。我遇到的最大问题是确保查询实际上是以我能预测的方式排序的。这意味着我需要在Order Parameters中有一个基于具有唯一值的列的订单列。因此,每当用户对列进行排序时,我会在语句中附加一个唯一列上的另一个订单参数(我使用“修改日期戳”)来断开关系。

创建语句的WHERE部分需要的不仅仅是添加一堆AND。它更容易演示。假设您有3个Order列:“LastName”ASC,“FirstName”DESC和“Modified Stamp”ASC(tie tie)。 WHERE语句必须看起来像这样('?'=记录值):

WHERE
    "LastName" < ? OR
    ("LastName" = ? AND "FirstName" > ?) OR
    ("LastName" = ? AND "FirstName" = ? AND "Modified Stamp" < ?)

通过括号组合在一起的每组WHERE参数都是断路器。事实上,如果“LastName”的记录值相等,那么我们必须查看“FirstName”,最后是“Modified Stamp”。显然,如果你按一堆订单参数排序,这个陈述可能会很长。

上述解决方案仍存在一个问题。对NULL值的数学运算总是返回false,但是当您对SQLite进行排序时,首先会对NULL值进行排序。因此,为了适当地处理NULL值,您必须添加另一层复杂功能。首先,所有数学平等操作=必须由IS替换。其次,所有<操作都必须与OR IS NULL嵌套,以便在NULL运算符上适当地包含<值。这将上述操作转换为:

WHERE
    ("LastName" < ? OR "LastName" IS NULL) OR
    ("LastName" IS ? AND "FirstName" > ?) OR
    ("LastName" IS ? AND "FirstName" IS ? AND ("Modified Stamp" < ? OR "Modified Stamp" IS NULL))

然后我使用上面的WHERE参数计算RowID。

事实证明,我做的很容易,因为我已经构建了一组对象来表示我的SQL语句的各个方面,这些方面可以被组合以生成语句。我甚至无法想象试图以任何其他方式操纵这样的SQL语句。

到目前为止,我已经测试过在几台iOS设备上使用它,在一个表中有多达10,000条记录,而且我没有明显的性能问题。当然,它是专为单个记录编辑/插入而设计的,所以我并不需要超快速/高效。