简体   繁体   English

如何设计表关系,其中外键可以表示“所有行”,“某些行”或“一行”?

[英]How to design table relationship where the foreign key can mean “all rows”, “some rows” or “one row”?

I hope you can help me with this. 我希望你能帮助我。 I've used pseudocode to keep everything simple. 我用伪代码来保持一切简单。

I have a table which describes locations. 我有一张描述位置的表格。

location_table
location = charfield(200) # New York, London, Tokyo

A product manager now wants locations to be as follows: 产品经理现在希望地点如下:

Global = select every location
Asia = select every location in Asia
US = select every location in US
Current system = London (etc.)

This is my proposed redesign. 这是我提出的重新设计。

location_table
location = charfield(200) # New York, London, Tokyo
continent = foreign key to continent_table

continent_table
continent = charfield(50) # "None", "Global", Asia, Europe

But this seems horrible. 但这似乎很可怕。 It means in my code I'll always need to check if the customer is using "global" or "none", and then select the corresponding location records. 这意味着在我的代码中我总是需要检查客户是使用“全局”还是“无”,然后选择相应的位置记录。 For example, there will be code like this scattered everywhere: 例如,会有像这样分散在各处的代码:

get continent 
if continent is global, select everything from location_table
else if continent is none, select location from location_table
else select location from location_table where foreign key is continent

My feeling is this is a known problem, and there is a known solution for it. 我的感觉是这是一个已知的问题,并且有一个已知的解决方案。 Any ideas? 有任何想法吗?

Thank you. 谢谢。

Use levels: 使用级别:

0   -> None
00  -> Global
001 -> Europe
002 -> Asia
003 -> Africa

select location from location_table where continent like '[value]%'

Using a fixed length code, you can prefix regions, and then add one more digit for a region inside a region, and so on. 使用固定长度代码,您可以为区域添加前缀,然后为区域内的区域添加一个数字,依此类推。

Ok, let me try to improve it. 好吧,让我试着改进它。

Consider the world, it has the minimum level (or maximum depending on how you see it) 考虑一下这个世界,它具有最低水平(或最大值取决于你如何看待它)

World ID = '0' (1 digit)

Now, select how you want to divide the world: (Continents, Half-Continents, ...) and assign the next level. 现在,选择你想要划分世界的方式:(大陆,半大陆......)并指定下一个级别。

Europe ID  = '01' (First digit World + Second digit Europe)
Asia ID    = '02' 
America ID = '03'
...

Next Level: Countries. 下一级:国家。 (At least 2 digits) (至少2位数)

England ID    = '0101' (World + Continent + Country)
Deutchland ID = '0102'
....
Texas ID      = '0301'
....

Next Level: Regions (2 digits) 下一级:地区(2位数)

Yorkshire ID = '010101' (World + Continent + Country + Region)
....

Next Level: Cities (2 or 3 digits) 下一级:城市(2或3位数)

London ID = '01010101' (World + Continent + Country + Region + City)

And so on. 等等。

Now, the same SELECT some_aggregate, statistics, ... FROM ... can be used for no matter what region, simply change: 现在,相同的SELECT some_aggregate, statistics, ... FROM ...可以用于任何区域,只需更改:

WHERE Region like '0%'                        --> The whole world
WHERE Region like '02%'                       --> Asia
WHERE Region like '01010101%'                 --> London
WHERE Region like '02%' AND Region like '01%' --> Asia & Europe

What you seem to have here is a set of locations, and then a set of location groups. 你在这里看到的是一组位置,然后是一组位置组。 Those groups might be all of the locations (global), or a subset of them. 这些组可能是所有位置(全局)或其中的一部分。

You can build this with an intermediate table between the locations and a new location sets table which associates locations and location sets. 您可以使用位置之间的中间表和关联位置和位置集的新位置集表来构建它。

You might build the location set table and the join table so that the individual locations are also location sets, but ones which join only to one location. 您可以构建位置集表和连接表,以便各个位置也是位置集,但只能连接到一个位置。 That way all location selections come from one table -- the location sets. 这样,所有位置选择都来自一个表 - 位置设置。

So you end up with three different types of location set: 因此,您最终会得到三种不同类型的位置集:

  1. Ones which map 1:1 with a location 与地点1:1的地图
  2. One which maps 1:all ("global") 一个映射1:全部(“全局”)
  3. Ones which map 1:many (continents and other areas) 地图1:许多(大陆和其他地区)

It's conceivable that this could be created as a hierarchy, but those queries can be inefficient because the join cardinalities tend to be obscured from the optimiser. 可以想象,这可以作为层次结构创建,但是这些查询可能效率低下,因为连接基数往往会被优化器模糊。

You could do this using a hierarchy, and a self referencing foreign key, eg 您可以使用层次结构和自引用外键来执行此操作,例如

LocationID      Name        ParentLocationID        LocationType
------------------------------------------------------------------
    1        Planet Earth      NULL                 Planet
    2           Africa          1                   Continent
    3         Antartica         1                   Continent
    4           Asia            1                   Continent
    5        Australasia        1                   Continent
    6           Europe          1                   Continent
    7        North America      1                   Continent
    8       South America       1                   Continent
    9       United States       7                   Country
    10         Canada           7                   Country
    11         Mexico           7                   Country
    12      California          9                   State
    13      San Diego           12                  City
    14        England           6                   Country
    15      Cornwall            14                  County
    16        Truro             15                  City

Hierarchical data usually requires either recursion, or multiple joins to get all levels, this answer contains links to articles comparing performance on the major DBMS. 分层数据通常需要递归或多个连接才能获得所有级别, 此答案包含指向比较主要DBMS上的性能的文章的链接。

Many DBMS now support recursive Common table expressions, and since no DBMS is specified I will use SQL Server syntax because it is what I am most comfortable with, a quick example would be. 许多DBMS现在支持递归公用表表达式,并且由于没有指定DBMS,我将使用SQL Server语法,因为它是我最熟悉的,一个简单的例子。

DECLARE @LocationID INT = 7; -- NORTH AMERICA

WITH LocationCTE AS
(   SELECT  l.LocationID, l.Name, l.ParentLocationID, l.LocationType
    FROM    dbo.Location AS l   
    WHERE   LocationID = @LocationID
    UNION ALL
    SELECT  l.LocationID, l.Name, l.ParentLocationID, l.LocationType
    FROM    dbo.Location AS l
            INNER JOIN LocationCTE AS c
                ON c.LocationID = l.ParentLocationID
)
SELECT  *
FROM    LocationCTE;

Output based on above sample data 基于以上样本数据的输出

LocationID  Name            ParentLocationID    LocationType
-----------------------------------------------------------------
7           North America   1                   Continent
9           United States   7                   Country
10          Canada          7                   Country
11          Mexico          7                   Country
12          California      9                   State
13          San Diego       12                  City

Online Demo 在线演示

Supplying a value of 1 (Planet Earth) for the location ID will return the full table, or supplying a locationID of 11 (Mexico) would only return this one row, because there is nothing smaller than this in the sample data. 为位置ID提供值1(行星地球)将返回完整表,或者提供11的位置ID(墨西哥)将仅返回此一行,因为样本数据中没有任何小于此值。

I'll go with your answer and say that I don't find it quite horrible to look everytime a customer to check if he searches by city or location, or nothing. 我会回答你的问题,并说每次客户检查他是否按城市或地点搜索,或者什么也没有,我都觉得这很糟糕。 That would be the role of the backend code and would always lead to different queries depending on what option he chooses. 这将是后端代码的作用,并且总是会根据他选择的选项导致不同的查询。

But I would remove "None", "Global" from the continent table, and just use other queries when these option are not chosen. 但我会从大陆表中删除“无”,“全局”,并在未选择这些选项时使用其他查询。 You would end up with the 3 possibles SQL queries you have, and I don't find it to be bad design per se. 你最终会得到3种可能的SQL查询,而且我发现它本身并不是糟糕的设计。 Maybe other solution are more performant, but this one seems to be more readable and logical. 也许其他解决方案更具性能,但这个解决方案似乎更具可读性和逻辑性。 It's just optional querying with join tables. 它只是可选的查询连接表。

Other answer will trade performance/duplication for readability (which isn't a bad thing, depending on how many time you will be relying on this condition in your application, in how many queries you'll be using it, and how many cities you have). 其他答案将交换性能/重复以提高可读性(这不是一件坏事,取决于您在应用程序中依赖此条件的时间,您将使用它的查询数量,以及您有多少个城市有)。

For readability and non-repetition, the best thing would be to concentrate these condition in one SQL function wich take a string parameter and return all location depending on the input (but at the cost of preformance). 对于可读性和非重复性,最好的方法是将这些条件集中在一个SQL函数中,该函数采用字符串参数并根据输入返回所有位置(但是以性能为代价)。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

相关问题 如何设计这些表以防止其中一个表的外键引用的行必须一致的数据损坏? - How can I design these tables to prevent corrupted data where one table's foreign keys' referenced rows must agree? 即使有一行匹配条件,也要获取外键的所有行 - Fetch all rows for a foreign key if even one row matches condition 如何选择外键子行早于特定日期的所有父行? - How can I select all parent rows with a foreign key child row older than a certain date? 如何将具有相同外键的多个行的数据汇总到一行? - How can I sum data from mulitple rows which have the same foreign key into one row? 抓取表中的所有行,并在有条件的情况下抓取一行 - Grab all rows in a table and grab one row where condition php(数组)-> mysql(带有外键的多行)更新表中特定行,其中id和行号不同 - php (array)-> mysql (multiple rows with a foreign key) Update specific row in table where id and row number varies 表A的列和表B的行之间的外键关系? - Foreign Key Relationship Between Columns of Table A and Rows of Table B? SQL 强制执行规则的关系,即一个表只能引用具有相同外键的另一个表的行 - SQL relationship to enforce rule that a table can only reference rows of another table with the same foreign key 如何根据存在于具有外键关系的不同表中的字段从表中获取行? - How to fetch rows from a table based on a field which is present in a different table having a foreign key relationship with it? 如何关联一个相关表行中的某些行? - How to relation some rows from one related table row?
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM