MySQL表结构最佳实践多对多

时间:2012-03-04 04:59:01

标签: php mysql

您好我有39个大型表,介于8,000到1,500,000行之间,用于在MySQL4中对数据进行分区。每张表代表一个原始机场代码,其中的数据是来自每个起源的包假期交易(140列)。

我正在为酒店名称制作一个自动建议字段,但我想根据包裹持续时间,包裹类型和来源进行调整。

我有另一张桌子,所有酒店只有3000行,所以我想使用这张表作为自动推荐功能,因为它的尺寸和速度都很小。

每家酒店都有很多起源,包装类型和包装时间。

我的问题是我应该如何填充所有来源的酒店表格。 到目前为止,我唯一的想法是每个包裹类型,包裹持续时间和机场代码的分隔列表。

示例记录:

hotel_name = Some Hotel
origin = YYC;YEG;YVR
duration = 04;05;06;07;09;10;14
package_Type = 03;04;09

然后使用:

SELECT `hotel_name` from `hotels`
WHERE `hotel_name` LIKE '%$typed_text%' 
AND  `package_duration` LIKE '%09%' 
AND  `package_type` LIKE '%04%' 
AND  `origin` LIKE '%YEG%' 
ORDER BY `hotel_name`

这是解决此类查询的最佳方式吗?我还应该做些什么吗? 感谢


编辑:我尝试了几个解决方案,这个解决方案符合我的数据结构,并在0.001秒内查询3000家酒店。

这绝不是推荐的方法,因为它几乎打破了数据库查询中的每个规则,但无论如何它都是

这是一个示例转储:

CREATE TABLE IF NOT EXISTS `hotels` (
  `id` int(9) NOT NULL auto_increment,
  `hotel_name` varchar(255) NOT NULL default '',
  `origins` varchar(255) NOT NULL default '',
  `package_types` varchar(255) NOT NULL default '',
  `package_durations` varchar(255) NOT NULL default '',
  PRIMARY KEY  (`id`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1 AUTO_INCREMENT=6 ;

--
-- Dumping data for table `hotels`
--

INSERT INTO `hotels` (`id`, `hotel_name`, `origins`, `package_types`, `package_durations`) VALUES
(1, 'Bel Air Collection Resort & Spa Vallarta', 'YYC;YXX;YQQ;YEG;YLW;YUL;YQR;YYZ;YVR;YYJ;YWG;', '09;', '06;07;08;13;14;15;'),
(2, 'Grand Velas All Suites & Spa Resort, Riviera Maya', 'YYC;YXX;YQQ;YEG;YHM;YXU;YUL;YOW;YQB;YQR;YXE;YYZ;', '09;', '03;04;09;10;11;06;07;08;13;14;15;'),
(3, 'Barcelo Capella Beach Resort', 'YYC;YEG;YHZ;YUL;YOW;YYZ;YVR;YWG;', '09;', '06;07;08;13;14;15;'),
(4, 'Iberostar Cozumel', 'YYC;YBG;YYG;YEG;YFC;YHZ;YXU;YQM;YUL;YOW;YQB;', '09;', '06;07;08;13;14;15;'),
(5, 'Valentin Imperial Maya', 'YYC;YBG;YXX;YQQ;YEG;YHZ;YHM;YXU;YUL;', '09;', '06;07;08;03;04;09;10;11;13;14;15;');

查询表格的示例脚本:

$data[n] = 'resort';  //auto suggest text
$data[t] = '9';       //package_type
$data[d] = '7;8;10';  //durations
$data[o] = 'YUL;YVR'; //origins

//Split origins
foreach(explode(';',$data[o]) as $ori)
{
    $origin_sql[] = "`origins` LIKE '%".str_pad(mysql_real_escape_string($ori), 2, "0", STR_PAD_LEFT).";%'";
}
$data[o] = "(".implode(' OR ',$origin_sql).")";

//Split durations 
foreach(explode(';',$data[d]) as $dur)
{
    $duration_sql[] = "`package_durations` LIKE '%".str_pad(mysql_real_escape_string($dur), 2, "0", STR_PAD_LEFT).";%'";
}
$data[d] = "(".implode(' OR ',$duration_sql).")";

if($data[n]!=''&&$data[o]!=''&&$data[t]!=''&&$data[d]!='')
{
    $n = str_pad(mysql_real_escape_string($data[n]), 2, "0", STR_PAD_LEFT);
    $t = str_pad(mysql_real_escape_string($data[t]), 2, "0", STR_PAD_LEFT);

    $s = "SELECT `hotel_name` FROM `backend_hotels` WHERE
    `hotel_name` LIKE '%".$n."%' AND 
    `package_types` LIKE '%".$t.";%' AND 
    ".$data[o]." AND
    ".$data[d]."
     ORDER BY `hotel_name` ASC;";
    if($q = mysql_query($s))
    {
        while($r=mysql_fetch_array($q,MYSQL_ASSOC))
        {
            $names[] = $r[hotel_name];
        }
        echo json_encode($names);
    }
}

生成如下查询:

SELECT `hotel_name`
FROM `hotels`
WHERE `hotel_name` LIKE '%resort%'
AND `package_types` LIKE '%09;%'
AND (
  `origins` LIKE '%YUL;%'
   OR `origins` LIKE '%YVR;%'
)
AND (
  `package_durations` LIKE '%07;%'
  OR `package_durations` LIKE '%08;%'
  OR `package_durations` LIKE '%10;%'
)
ORDER BY `hotel_name` ASC
LIMIT 0 , 30 

就像我提到的那样,由于LIKE查询速度慢,这种方法对大型数据集来说并不好。这恰好更容易实现1100万套餐和3000家酒店,我只想展示我最终实施的内容,以防它可以帮助其他任何人

2 个答案:

答案 0 :(得分:3)

我通常做的是将关系分解为部分:

  

每个酒店都有起源包类型包裹持续时间

所以你有:

  • 起源
  • 包类型
  • 包裹持续时间

现在,如果你想到它,酒店是这里的主要主题,因此它应该是标记的项目(想象一下facebook)

现在,你如何“标记”?让我们来源吧。每个酒店都可以有很多起源,起源可能有很多酒店。你现在做的是建立一个“联结表”。我会尝试在这里画画:

    Hotels      Hotels_Origin    Origin
1. Marriott          1-1       1. US
2. Waterfront        1-2       2. UAE
                     2-1

你看到“标签”只不过是一个基于交叉点的“关系”。在此示例中,万豪标记在两个位置,Waterfront标记为美国。要获得此查询,您必须使用“LEFT JOIN”

  

LEFT JOIN关键字返回左表(table_name1)中的所有行,即使右表(table_name2)中没有匹配项也是如此。 ( W3schools ) - 这只是一个解释。

左连接确保我们根据参数的左侧获得我们想要的东西:

SELECT hotels.name, origin.name FROM hotels
LEFT JOIN hotels_origin ON hotels.id = hotels_origin.id  //match the hotel to junction
LEFT JOIN hotels_origin ON hotels_origin.id = origin.id  //match junction to origins
WHERE hotel.id=1                                        //get hoted by id

以下返回ID为1(万豪)的酒店:

hotels.name    origin.name
Marriott       US
Marriott       UAE

现在说,你对所有其他表做同样的事情。

这种方法的优点是

  • 表格中没有重复项。您可以将一个来源标记为许多酒店,只修改了联结表关系。

  • 此方法具有更高的可扩展性和可维护性

  • 更容易查找,尤其是自动建议(因为没有重复)

  • 你不修改记录,只修改关系。

    现在的问题是......您需要重新构建数据库。

答案 1 :(得分:1)

不,这不是最好的方法。您不希望在列中使用分隔列表。你想要的是一个表示每个关系的联结表。您想以第三范式设计数据库。

在上面的示例中,您需要将每个多对多关系分解为自己的表。因此酒店表将有一个主键(id),名称等。然后有一个HotelToOrigin表,每行持有一个hotel_id和origin_id。然后,HotelToDuration表类似,hotel_id和duration_id。等等。设置起来有点复杂,但这使得查询数据库变得最简单,最准确。如果您使用上面提到的表结构的LIKE语句,如果您的包类型为'044',您将在查询中得到它而不仅仅是'04'。所以你的查询将是不准确的。