一連のネストされたルールを作成できる UI ウィジェットを作成しました。たとえば、次のルールを指定できます。
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 クエリに変換する必要があります。サブクエリを使用するとかなり簡単に実行できますが、パフォーマンス上の理由からサブクエリは避けたほうがよいでしょう。Documents and tags テーブルには、それぞれ数百万のレコードが含まれる可能性があります。
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')
)
このクエリを書き直して、より効率的な結合を使用するにはどうすればよいですか?
最初の 2 つのタグ ルールを INNER 結合として追加できますが、ルール セットの後半部分を処理するにはどうすればよいですか? ドキュメントを表示するためにタグの存在を要求するルールがさらにある場合はどうなるでしょうか?
ルール セットは、その中のすべてまたは任意のルールに一致するように設定でき、理論的には何度もネストできることに注意してください。
この問題に取り組むための一般的な方向性に関するアイデアはありますか?
アップデート:
私は自分のテーブルを最適化し、非常に高速に見えるテーブルをクエリする方法を見つけました (別の問題である一致するレコードの数を数えることは別として)。一度に 100 を超えるドキュメントを選択することはありません。ドキュメント セットが 60 万以下でタグが 200 万以下の場合、このソリューションは結果を 0.02 秒以内に返します。これは以前よりもはるかに優れています。
問題のテーブル...
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
そして、私がテストしていたクエリ...
このクエリは、"blue" と "green" の両方のタグを持ち、"red" を持たないすべてのドキュメントとそのタグを検索します。
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
ただし、これはより良いインデックスを使用して改善できると確信しています。