选择具有复杂JOIN的行

时间:2012-09-13 20:00:01

标签: mysql database-design join

我在制作查询以返回正确的数据方面遇到了麻烦,而且我对单一查询甚至可能无法自信。

我有一个存储在MySQL数据库中的日志记录,与printf()的工作方式非常相似,只是我必须保持格式字符串与替换值分开存储。我想做的是在搜索某些值的情况下以最有效的方式返回这些数据。

这是表格设置:

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)

如果我想要 ALL 日志记录(忽略了对于包含多个变量的任何日志,冗余地返回log.log_id和log.message这一事实),这样可以正常工作。但我想要更具体。

借用上面的例子,我希望能够指明我只想要包含“电子邮件”为“him@example.com”的日志记录,比方说。当我将其添加到我的查询中时......

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')

它将返回该日志/变量/值记录,但它将 NOT 返回关联的“num = 2”记录(这是完全重建日志所必需的)。另外,假设我想指定第二个约束,例如,“action”=“已注销”。我可以(错误地)改变我的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 个答案:

答案 0 :(得分:1)

怎么样:

...
WHERE log.id IN (SELECT l.id 
                 FROM log l 
                 INNER JOIN variable v ON l.log_id = v.log_id
                 INNER JOIN value vv ON v.value_id = vv.value_id
                 WHERE v.name = 'email' and vv.value = 'him@example.com')

在不知道更大的数据样本的情况下,我无法真正评论表格设计。在这一点上,我确实问题是分离出变量和值表,除非这是一对多的关系变量 - >值。

答案 1 :(得分:1)

那么你可以提出以下结构

CREATE TABLE `logs` (
  `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 (`id`)
);

CREATE TABLE `logs_values` (
  `log_id` int(10) unsigned NOT NULL,
  `value_id` int(10) unsigned NOT NULL
);

CREATE TABLE `value` (
  `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
  `name_id` int(10) unsigned NOT NULL,
  `value` varchar(255) NOT NULL,
  `created` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
  PRIMARY KEY (`id`),
  UNIQUE KEY `value` (`value`)
);

CREATE TABLE `names`(
  `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
  `name` varchar(255) NOT NULL,
  PRIMARY KEY (`id`)
);

要获取所有日志记录,请运行此查询

SELECT * FROM logs
LEFT JOIN logs_values ON logs_values.log_id = logs.id
LEFT JOIN value ON logs_values.value_id = value.id
LEFT JOIN names ON value.name_id = names.id;

获取具有指定值的所有日志记录

SELECT * FROM logs
LEFT JOIN logs_values ON logs_values.log_id = logs.id
LEFT JOIN value ON logs_values.value_id = value.id
LEFT JOIN names ON value.name_id = names.id
WHERE names.name = 'email' AND value.value = 'email@email.com';

结果

ID  MESSAGE NUM_VARIABLES   CREATED                           VALUE                 NAME
1   test       2            September, 13 2012 16:24:31-0400  email@email.com   email

SQL Fiddle

P.S。当然,您需要设置所需的索引以获得更好的性能