2012-06-03 52 views
3

我已经构建了一个UI小部件,它允许我创建一组嵌套规则。例如,我可以指定以下规则:从嵌套规则生成器生成MySQL查询

Match ALL of these rules 
    - Document Status == Open 
    - Has Tag = 'sales' 
    - Has Tag = 'question' 
    - Match ANY of these rules 
    - Has Tag = 'important' 
    - Has Tag = 'high-priority' 
    - Has Tag = 'critical-priority' 

在英语中,这将转化这个查询:

Find Documents where status = Open AND has tag 'sales' AND has tag 'question' 
    AND has at least one of these tags: 'important', 'high-priority', 'critical-priority' 

表结构类似于此。

Documents {id, title, status} 
Tags {document_id, tag_value} 

现在,在这一点上,我需要将这套规则转换为SQL查询。使用子查询可以很容易地完成,但是由于性能原因,Id宁愿避免它们。文档和标签表可能包含数百万条记录。

SELECT 
    d.id 
FROM 
    Documents d 
WHERE 
    d.status = 'open' 
    AND EXISTS (SELECT * FROM Tags t WHERE t.doc_id = d.id AND t.value = 'sales') 
    AND EXISTS (SELECT * FROM Tags t WHERE t.doc_id = d.id AND t.value = 'question') 
    AND (
     EXISTS (SELECT * FROM Tags t WHERE t.doc_id = d.id AND t.value = 'important') 
     OR EXISTS (SELECT * FROM Tags t WHERE t.doc_id = d.id AND t.value = 'high-priority') 
     OR EXISTS (SELECT * FROM Tags t WHERE t.doc_id = d.id AND t.value = 'critical-priority') 
    ) 

如何重写此查询以使用更高效的联接?

我可以将前两个标记规则添加为INNER连接,但是如何处理规则集的后面部分?如果有进一步的规则需要标签出现以便文档出现,会怎么样?

请记住,可以将规则集设置为匹配ALL或其中的任意规则,并且它理论上可以嵌套多次。

关于解决此问题的一般方向的任何想法?

更新:

我优化我的表,发现查询似乎非常快(除了计数的匹配记录的数量,这是另一个问题)表的方法。我永远不会一次选择超过100个文档,并且具有约600k和200万个标签的文档集,这个解决方案将结果返回到0.02s,这比以前好得多。

有问题的表...

CREATE TABLE `app_documents` (
    `id` int(11) NOT NULL AUTO_INCREMENT, 
    `account_id` int(11) NOT NULL, 
    `status_id` int(11) DEFAULT NULL, 
    `subject` varchar(255) COLLATE utf8_unicode_ci NOT NULL, 
    `created` datetime NOT NULL, 
    `updated` datetime NOT NULL, 
    PRIMARY KEY (`id`), 
    KEY `IDX_B91B1DB99B6B5FBA` (`account_id`), 
    KEY `IDX_B91B1DB96BF700BD` (`status_id`), 
    KEY `created_idx` (`created`), 
    KEY `updated_idx` (`updated`), 
    CONSTRAINT `FK_B91B1DB96BF700BD` FOREIGN KEY (`status_id`) REFERENCES `app_statuses` (`id`), 
    CONSTRAINT `FK_B91B1DB99B6B5FBA` FOREIGN KEY (`account_id`) REFERENCES `app_accounts` (`id`), 
) ENGINE=InnoDB AUTO_INCREMENT=500001 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci 

CREATE TABLE `app_tags` (
    `id` int(11) NOT NULL AUTO_INCREMENT, 
    `value` varchar(50) COLLATE utf8_unicode_ci NOT NULL, 
    PRIMARY KEY (`id`), 
    KEY `value_idx` (`value`) 
) ENGINE=InnoDB AUTO_INCREMENT=8 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci 


CREATE TABLE `app_documents_tags` (
    `document_id` int(11) NOT NULL, 
    `tag_id` int(11) NOT NULL, 
    PRIMARY KEY (`document_id`,`tag_id`), 
    KEY `IDX_A849587A700047D2` (`document_id`), 
    KEY `IDX_A849587ABAD26311` (`tag_id`), 
    CONSTRAINT `FK_A849587ABAD26311` FOREIGN KEY (`tag_id`) REFERENCES `app_tags` (`id`) ON DELETE CASCADE, 
    CONSTRAINT `FK_A849587A700047D2` FOREIGN KEY (`document_id`) REFERENCES `app_documents` (`id`) ON DELETE CASCADE 
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci 

而且我对测试查询...

此查询查找所有文件和他们的标签有两个标签“蓝色”和“绿色“但不是”红色“。

SELECT 
    d.* 
FROM 
    app_documents d 
LEFT JOIN 
    app_documents_tags dtg ON ttg.document_id = d.id 
LEFT JOIN 
    app_tags tg ON tg.id = dtg.tag_id 
WHERE 
    d.account_id = 1 
    AND EXISTS (
     SELECT 
      * 
     FROM 
      app_tags t1 
     CROSS JOIN 
      app_tags t2 
     CROSS JOIN 
      app_tags t3 
     INNER JOIN 
      app_documents_tags dtg1 ON t1.id = ttg1.tag_id 
     INNER JOIN 
      app_documents_tags dtg2 ON dtg1.ticket_id = dtg2.ticket_id AND dtg2.tag_id = t2.id 
     LEFT JOIN 
      app_documents_tags dtg3 ON dtg2.ticket_id = dtg3.ticket_id AND dtg3.tag_id = t3.id 
     WHERE 
      t1.value = 'blue' AND t2.value = 'green' AND t3.value = 'red' AND dtg3.ticket_id IS NULL AND dtg2.document_id = t.id 
    ) 
ORDER BY 
    d.created 
LIMIT 45 

我相信这可以通过使用更好的索引来改善。

+0

数据库的架构或涉及的表的结构是什么? –

+0

我将它列在原始问题中。与我正在使用的相比,它非常简化,但它足以显示我的意思。 –

+0

@BartW如果你想提高性能,我们需要更多关于你的表格的信息,你可以做一个表演创建表格和'EXPLAIN QUERY'吗? – jcho360

回答

1

Forumlate从问题的查询,如下所示:兼具销售和问题,标签(子查询AA),其标签中的一个(重要的是,高优先级的

  • 收集文档ID”

    • 收集文档ID ,关键的优先级)(子查询BB)
    • 合并AA和BB,你会得到子查询DocsWithValidTagRules
    • 加入与文件表DocsWithValidTagRules开放状态
    • PERFO RM您的分页

    鉴于描述,这里是结果查询:

    SELECT Documents.id 
    FROM 
    (
        SELECT AA.document_id 
        (
         SELECT B.document_id,COUNT(1) tagcount FROM 
         (
          SELECT id FROM app_tags 
          WHERE `value` IN ('sales','question') 
         ) A 
         INNER JOIN app_documents_tags B 
         ON A.id = B.tag_id 
         GROUP BY B.document_id 
         HAVING COUNT(1) = 2 
        ) AA 
        INNER JOIN 
        (
         SELECT B.document_id,COUNT(1) tagcount FROM 
         (
          SELECT id FROM app_tags 
          WHERE `value` IN ('important','high-priority','critical-priority') 
         ) A 
         INNER JOIN app_documents_tags B 
         ON A.id = B.tag_id 
         GROUP BY B.document_id 
        ) BB 
    ) DocsWithValidTagRules 
    INNER JOIN Documents 
    ON DocsWithValidTagRules.document_id = Documents.id 
    WHERE Documents.status = 'open' 
    LIMIT page_offset,page_size; 
    

    确保您对文档

    ALTER TABLE Documents ADD INDEX status_id_index (status,id); 
    

    试试看这个指数!

  • +0

    这并不完全符合我的要求(我在我的主要WHERE子查询中),但它非常相似,也是迄今为止最接近的答案。我将不得不尝试你的方法,看看哪一个是最快的。谢谢! –

    0

    它是否必须是纯粹的sql解决方案?

    您可以使用类似这样的方式来缩小设置的数据范围,其中只有一个连接 然后使用您检索数据的任何语言来过滤较小的数据集并使用适当的逻辑。

    SELECT 
        d.id, 
        t.value 
    FROM 
        Documents d 
        JOIN Tags t_required ON t.doc_id=d.id 
    WHERE 
        d.status = 'open' 
        and t.value IN ('sales', 'question', 'important', 'high-priority', 'critical-priority') 
    
    +0

    这种方法存在一些问题。如果不是不可能的话,它会使结果的分页变得困难。其次,要正确分页和过滤结果,我将不得不检索整个结果集(可能是数百万条记录),水合所有对象,然后手动过滤它们。这将是一个非常昂贵的过程。我宁愿让数据库只返回匹配我的查询的结果。 –

    +0

    然后,我看不出有什么改进,就是你已经做了什么。也许你可以在内存表或视图中创建“SELECT * FROM标记t WHERE t.doc_id = d.id和t.value IN('...','..')”,然后使用该视图子查询。如果不对真实数据进行测试,我无法确定这是否会更快。你是否也看过像http://www.percona.com这样的东西来加速数据库。 – bumperbox

    +0

    我实际上会用Percona XtraDB来部署,所以我看了他们的各种建议。我以几个子查询为例给出的查询几乎只用于我想要避免的演示目的。子查询可以优化一点,以包含您的建议,但我想知道是否有更好的方法来做到这一点。 –

    0

    你有没有考虑的Lucene/Solr的

    +0

    我有,在我的情况下,这可能是一个值得的解决方案。但是,在这个问题中,我正在寻找基于“嵌套规则生成器”构建查询的方法。 –

    0

    以下是我对这个问题做。除了上面的关系模型之外,我还将创建另一个只有两列的表格"DocumentID"|"MetadataXML"。当我创建/更新任何文档时,我将创建一个准确包含每个文档的所有元数据的XML文档(最好是模式验证)。然后我将使用XPATH表达式来搜索文档。

    它可能并不快,甚至快。但是这个想法的最大优点是,你的数据模型和索引和工作流程是稳定的。 XML模式将抽象出所有复杂性。

    此外,我将在此之上实现Lucene/Solr以提供快速基本搜索。

    Fast basic full text search -> Lucene/Solr 
    Advanced Search -> XML/XPATH expression search 
    Federated Searches, Rest APIs etc -> SQL 
    
    +0

    不幸的是,表现是我正在寻找的标准之一。任何超过100毫秒的搜索百万份文档都是不可接受的。 Lucene/Solr非常适合搜索,但我或多或少试图根据用户给出的标准创建文档的自定义“视图”。 –

    0

    它可能不快,甚至快。但是这个想法的最大优点是,你的数据模型和索引和工作流程是稳定的。 XML模式将抽象出所有复杂性。

    此外,我将在此之上实现Lucene/Solr以提供快速基本搜索。