【问题标题】:What's the best way to get related data from their ID's in a single query?在单个查询中从他们的 ID 获取相关数据的最佳方法是什么?
【发布时间】:2008-12-27 07:15:34
【问题描述】:

我有一个表,其中每一行都有一些字段,这些字段的 ID 与其他表中的一些其他数据相关。

假设它叫people,每个人都有citystatecountry的ID。

所以会有另外三个表,citiesstatescountries,每个表都有一个 ID 和一个名称。

当我选择一个人时,在单个查询中获取citystatecountry姓名的最简单方法是什么?

注意:我知道这可以通过连接实现,但是由于相关表更多,嵌套连接使查询难以阅读,我想知道是否有更简洁的方法。该人也应该可以将这些字段留空。

【问题讨论】:

    标签: sql mysql join relational


    【解决方案1】:

    假设以下表格:

    create table People
    (
         ID        int          not null primary key auto_increment
        ,FullName  varchar(255) not null
        ,StateID   int 
        ,CountryID int 
        ,CityID    int 
    )
    ;
    create table States
    (
         ID   int          not null primary key auto_increment
        ,Name varchar(255) not null
    )
    ;
    create table Countries
    (
         ID   int          not null primary key auto_increment
        ,Name varchar(255) not null
    )
    ;
    create table Cities
    (
         ID   int          not null primary key auto_increment
        ,Name varchar(255) not null
    )
    ;
    

    使用以下数据:

    insert into Cities(Name) values ('City 1'),('City 2'),('City 3');
    insert into States(Name) values ('State 1'),('State 2'),('State 3');
    insert into Countries(Name) values ('Country 1'),('Country 2'),('Country 3');
    insert into People(FullName,CityID,StateID,CountryID) values ('Has Nothing'   ,null,null,null);
    insert into People(FullName,CityID,StateID,CountryID) values ('Has City'      ,   1,null,null);
    insert into People(FullName,CityID,StateID,CountryID) values ('Has State'     ,null,   2,null);
    insert into People(FullName,CityID,StateID,CountryID) values ('Has Country'   ,null,null,   3);
    insert into People(FullName,CityID,StateID,CountryID) values ('Has Everything',   3,   2,   1);
    

    那么这个查询应该给你你所追求的。

    select 
     P.ID
    ,P.FullName
    ,Ci.Name as CityName
    ,St.Name as StateName
    ,Co.Name as CountryName
    from People P
    left Join Cities    Ci on Ci.ID = P.CityID
    left Join States    St on St.ID = P.StateID
    left Join Countries Co on Co.ID = P.CountryID
    

    【讨论】:

      【解决方案2】:

      JOINS 是真正做到这一点的唯一方法。

      您也许可以更改您的架构,但无论如何问题都是一样的。

      (City 总是在 State 中,State 总是在 Country 中 - 所以 Person 可以只引用 city_id 而不是全部三个。但您仍然需要加入 3 个表)。

      【讨论】:

      • 城市/州等只是示例。但是谢谢,我想我得嵌套一些左连接。
      • 城市名称不一定是唯一的。例如,以美国俄亥俄州迈阿密和佛罗里达州迈阿密为例,尽管人们可以很容易地看看加拿大安大略省,那里的城市伦敦和巴黎是其他国家的首都。
      • 没关系 - 您不一定需要名称是唯一的,因为您可以使用不同的保证唯一键(可能是自动递增的 id 值)。
      【解决方案3】:

      没有比连接更干净的方法了。如果允许字段为空,请使用外连接

      SELECT c.*, s.name AS state_name
        FROM customer c
        LEFT OUTER JOIN state s ON s.id = c.state
       WHERE c.id = 10
      

      【讨论】:

        【解决方案4】:

        根据您提供的模式描述,您必须在单个查询中使用 JOINS。

        SELECT 
           p.first_name 
         , p.last_name
         , c.name as city
         , s.name as state
         , co.name as country 
        FROM people p 
        LEFT OUTER JOIN city c 
          ON p.city_id = c.id
        LEFT OUTER JOIN state s 
          ON p.state_id = s.id
        LEFT OUTER JOIN country co
          ON p.country_id = co.id; 
        

        LEFT OUTER JOIN 将允许您获取人员的详细信息,即使某些 ID 为空白或为空。

        另一种方法是重新设计查找表。一个城市永远在一个州,一个州在一个国家。因此,您的 city 表将包含以下列:Id, Namestate_id。您的state 表将是:Id, Namecountry_idcountry 表将保持不变:IdName

        person 表现在将只有 1 个 id:city_id

        现在您的查询将是:

         SELECT 
           p.first_name 
         , p.last_name
         , c.name as city
         , s.name as state
         , co.name as country 
        FROM people p 
        LEFT OUTER JOIN city c 
          ON p.city_id = c.id
        LEFT OUTER JOIN state s 
          ON c.state_id = s.id
        LEFT OUTER JOIN country co
          ON s.country_id = co.id;
        

        注意最后两个 OUTER JOINS 的区别

        【讨论】:

          【解决方案5】:

          如果涉及的表是参考表(即它们保存在会话的生命周期内不会更改的查找数据),则根据您的应用程序的性质,您可以在您使用期间预加载参考数据应用程序启动。然后,您的查询不需要进行连接,而是返回 id 值,并且在您的应用程序中,当您需要显示数据时,您对 id 进行解码。

          【讨论】:

            【解决方案6】:

            最简单的解决方案是使用名称作为citystatecountry 中的主键。然后您的person 表可以通过名称而不是伪键“id”来引用它们。这样,您就不需要进行连接,因为您的 person 表已经具有所需的值。

            存储字符串而不是 4 字节的伪密钥确实需要更多空间。但是你可能会发现这种权衡是值得的,如果你受到连接的威胁,就像你看起来一样(顺便说一下,这就像一个 PHP 程序员不愿意使用foreach - 连接同样是 SQL 的基础方式)。

            还有许多城市名称出现在不止一个州。所以你的city 表应该引用state 表并使用这两列作为主键。

            CREATE TABLE cities (
              city_name VARCHAR(30),
              state     CHAR(2),
              PRIMARY KEY (city_name, state),
              FOREIGN KEY (state) REFERENCES states(state)
            );
            
            CREATE TABLE persons (
              person_id    SERIAL PRIMARY KEY,
              ...other columns...
              city_name    VARCHAR(30),
              state        CHAR(2),
              country_name VARCHAR(30),
              FOREIGN KEY (city_name, state) REFERENCES cities(city_name, state),
              FOREIGN KEY (country_name) REFERENCES countries(country_name)
            );
            

            这只是该技术的一个示例。当然它比这更复杂,因为您可能在多个国家/地区拥有城市名称,您可能拥有没有州的国家,等等。关键是 SQL 不会强制您使用整数伪键,因此请在适当的情况下使用 CHAR 和 VARCHAR 键。

            【讨论】:

              【解决方案7】:

              标准 SQL 的一个缺点是返回数据需要采用表格格式。 但是,一些数据库供应商添加了一些功能,可以选择非表格格式的数据。我不知道 MySQL 是否知道这些特性。

              【讨论】:

                【解决方案8】:

                创建一个视图,为您连接 Person、City、State 和 Country。然后只需在所有其他联接中引用视图。

                类似:

                CREATE VIEW FullPerson AS  
                SELECT Person.*, City.Name, State.Name, Country.Name  
                FROM  
                  Person LEFT OUTER JOIN City ON Person.CityId = City.Id  
                  LEFT OUTER JOIN State ON Person.StateId = State.Id  
                  LEFT OUTER JOIN Country ON Person.CountryId = Country.Id
                

                然后在其他查询中,可以

                SELECT FullPerson.*, Other.Value  
                FROM FullPerson LEFT OUTER JOIN Other ON FullPerson.OtherId = Other.Id
                

                【讨论】:

                  【解决方案9】:

                  所有很好的答案,但提问者指定他们不想使用连接。正如一位受访者所展示的,假设您的 CitiesStatesCountries 表有一个 Id 和一个 Description 字段,您可能可以执行以下操作:

                  SELECT
                     p.Name, c.Description, s.Description, ct.Description
                  FROM
                     People p, Cities c, States s, Countries ct
                  WHERE
                     p.Id = value AND
                     c.Id = value AND
                     s.Id = value AND
                     ct.Id = value;
                  

                  【讨论】:

                  • 那是使用语法晦涩的连接。
                  【解决方案10】:

                  加入是的答案。通过练习,它们会变得更易读。
                  在某些特殊情况下,创建函数会对您有所帮助,例如您可以执行以下操作(在 Oracle 中,我不知道任何 mysql):
                  您可以创建一个函数来返回给定城市州和国家/地区代码的格式化地址,然后您的查询变为

                  SELECT first_name, last_name, formated_address(city_id, state_id, country_id) 
                  FROM people
                  WHERE some_where_clause;
                  

                  formated_address 在城市状态和国家/地区表上进行单独查找,并在解码后的值之间放置分隔符,如果它们都是空的,则返回“无地址”等

                  【讨论】:

                  • OOO。不。这些结构的低效率是没有限制的
                  猜你喜欢
                  • 1970-01-01
                  • 2014-10-19
                  • 2013-04-16
                  • 1970-01-01
                  • 2014-03-30
                  • 1970-01-01
                  • 1970-01-01
                  • 2010-10-19
                  • 2017-12-11
                  相关资源
                  最近更新 更多