你如何通过加入执行AND?

时间:2009-03-01 07:57:37

标签: sql mysql join left-join

我有以下数据结构和数据:

CREATE TABLE `parent` (
  `id` int(11) NOT NULL auto_increment,
  `name` varchar(10) NOT NULL,
  PRIMARY KEY  (`id`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1;

INSERT INTO `parent` VALUES(1, 'parent 1');
INSERT INTO `parent` VALUES(2, 'parent 2');

CREATE TABLE `other` (
  `id` int(11) NOT NULL auto_increment,
  `name` varchar(10) NOT NULL,
  PRIMARY KEY  (`id`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1;

INSERT INTO `other` VALUES(1, 'other 1');
INSERT INTO `other` VALUES(2, 'other 2');

CREATE TABLE `relationship` (
  `id` int(11) NOT NULL auto_increment,
  `parent_id` int(11) NOT NULL,
  `other_id` int(11) NOT NULL,
  PRIMARY KEY  (`id`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1;

INSERT INTO `relationship` VALUES(1, 1, 1);
INSERT INTO `relationship` VALUES(2, 1, 2);
INSERT INTO `relationship` VALUES(3, 2, 1);

我想找到包含其他1和1的父记录。 2。

这是我已经想到的,但我想知道是否有更好的方法:

SELECT p.id, p.name
FROM parent AS p
    LEFT JOIN relationship AS r1 ON (r1.parent_id = p.id)
    LEFT JOIN relationship AS r2 ON (r2.parent_id = p.id)
WHERE r1.other_id = 1 AND r2.other_id = 2;

结果是1,“父1”是正确的。问题是,一旦你获得了5个以上连接的列表,就会变得混乱,随着关系表的增长,它会变慢。

有更好的方法吗?

我正在使用MySQL和PHP,但这可能非常通用。

7 个答案:

答案 0 :(得分:4)

好的,我测试了这个。从最好到最差的查询是:

查询1:加入(0.016s;基本上即时

SELECT p.id, name
FROM parent p
JOIN relationship r1 ON p.id = r1.parent_id AND r1.other_id = 100
JOIN relationship r2 ON p.id = r2.parent_id AND r2.other_id = 101
JOIN relationship r3 ON p.id = r3.parent_id AND r3.other_id = 102
JOIN relationship r4 ON p.id = r4.parent_id AND r4.other_id = 103

查询2:EXISTS(0.625s)

SELECT id, name
FROM parent p
WHERE EXISTS (SELECT 1 FROM relationship WHERE parent_id = p.id AND other_id = 100)
AND EXISTS (SELECT 1 FROM relationship WHERE parent_id = p.id AND other_id = 101)
AND EXISTS (SELECT 1 FROM relationship WHERE parent_id = p.id AND other_id = 102)
AND EXISTS (SELECT 1 FROM relationship WHERE parent_id = p.id AND oth

查询3:汇总(1.016s)

SELECT p.id,p.name 来自父母p WHERE(SELECT COUNT(*)FROM relationship WHERE parent_id = p.id AND other_id IN(100,101,102,103))

查询4:UNION Aggregate(2.39s)

SELECT id, name FROM (
  SELECT p1.id, p1.name
  FROM parent AS p1 LEFT JOIN relationship as r1 ON(r1.parent_id=p1.id)
  WHERE r1.other_id = 100
  UNION ALL
  SELECT p2.id, p2.name
  FROM parent AS p2 LEFT JOIN relationship as r2 ON(r2.parent_id=p2.id)
  WHERE r2.other_id = 101
  UNION ALL
  SELECT p3.id, p3.name
  FROM parent AS p3 LEFT JOIN relationship as r3 ON(r3.parent_id=p3.id)
  WHERE r3.other_id = 102
  UNION ALL
  SELECT p4.id, p4.name
  FROM parent AS p4 LEFT JOIN relationship as r4 ON(r4.parent_id=p4.id)
  WHERE r4.other_id = 103
) a
GROUP BY id, name
HAVING count(*) = 4

实际上上面的内容产生了错误的数据,所以它错了或者我做错了。无论如何,以上只是一个坏主意。

如果那不快,那么你需要查看查询的解释计划。你可能只是缺乏适当的指数。尝试使用:

CREATE INDEX ON relationship (parent_id, other_id)

在你走下聚合路线(SELECT COUNT(*)FROM ...)之前,你应该阅读SQL Statement - “Join” Vs “Group By and Having”

注意:上述时间基于:

CREATE TABLE parent (
  id INT PRIMARY KEY,
  name VARCHAR(50)
);

CREATE TABLE other (
  id INT PRIMARY KEY,
  name VARCHAR(50)
);

CREATE TABLE relationship (
  id INT PRIMARY KEY,
  parent_id INT,
  other_id INT
);

CREATE INDEX idx1 ON relationship (parent_id, other_id);
CREATE INDEX idx2 ON relationship (other_id, parent_id);

创建了近800,000条记录:

<?php
ini_set('max_execution_time', 600);

$start = microtime(true);

echo "<pre>\n";
mysql_connect('localhost', 'scratch', 'scratch');
if (mysql_error()) {
    echo "Connect error: " . mysql_error() . "\n";
}
mysql_select_db('scratch');
if (mysql_error()) {
    echo "Selct DB error: " . mysql_error() . "\n";
}

define('PARENTS', 100000);
define('CHILDREN', 100000);
define('MAX_CHILDREN', 10);
define('SCATTER', 10);
$rel = 0;
for ($i=1; $i<=PARENTS; $i++) {
    query("INSERT INTO parent VALUES ($i, 'Parent $i')");
    $potential = range(max(1, $i - SCATTER), min(CHILDREN, $i + SCATTER));
    $elements = sizeof($potential);
    $other = rand(1, min(MAX_CHILDREN, $elements - 4));
    $j = 0;
    while ($j < $other) {
        $index = rand(0, $elements - 1);
        if (isset($potential[$index])) {
            $c = $potential[$index];
            $rel++;
            query("INSERT INTO relationship VALUES ($rel, $i, $c)");
            unset($potential[$index]);
            $j++;
        }
    }
}
for ($i=1; $i<=CHILDREN; $i++) {
    query("INSERT INTO other VALUES ($i, 'Other $i')");
}

$count = PARENTS + CHILDREN + $rel;
$stop = microtime(true);
$duration = $stop - $start;
$insert = $duration / $count;

echo "$count records added.\n";
echo "Program ran for $duration seconds.\n";
echo "Insert time $insert seconds.\n";
echo "</pre>\n";

function query($str) {
    mysql_query($str);
    if (mysql_error()) {
        echo "$str: " . mysql_error() . "\n";
    }
}
?>

所以再一次加入这一天。

答案 1 :(得分:2)

鉴于父表包含唯一键(parent_id,other_id),您可以这样做:

select p.id, p.name 
  from parent as p 
 where (select count(*) 
        from relationship as r 
       where r.parent_id = p.id 
         and r.other_id in (1,2)
        ) >= 2

答案 2 :(得分:1)

简化一点,这应该有效。

  

SELECT DISTINCT p.id,p.name
  来自父母p   INNER JOIN关系r1 ON p.id = r1.parent_id AND r1.other_id = 1
  INNER JOIN关系r2 ON p.id = r2.parent_id AND r2.other_id = 2

每个“其他”值至少需要一条连接记录。并且优化器应该知道它只需要找到一个匹配,并且它只需要读取索引,而不是任何一个子表,其中一个甚至根本没有引用。

答案 3 :(得分:0)

我实际上没有对它进行过测试,但有些内容如下:

SELECT id, name FROM (
  SELECT p1.id, p1.name
  FROM parent AS p1 LEFT JOIN relationship as r1 ON(r1.parent_id=p1.id)
  WHERE r1.other_id = 1
  UNION ALL
  SELECT p2.id, p2.name
  FROM parent AS p2 LEFT JOIN relationship as r2 ON(r2.parent_id=p2.id)
  WHERE r2.other_id = 2
   -- etc
) GROUP BY id, name
HAVING count(*) = 2

这个想法是你不必做多路连接;只需连接常规连接的结果,按ID分组,然后选择每个段中显示的行。

答案 4 :(得分:0)

当通过多对多联接搜索多个联系人时,这是一个常见问题。这通常在使用“标签”概念的服务中遇到,例如计算器

See my other post on a better architecture for tag (in your case 'other') storage

搜索是一个两步过程:

  1. 查找TagCollections的所有可能的候选人,这些候选人拥有您需要的任何/所有标签(使用循环构造的光标可能更容易)
  2. 选择与TagCollection匹配的数据
  3. 由于TagCollections明显少于要搜索的数据项

    ,因此性能总是更快

答案 5 :(得分:0)

你可以使用嵌套选择,我在MSSQL 2005中测试它,但正如你所说它应该是非常通用的

SELECT * FROM parent p
WHERE p.id in(
    SELECT r.parent_Id 
    FROM relationship r 
    WHERE r.parent_id in(1,2) 
    GROUP BY r.parent_id
    HAVING COUNT(r.parent_Id)=2
)

并且COUNT(r.parent_Id)=2中的数字2取决于您需要的连接数)

答案 6 :(得分:0)

如果您可以将other_id值列表放入理想的表中。下面的代码查找至少给出ID的父母。如果您希望它具有完全相同的ID(即没有额外的),则必须稍微更改查询。

SELECT
     p.id,
     p.name
FROM
     My_Other_IDs MOI
INNER JOIN Relationships R ON
     R.other_id = MOI.other_id
INNER JOIN Parents P ON
     P.parent_id = R.parent_id
GROUP BY
     p.parent_id,
     p.name
HAVING
     COUNT(*) = (SELECT COUNT(*) FROM My_Other_IDs)