正しいデータを返すクエリを作成するのに苦労しており、単一のクエリでそれが可能であるかどうかさえ確信が持てなくなっています。
printf() が機能するのとほとんど同じ方法で、MySQL データベースにログ レコードを保存していますが、書式文字列を置換値とは別に保存する必要がある点が異なります。私がやりたいのは、特定の値を検索した場合に、可能な限り最も効率的な方法でこのデータを返すことです。
テーブルのセットアップは次のとおりです。
CREATE TABLE `log` (
`log_id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`message` varchar(255) NOT NULL,
`num_variables` int(10) unsigned NOT NULL,
`created` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`log_id`)
);
CREATE TABLE `variable` (
`log_id` int(10) unsigned NOT NULL,
`order` int(10) unsigned NOT NULL,
`name` varchar(255) NOT NULL,
`value_id` int(10) unsigned NOT NULL,
KEY `log_id` (`log_id`),
KEY `value_id` (`value_id`)
);
CREATE TABLE `value` (
`value_id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`value` varchar(255) NOT NULL,
`created` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`value_id`),
UNIQUE KEY `value` (`value`)
);
使用例は次のとおりです。
log('user %email% invited %num% new players', 'him@example.com', 2);
これは、次のクエリにつながります。
-- create the log record (resulting PK would be 1)
INSERT INTO log
(message, num_variables)
VALUES
('user %email% invited %num% new players', 'him@example.com', '2');
-- create the first value record (resulting PK would be 1)
INSERT INTO value
(value)
VALUES
('him@example.com');
-- create the first variable record (resulting PK would be 1)
INSERT INTO variable
(log_id, order, name, value_id)
VALUES
(1, 0, 'email', 1);
-- create the second value record (resulting PK would be 2)
INSERT INTO value
(value)
VALUES
('2');
-- create the second variable record (resulting PK would be 2)
INSERT INTO variable
(log_id, order, name, value_id)
VALUES
(1, 1, 'num', 2);
ここで、関連する変数と値を使用して、ログ レコードをデータベースから取得できるようにしたいと考えています。具体的には、ログ メッセージとそれに関連するすべての値が必要です。
SELECT log.id, log.message
variable.order, variable.name
value.value_id, value.value
FROM log
LEFT JOIN variable ON (log.log_id = variable.log_id)
LEFT JOIN value ON (variable.value_id = value.value_id)
すべてのログ レコードが必要な場合、これは正常に機能します (複数の変数を持つログに対して log.log_id と log.message が重複して返されるという事実を無視します)。しかし、私はもっと具体的にしたいです。
上記の例から借りるために、"him@example.com" の "email" を含むログ レコードのみが必要であると指定できるようにしたいと考えています。それをクエリに追加すると...
SELECT log.log_id, log.message
variable.order, variable.name
value.value_id, value.value
FROM log
LEFT JOIN variable ON (log.log_id = variable.log_id)
LEFT JOIN value ON (variable.value_id = value.value_id)
WHERE (variable.name = 'email' AND value.value = 'him@example.com')
そのログ/変数/値レコードは返されますが、関連する "num = 2" レコード (ログを完全に再構築するために必要) は返されません。さらに、2 つ目の制約を指定したいとします。たとえば、"action" = "logged out" の場合です。WHERE 句を次のように (誤って) 変更することができます。
-- won't return anything
WHERE (variable.name = 'email' AND value.value = 'him@example.com')
AND (variable.name = 'action' AND value.value = 'logged out')
またはこれ:
-- will also return logs containing only ONE of the given constraints
WHERE (variable.name = 'email' AND value.value = 'him@example.com')
OR (variable.name = 'action' AND value.value = 'logged out')
しかし、どちらの場合も、目標を達成できず、探している正確な結果セットが返されないことがわかります。
テーブルの設計が不十分 (または不十分または過剰) ではないか? クエリに間違った方法でアプローチしていますか? 派生データのフィールドをどこかに保存すると、必要なものが得られますか? 問題を解決するために使用できなかった JOIN はありますか?
更新 1:
variable.order と variable.name は、値が log.message に正しく補間されることを保証するための 2 つの異なる方法です。
更新 2:
コメントに基づいて、これらのテーブルは投稿を単純化するために考案された例であることに注意してください。実際のテーブル構造は、提示されているよりもわずかに複雑です。その複雑さを問題の核心にまで落としただけです。単一のテーブルを使用して値をシリアル化するという単純な手法は、私にはうまくいきません。それとは別に、値に基づいてこれらのログを非常に迅速に検索できる必要があり、そのようなソリューションでは適切なインデックス作成機能が提供されません。