使用许多联接帮助MySQL Query

时间:2009-10-30 17:37:47

标签: mysql join

设置:使用4个表格联系数据库

  • 联系人
  • 城市
  • 拉链

结构:

CREATE TABLE `contacts` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `last` varchar(100) CHARACTER SET latin1 COLLATE latin1_general_ci DEFAULT NULL,
  `first` varchar(100) CHARACTER SET latin1 COLLATE latin1_general_ci DEFAULT NULL,
  `prefix` varchar(50) CHARACTER SET latin1 COLLATE latin1_general_ci DEFAULT NULL,
  `suffix` varchar(50) CHARACTER SET latin1 COLLATE latin1_general_ci DEFAULT NULL,
  `address` varchar(100) CHARACTER SET latin1 COLLATE latin1_general_ci DEFAULT NULL,
  `address_1` varchar(100) CHARACTER SET latin1 COLLATE latin1_general_ci DEFAULT NULL,
  `city_id` int(100) DEFAULT NULL,
  `state_id` int(20) DEFAULT NULL,
  `alt_address_1` varchar(255) CHARACTER SET latin1 COLLATE latin1_general_ci DEFAULT NULL,
  `alt_address_2` varchar(255) CHARACTER SET latin1 COLLATE latin1_general_ci DEFAULT NULL,
  `alt_city` varchar(100) CHARACTER SET latin1 COLLATE latin1_general_ci DEFAULT NULL,
  `alt_state` varchar(20) CHARACTER SET latin1 COLLATE latin1_general_ci DEFAULT NULL,
  `alt_zip` varchar(15) CHARACTER SET latin1 COLLATE latin1_general_ci DEFAULT NULL,
  `publish_name` varchar(255) CHARACTER SET latin1 COLLATE latin1_general_ci DEFAULT NULL,
  `salutation` varchar(255) CHARACTER SET latin1 COLLATE latin1_general_ci DEFAULT NULL,
  `mail_label` varchar(255) CHARACTER SET latin1 COLLATE latin1_general_ci DEFAULT NULL,
  `solicitor` varchar(100) CHARACTER SET latin1 COLLATE latin1_general_ci DEFAULT NULL,
  `is_volunteer` tinyint(1) DEFAULT NULL,
  `is_sponsor` tinyint(1) DEFAULT '0',
  `is_company` tinyint(1) DEFAULT '0',
  `is_foundation` tinyint(1) DEFAULT '0',
  `status` varchar(15) CHARACTER SET latin1 COLLATE latin1_general_ci DEFAULT NULL,
  `created_on` datetime NOT NULL,
  `created_by` varchar(30) CHARACTER SET latin1 COLLATE latin1_general_ci DEFAULT NULL,
  `modified_on` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  `modified_by` varchar(100) CHARACTER SET latin1 COLLATE latin1_general_ci DEFAULT NULL,
  `agency_id` int(25) DEFAULT NULL,
  `primary_id` int(11) DEFAULT NULL,
  PRIMARY KEY (`id`),
  KEY `primary_id` (`primary_id`)
) ENGINE=InnoDB AUTO_INCREMENT=3008 DEFAULT CHARSET=utf8

CREATE TABLE `cities` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `city` varchar(50) CHARACTER SET latin1 COLLATE latin1_general_ci DEFAULT NULL,
  `stateid` int(11) DEFAULT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `city` (`city`)
) ENGINE=InnoDB AUTO_INCREMENT=128 DEFAULT CHARSET=utf8

CREATE TABLE `states` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `abbreviation` varchar(2) CHARACTER SET latin1 COLLATE latin1_general_ci DEFAULT NULL,
  `state` varchar(20) CHARACTER SET latin1 COLLATE latin1_general_ci DEFAULT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `state` (`state`),
  UNIQUE KEY `abbreviation` (`abbreviation`),
  KEY `id` (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=52 DEFAULT CHARSET=utf8

CREATE TABLE `zips` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `zip` varchar(10) CHARACTER SET latin1 COLLATE latin1_general_ci DEFAULT NULL,
  `cityid` int(100) DEFAULT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `zip` (`zip`)
) ENGINE=InnoDB AUTO_INCREMENT=128 DEFAULT CHARSET=utf8

我已经用111个联系人填写了联系人,状态只是所有状态,城市有相应的id密钥与状态ID相关,邮政编码有一个与城市匹配的密钥。

查询是生成与正确字段匹配的人员列表。这是查询。

SELECT concat(contacts.last,' ', contacts.first) as name
     , cities.city
     , zips.zip
  FROM contacts
  JOIN cities
    ON cities.id = contacts.city_id
  JOIN states ON states.id = contacts.state_id
  JOIN zips ON zips.cityid = cities.id

此查询返回338行,可能有11个联系人。有明显的重复。这种情况发生在我加入邮政编码时,因为它们属于超过1个城市,所以每个城市都匹配(我认为这就是正在发生的事情)。任何人都有关于如何正确加入这些表的答案?

谢谢。 富

2 个答案:

答案 0 :(得分:1)

我相信你应该重新考虑很多这些表的代理键使用,并尽可能使用自然键。以状态表为例,在大多数情况下,简单地使用状态short(即TX与Texas)进行数据和显示是可以接受的。这意味着如果您删除了状态表上的递增ID并为每个状态使用了自然键,那么在90%的情况下,您将减少连接的必要性。

然后在需要存储状态值的表中使用state.abbriviation作为FK。将此扩展到zipcodes和城市,您可以将状态缩写为城市表格,并将城市表格中的复合FK制作到联系人表格,同时为您提供城市和州的密钥。

示例模式(排除的zipcodes表和缩短的联系人表):

CREATE  TABLE IF NOT EXISTS `states` (
  `state_id` CHAR(2) NOT NULL ,
  `name` VARCHAR(45) NULL ,
  PRIMARY KEY (`state_id`) ,
  UNIQUE INDEX `state_name` (`name` ASC)
)
ENGINE = InnoDB;

CREATE  TABLE IF NOT EXISTS `cities` (
  `state_id` CHAR(2) NOT NULL ,
  `city_name` VARCHAR(255) NOT NULL ,
  PRIMARY KEY (`state_id`, `city_name`) ,
  INDEX `fk_city_state_id` (`state_id` ASC) ,
  CONSTRAINT `fk_city_state_id`
    FOREIGN KEY (`state_id` )
    REFERENCES `states` (`state_id` )
    ON DELETE NO ACTION
    ON UPDATE NO ACTION
)
ENGINE = InnoDB;

CREATE  TABLE IF NOT EXISTS `contacts` (
  `contacts_id` INT NOT NULL AUTO_INCREMENT ,
  `state` CHAR(2) NULL ,
  `city` VARCHAR(255) NULL ,
  PRIMARY KEY (`contacts_id`) ,
  INDEX `fk_contact_city` (`state` ASC, `city` ASC) ,
  INDEX `fk_contact_state` (`state` ASC) ,
  CONSTRAINT `fk_contact_city`
    FOREIGN KEY (`state` , `city` )
    REFERENCES `cities` (`state_id` , `city_name` )
    ON DELETE NO ACTION
    ON UPDATE NO ACTION,
  CONSTRAINT `fk_contact_state`
    FOREIGN KEY (`state` )
    REFERENCES `states` (`state_id` )
    ON DELETE NO ACTION
    ON UPDATE NO ACTION
)
ENGINE = InnoDB;

-- -----------------------------------------------------
-- Data for table `states`
-- -----------------------------------------------------
SET AUTOCOMMIT=0;
INSERT INTO `states` (`state_id`, `name`) VALUES ('TX', 'Texas');
INSERT INTO `states` (`state_id`, `name`) VALUES ('CA', 'California');
INSERT INTO `states` (`state_id`, `name`) VALUES ('OR', 'Oregon');
COMMIT;

-- -----------------------------------------------------
-- Data for table `cities`
-- -----------------------------------------------------
SET AUTOCOMMIT=0;
INSERT INTO `cities` (`state_id`, `city_name`) VALUES ('CA', 'modesto');
INSERT INTO `cities` (`state_id`, `city_name`) VALUES ('OR', 'protland');
INSERT INTO `cities` (`state_id`, `city_name`) VALUES ('TX', 'Dallas');
COMMIT;

现在你的查询被简化了,除非你需要一个完整的状态命名法的极端情况:

SELECT
    concat(contacts.last,' ', contacts.first) as name,
    city,
    state,
    zip
FROM contacts
WHERE {INSERTWHERE}

答案 1 :(得分:0)

你的桌子正确加入了。我认为你在这里遇到的问题是你已经不正确地规范了你的数据并且走得太远了。只需存储提供的地址。不要尝试将其拆分为具有数字ID的表。

例如,通过将状态存储为联系表中的数字而不仅仅是状态代码,可以获得什么样的好处?同样的问题适用于城市。