【问题标题】:how to avoid multiple rows populated in a join without using primarykey?如何在不使用主键的情况下避免在连接中填充多行?
【发布时间】:2019-12-28 11:01:15
【问题描述】:

我有一个这样的查询;

SELECT DISTINCT TOP (100) PERCENT ,
 a.Type,
 a.Period,
 s.SiteCode,
 s.Name AS SiteName,
 s.StreetAddress,

 .........
FROM dbo.Anal AS a 
INNER JOIN  dbo.TankSystems AS ts ON a.TankSystemId = ts.TankSystemId 
INNER JOIN dbo.Sites AS s ON ts.SiteId = s.SiteId 

Where s.SiteId=@SiteId

上述查询按预期返回结果。例如:3 行。

我有另一张表作为坦克,列有 tankid、tank number、siteid、grade、diameter。 我想用左/内连接返回 tanknumber。

如果我添加,

INNER/LEFt JOIN dbo.Tanks AS tt ON s.SiteId =tt.SiteId 

结果是三倍。比如说,如果我在 tanktable 中有 3 个特定站点 ID 的条目,我会得到 9 行结果。 我的问题是,除了 siteid 之外,我没有任何其他列来限制加入。

如何避免?

【问题讨论】:

  • 没有“左内连接”之类的东西——它要么是“左(外)连接”,要么是“内连接”。
  • 您不会根据基表行和/或业务情况说出您想要哪些行。请在代码问题中提供minimal reproducible example--cut & paste & runnable code;具有期望和实际输出的示例输入(包括逐字错误消息);标签和版本;明确的规范和解释。这包括您可以提供的最少代码,即您显示的代码可以通过您显示的代码扩展为不正常。 (调试基础。)在给出关系(船舶)/关联或表(基础或查询结果)时,说明其中的一行根据其列值说明了业务情况。
  • 查询不需要约束(包括PKs & FKs)。在每个基表和查询结果中,了解一行对业务情况的描述是必要且足够的。

标签: sql tsql join left-join


【解决方案1】:

您需要加入 Tanks 中的另一个(附加)列,或者您需要将 Tanks 折叠到每个 siteid 一行。

您选择哪种很大程度上取决于要求是什么。假设 Tanks 列出了站点上的储罐,并且每个站点都有一个塑料储罐和一个金属储罐,这就是每个站点有两个储罐行的原因。两个水箱均由同一制造商制造。您需要坦克制造商的 ID。这意味着您选择哪种类型的坦克并不重要,您可以添加以下连接条件:

AND tt.TankType = 'plastic'

查询现在丢弃所有金属罐,每个站点只有一个塑料罐,结果不会加倍(称为笛卡尔积),并且您通过塑料罐获得制造商名称。

假设您想知道现场所有储罐的总容量(以升为单位),即塑料加金属。如果您按站点 ID 对容量进行分组和求和,您将获得站点 ID 唯一的行:

INNER JOIN (SELECT siteid, sum(capacity) as sumcap FROM tanks GROUP BY siteid) tt
ON tt.siteid = s.siteid

您说过您需要 tanknumber,但是如果一个站点上有多个 tank number 怎么办。您想要最小的 tank number 吗?最高的?所有坦克编号的逗号分隔列表?

INNER JOIN (SELECT siteid, min(tanknumber) as firsttank FROM tanks GROUP BY siteid) tt
ON tt.siteid = s.siteid

INNER JOIN (SELECT siteid, max(tanknumber) as lasttank FROM tanks GROUP BY siteid) tt
ON tt.siteid = s.siteid

INNER JOIN (SELECT siteid, string_agg(tanknumber, ',') as tanklist FROM tanks GROUP BY siteid) tt
ON tt.siteid = s.siteid

这些都是如何将坦克列表简化为单个站点 ID 的示例,这是您必须做的事情;决定你想要什么,然后向数据库索要它

避免使用 DISTINCT

通常没有充分的理由。绝对不要仅仅使用它来压缩在您加入后出现的大量重复外观的行。

不要让数据库准备两倍的行数,然后运行一个昂贵的过程来过滤掉所有重复项。取而代之的是,每当将新表添加到联接中时,您会看到行加倍询问自己,您真正想要从该新表中得到什么,或者限制它以便只得到您想要的,或者将其分组/聚合,这样您想要的信息被压缩为单个标识符,并且不会导致您的行增加一倍/三倍/等等

【讨论】:

  • 我明白了。 persite 储罐具有唯一的储罐编号。这是一个序列。 1,2,3,4。表示该站点中有 4 个坦克。 All4 包含不同的燃料。所以我的要求是重新运行罐号为 1 或 2 或 3 或 4。容量或任何其他列都不是唯一的。该表中只有主键和 Tanknumber 是唯一的 :(
  • 您似乎是说任何油箱编号都可以,所以只需按照我的示例选择 MIN 或 MAX,或者如果每个站点都有一个 tanknumber 1(但不是每个站点都会有一个 tank编号 2 更不用说 3 或 4),只需添加 AND tt.tanknumber = 1。你并没有真正说出你想从坦克表中得到什么其他信息,所以很难给出具体的建议。 你为什么要加入坦克桌? “我要返回tanknumber”是什么意思?你想要最高的坦克号吗?坦克编号列表?它们包含的不同燃料的数量?
  • “正确”的油箱号是什么意思?正确=正确?右边=左边的对面?请尽量记住,你知道你想要什么,只有你向我解释清楚,我才会知道你想要什么;我无法读心
  • 我已经添加了一张结果图片,目前正在使用带有 siteid 限制的 tank 表上的左连接。您可以看到 TankNumber 列。它是如何填充的。理想情况下,Tank1 必须是 PULP,Tank2 必须是 Super,Tank 3 必须是无铅的
  • 所以你是说tank1总是存储PULP,tank2总是存储Super,tank3总是存储Unleaded,但是Tank表没有记录这个?罐表应该记录这一点,因为它是将罐中的内容与站点上的内容联系起来的过程的一部分。如果您真的想做出将这些数据放入代码中的可疑决定,请添加 AND ProductName = CASE tt.TankNumber WHEN 1 THEN 'PULP' WHEN 2 THEN 'Super' WHEN 3 THEN 'Unleaded' END 的连接条件
【解决方案2】:

试试这个。

SELECT DISTINCT TOP (100) PERCENT ,
 a.Type,
 a.Period,
 s.SiteCode,
 s.Name AS SiteName,
 s.StreetAddress,
 s.CityOrLocal,
 tanks= STUFF((
 SELECT ', ' + [tank number] 
   FROM  dbo.Tanks tt 
   where tt.SiteId = s.SiteId  
   FOR XML PATH, TYPE).value(N'.[1]', N'varchar(max)'), 1, 2, '')   
FROM dbo.Analyses AS a 
INNER JOIN  dbo.TankSystems AS ts ON a.TankSystemId = ts.TankSystemId 
INNER JOIN dbo.Sites AS s ON ts.SiteId = s.SiteId 

Where s.SiteId=@SiteId

【讨论】:

  • 这将为所有行返回相同的 TankNumber。即所有 3 行的 TankNumber=1。它应该是 TankNumber 1 ,2,3
  • 但这只是你的标准 s.SiteId =tt.SiteId
  • 嗯..是的。但是要求就像我说的那样..我无法更改架构..
  • 您使用的是哪个sql引擎和版本?
  • mssql 服务器 2012
猜你喜欢
  • 1970-01-01
  • 2021-03-15
  • 1970-01-01
  • 2017-09-27
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多