AskOverflow.Dev

AskOverflow.Dev Logo AskOverflow.Dev Logo

AskOverflow.Dev Navigation

  • 主页
  • 系统&网络
  • Ubuntu
  • Unix
  • DBA
  • Computer
  • Coding
  • LangChain

Mobile menu

Close
  • 主页
  • 系统&网络
    • 最新
    • 热门
    • 标签
  • Ubuntu
    • 最新
    • 热门
    • 标签
  • Unix
    • 最新
    • 标签
  • DBA
    • 最新
    • 标签
  • Computer
    • 最新
    • 标签
  • Coding
    • 最新
    • 标签
主页 / dba / 问题 / 338587
Accepted
John K. N.
John K. N.
Asked: 2024-04-12 20:31:19 +0800 CST2024-04-12 20:31:19 +0800 CST 2024-04-12 20:31:19 +0800 CST

为什么 SQL Server 需要 LEFT JOIN 来检索不存在的状态?

  • 772

我最近正在查询我们的内部数据库库存工具以获取服务器、实例和数据库的列表,并向每个服务器、实例和数据库添加相应的状态。

关系图

Server ˂-- 1 : n --˃ Instance ˂-- 1 : n --˃ Database
   ˄                    ˄                      ˄
   |                    |                      |
   |                  1 : 1                    |
   |                    |                      |
   |                    ˅                      |
   +-- 1 : 1 --˃     Status        ˂-- 1 : 1 --+

读作:
...一个服务器可以有多个实例
...一个实例可以有多个数据库
...一个服务器、一个实例和一个数据库可以有一个状态

设置

状态表

CREATE TABLE [Status]
(
  StatusID int,
  StatusName char(20),
  );

状态数据

INSERT INTO [Status] (StatusID, StatusName)
VALUES
(1,'Productive'),
(2,'Prod ACC'),
(3,'Prod APP'),
(4,'Test'),
(5,'Test ACC'),
(6,'Test APP'),
(7,'OFFLINE'),
(8,'Reserved'),
(9,'Decommisioned');

服务器表

CREATE TABLE [Server]
(
  ServerID int,
  ServerName char(20),
  ServerStatusID int
  );

服务器数据

INSERT INTO [Server] (ServerID, ServerName, ServerStatusID)
VALUES
(1,'FirstServer',1),
(2,'SecondServer',2),
(3,'ThirdServer',5),
(4,'FourthServer',8),
(5,'FifthServer',8);

实例表

CREATE TABLE [Instance]
(
  InstanceID int,
  ServerID int,
  InstanceName char(30),
  InstanceStatusID int
  );

实例数据

INSERT INTO [Instance] 
  (InstanceID, ServerID, InstanceName, InstanceStatusID)
VALUES
(1,1,'GENERAL',1),
(2,1,'TAXES',1),
(3,2,'GENERAL',9),
(4,2,'SOCIAL',2),
(5,3,'GENERAL',5),
(6,3,'FBI',8),
(7,5,'COMINGSOON',8);

数据库表

CREATE TABLE [Database]
(
  DatabaseID int,
  InstanceID int,
  DatabaseName char(30),
  DatabaseStatusID int
  );

数据库数据

INSERT INTO [Database]
(DatabaseID, InstanceID, DatabaseName, DatabaseStatusID)
VALUES
(1,1,'master',1),
(2,1,'model',1),
(3,1,'msdb',1),
(4,1,'UserDB1',1),
(5,2,'master',1),
(6,2,'model',1),
(7,2,'msdb',1),
(8,2,'TaxesDB',1),
(9,4,'master',2),
(10,4,'model',2),
(11,4,'msdb',2),
(12,4,'HealthCareDB',2),
(13,5,'master',5),
(14,5,'model',5),
(15,5,'msdb',5),
(16,5,'GeneralUserDB',5),
(17,6,'master',8),
(18,6,'model',8),
(19,6,'msdb',8),
(20,6,'CriminalDB',8);

不涉及状态表的 SELECT 语句

初始 SELECT 语句仅涉及连接三个表:服务器、实例、数据库,如下所示:

-- Simple SELECT to get all information on Servers, Instances and Databases
-- The status of the server, instance or database is not returned
SELECT 
  ServerName, 
  InstanceName,
  DatabaseName 
  FROM [Server] as srv
    LEFT JOIN [Instance] as ins
      ON srv.ServerID = ins.ServerID
    LEFT JOIN [Database] as dbs
      ON ins.InstanceID = dbs.InstanceID;

1. 声明的结果

请注意...

  • 有一台没有实例和数据库的服务器
  • 有一个实例没有数据库
服务器名称 实例名称 数据库名称
第一服务器 一般的 掌握
第一服务器 一般的 模型
第一服务器 一般的 数据库数据库
第一服务器 一般的 用户数据库1
第一服务器 税收 掌握
第一服务器 税收 模型
第一服务器 税收 数据库数据库
第一服务器 税收 税收数据库
第二服务器 一般的 无效的
第二服务器 社会的 掌握
第二服务器 社会的 模型
第二服务器 社会的 数据库数据库
第二服务器 社会的 医疗保健数据库
第三服务器 一般的 掌握
第三服务器 一般的 模型
第三服务器 一般的 数据库数据库
第三服务器 一般的 通用用户数据库
第三服务器 联邦调查局 掌握
第三服务器 联邦调查局 模型
第三服务器 联邦调查局 数据库数据库
第三服务器 联邦调查局 刑事数据库
第四服务器 无效的 无效的
第五服务器 即将推出 无效的

涉及 Status 表的 SELECT 语句

在下一个语句中,我决定将状态添加到每个元素(服务器、实例、数据库),并JOIN使用表编辑每个表,Status如下所示:

-- Advanced SELECT to get all information on Servers, Instances and Databases 
-- including their status
SELECT 
  ServerName, 
  srvst.StatusName,
  InstanceName,
  insst.StatusName,
  DatabaseName,
  dbsst.StatusName
  FROM [Server] as srv
    JOIN [Status] as srvst
      ON srv.ServerStatusID = srvst.StatusID
    LEFT JOIN [Instance] as ins
      ON srv.ServerID = ins.ServerID
    JOIN [Status] as insst
      ON ins.InstanceStatusID = insst.StatusID
    LEFT JOIN [Database] as dbs
      ON ins.InstanceID = dbs.InstanceID
    JOIN [Status] as dbsst
      ON dbs.DatabaseStatusID = dbsst.StatusID
  ;

2. 声明结果

令我惊讶的是,没有实例和数据库的服务器以及有实例但没有数据库的服务器不再列出:

服务器名称 状态名称 实例名称 状态名称 数据库名称 状态名称
第一服务器 富有成效 一般的 富有成效 掌握 富有成效
第一服务器 富有成效 一般的 富有成效 模型 富有成效
第一服务器 富有成效 一般的 富有成效 数据库数据库 富有成效
第一服务器 富有成效 一般的 富有成效 用户数据库1 富有成效
第一服务器 富有成效 税收 富有成效 掌握 富有成效
第一服务器 富有成效 税收 富有成效 模型 富有成效
第一服务器 富有成效 税收 富有成效 数据库数据库 富有成效
第一服务器 富有成效 税收 富有成效 税收数据库 富有成效
第二服务器 产品ACC 社会的 产品ACC 掌握 产品ACC
第二服务器 产品ACC 社会的 产品ACC 模型 产品ACC
第二服务器 产品ACC 社会的 产品ACC 数据库数据库 产品ACC
第二服务器 产品ACC 社会的 产品ACC 医疗保健数据库 产品ACC
第三服务器 测试ACC 一般的 测试ACC 掌握 测试ACC
第三服务器 测试ACC 一般的 测试ACC 模型 测试ACC
第三服务器 测试ACC 一般的 测试ACC 数据库数据库 测试ACC
第三服务器 测试ACC 一般的 测试ACC 通用用户数据库 测试ACC
第三服务器 测试ACC 联邦调查局 预订的 掌握 预订的
第三服务器 测试ACC 联邦调查局 预订的 模型 预订的
第三服务器 测试ACC 联邦调查局 预订的 数据库数据库 预订的
第三服务器 测试ACC 联邦调查局 预订的 刑事数据库 预订的

调查结果/解决方案

通过反复试验的方法检查了各种选项后,我发现必须将表JOIN上的Status更改为 a ,LEFT JOIN以允许该语句显示没有实例或数据库的服务器,并显示没有数据库的实例:

-- Advanced SELECT to get all information on Servers, Instances and Databases 
-- including their status
SELECT 
  ServerName, 
  srvst.StatusName,
  InstanceName,
  insst.StatusName,
  DatabaseName,
  dbsst.StatusName
  FROM [Server] as srv
    LEFT JOIN [Status] as srvst
      ON srv.ServerStatusID = srvst.StatusID
    LEFT JOIN [Instance] as ins
      ON srv.ServerID = ins.ServerID
    LEFT JOIN [Status] as insst
      ON ins.InstanceStatusID = insst.StatusID
    LEFT JOIN [Database] as dbs
      ON ins.InstanceID = dbs.InstanceID
    LEFT JOIN [Status] as dbsst
      ON dbs.DatabaseStatusID = dbsst.StatusID;

3. 声明的结果

服务器名称 状态名称 实例名称 状态名称 数据库名称 状态名称
第一服务器 富有成效 一般的 富有成效 掌握 富有成效
第一服务器 富有成效 一般的 富有成效 模型 富有成效
第一服务器 富有成效 一般的 富有成效 数据库数据库 富有成效
第一服务器 富有成效 一般的 富有成效 用户数据库1 富有成效
第一服务器 富有成效 税收 富有成效 掌握 富有成效
第一服务器 富有成效 税收 富有成效 模型 富有成效
第一服务器 富有成效 税收 富有成效 数据库数据库 富有成效
第一服务器 富有成效 税收 富有成效 税收数据库 富有成效
第二服务器 产品ACC 一般的 退役 无效的 无效的
第二服务器 产品ACC 社会的 产品ACC 掌握 产品ACC
第二服务器 产品ACC 社会的 产品ACC 模型 产品ACC
第二服务器 产品ACC 社会的 产品ACC 数据库数据库 产品ACC
第二服务器 产品ACC 社会的 产品ACC 医疗保健数据库 产品ACC
第三服务器 测试ACC 一般的 测试ACC 掌握 测试ACC
第三服务器 测试ACC 一般的 测试ACC 模型 测试ACC
第三服务器 测试ACC 一般的 测试ACC 数据库数据库 测试ACC
第三服务器 测试ACC 一般的 测试ACC 通用用户数据库 测试ACC
第三服务器 测试ACC 联邦调查局 预订的 掌握 预订的
第三服务器 测试ACC 联邦调查局 预订的 模型 预订的
第三服务器 测试ACC 联邦调查局 预订的 数据库数据库 预订的
第三服务器 测试ACC 联邦调查局 预订的 刑事数据库 预订的
第四服务器 预订的 无效的 无效的 无效的 无效的
第五服务器 预订的 即将推出 预订的 无效的 无效的

参考资料

这里有一个db<>fiddle的链接来重现我的发现。

问题

为什么 SQL Server 需要在表LEFT JOIN中Status显示不存在的子项以及查询来显示这些项?

sql-server
  • 3 3 个回答
  • 431 Views

3 个回答

  • Voted
  1. Best Answer
    Charlieface
    2024-04-13T01:03:36+08:002024-04-13T01:03:36+08:00

    您需要嵌套连接。否则,会发生的情况是,它期望每个单独的连接子句返回一个结果,但如果前一个连接子句没有返回任何结果,则它不能返回结果。

    Essentially, you want the server to take the result of the inner-join of Instance and Status, and left-join all of that back to Server.

    SELECT 
      ServerName, 
      srvst.StatusName,
      InstanceName,
      insst.StatusName,
      DatabaseName,
      dbsst.StatusName
    FROM Server as srv
      JOIN Status as srvst
        ON srv.ServerStatusID = srvst.StatusID
      LEFT JOIN (
          Instance as ins
          JOIN Status as insst
            ON ins.InstanceStatusID = insst.StatusID
        )
        ON srv.ServerID = ins.ServerID
      LEFT JOIN (
          Database as dbs
          JOIN Status as dbsst
            ON dbs.DatabaseStatusID = dbsst.StatusID
        )
        ON ins.InstanceID = dbs.InstanceID;
    

    In SQL Server, the parenthesis are not essential, they are purely for readability. The key is putting JOIN table2 ON ... inside another join, so it comes out to LEFT JOIN table1 JOIN table2 ON ... ON ... ie the ON clauses are nested.

    This is the exact equivalent of using derived subqueries:

    SELECT 
      ServerName, 
      srvst.StatusName,
      InstanceName,
      ins.StatusName,
      DatabaseName,
      dbs.StatusName
    FROM Server as srv
      JOIN Status as srvst
        ON srv.ServerStatusID = srvst.StatusID
      LEFT JOIN (
          SELECT ins.*, insst.StatusName
          FROM Instance as ins
          JOIN Status as insst
            ON ins.InstanceStatusID = insst.StatusID
        ) ins
        ON srv.ServerID = ins.ServerID
      LEFT JOIN (
          SELECT dbs.*, dbsst.StatusName
          Database as dbs
          JOIN Status as dbsst
            ON dbs.DatabaseStatusID = dbsst.StatusID
        ) dbs
        ON ins.InstanceID = dbs.InstanceID;
    
    • 6
  2. Shivam Kumar
    2024-04-12T23:20:40+08:002024-04-12T23:20:40+08:00

    因为子项与状态表的联接是基于 ins.InstanceStatusID、dbs.DatabaseStatusID 进行的,当其之前的联接发生在某些行中时,结果为 NULL。

    当您稍后执行内部联接时,它会显示两个表中匹配的行,并且要获取具有空值的行,您将必须使用左联接。

    可视化相同的:

    SELECT 
      ServerName, 
      srvst.StatusName,
      InstanceName,
    
      DatabaseName,
    
      ins.InstanceStatusID,
      dbs.DatabaseStatusID 
      
      FROM [Server] as srv
        LEFT JOIN [Status] as srvst
          ON srv.ServerStatusID = srvst.StatusID
        LEFT JOIN [Instance] as ins
          ON srv.ServerID = ins.ServerID
        LEFT JOIN [Database] as dbs
          ON ins.InstanceID = dbs.InstanceID 
    

    enter image description here

    SELECT TMP.* ,    insst.StatusName,
      dbsst.StatusName FROM (SELECT 
      ServerName, 
      srvst.StatusName,
      InstanceName,
    
      DatabaseName,
    
      ins.InstanceStatusID,
      dbs.DatabaseStatusID 
      
      FROM [Server] as srv
        LEFT JOIN [Status] as srvst
          ON srv.ServerStatusID = srvst.StatusID
        LEFT JOIN [Instance] as ins
          ON srv.ServerID = ins.ServerID
        LEFT JOIN [Database] as dbs
          ON ins.InstanceID = dbs.InstanceID ) AS TMP
    
        /* Here if left join is not used values which are null are skipped and only values which are in both tables are returned */
    
        JOIN [Status] as insst
          ON TMP.InstanceStatusID = insst.StatusID 
        JOIN [Status] as dbsst
          ON TMP.DatabaseStatusID = dbsst.StatusID ;
    

    enter image description here

    使用左连接后,会从左结果集中提取所有记录,并从连接到结果集的两个表中提取匹配值

    SELECT TMP.* ,    insst.StatusName,
      dbsst.StatusName FROM (SELECT 
      ServerName, 
      srvst.StatusName,
      InstanceName,
    
      DatabaseName,
    
      ins.InstanceStatusID,
      dbs.DatabaseStatusID 
      
      FROM [Server] as srv
        LEFT JOIN [Status] as srvst
          ON srv.ServerStatusID = srvst.StatusID
        LEFT JOIN [Instance] as ins
          ON srv.ServerID = ins.ServerID
        LEFT JOIN [Database] as dbs
          ON ins.InstanceID = dbs.InstanceID ) AS TMP
        
        LEFT JOIN [Status] as insst
          ON TMP.InstanceStatusID = insst.StatusID 
        LEFT JOIN [Status] as dbsst
          ON TMP.DatabaseStatusID = dbsst.StatusID ;
    

    enter image description here

    • 2
  3. Paul White
    2024-04-15T16:01:06+08:002024-04-15T16:01:06+08:00

    Why does SQL Server require a LEFT JOIN on the Status table for child items that do not exist and for the query to display these items?

    It doesn't, in general. It is just one of the ways to express your requirement that produces the results you are after.

    Your query tries to (inner) join from a null-extended row (created by an outer join) to the Status table using a join predicate that looks for an equality match on StatusID values.

    This predicate does not return true because StatusID is null on one side, there is no matching null in the Status table, and the equality predicate would reject null matches anyway. Since the join predicate fails and you have specified an inner join, no result row is produced.

    For clarity, the inner join to Status could produce a result with a different join predicate.

    For example, there could be a null StatusID in the Status table (something your schema allows) and the join predicate could use IS NOT DISTINCT FROM or an equivalent test where nulls match. You could also use COALESCE or ISNULL in the join predicate to select a particular status (as a default) without adding any new status rows.

    There are many possibilities; the point is you could write a join predicate that would produce a match. The one you have does not.

    It is possible you thought SQL Server would skip the join to Status for any rows previously null-extended by an outer join, but things don't work that way. An outer join is not an 'optional' join with subsequent short-circuiting. Perhaps some people have a mental image of them functioning that way.

    Making any following joins 'outer' as a way to fix the output is close to using 'magic'—a recipe that works but isn't understood.

    Why does it work here? Because, with a left outer join, the null-extended row is preserved despite there being no match in Status according to the join predicate. The missing status columns are populated with null, as usual.

    An alternative solution

    People do seem to find left outer joins more intuitive to work with, but there's no particular reason to prefer them otherwise. You don't have to start at the top of the hierarchy and 'join down' to rows that may or may not be present.

    Let's see what happens with your schema if we instead start at the bottom of the hierarchy (the [Database] table) and work up. The first join to Status is straightforward:

    SELECT
        DBS.DatabaseName,
        DBSST.StatusName
    FROM dbo.[Database] AS DBS
    JOIN dbo.[Status] AS DBSST
        ON DBSST.StatusID = DBS.DatabaseStatusID;
    

    Moving up the hierarchy, we now need to add rows from the Instance table. We can't use a left outer join or inner join because there may be an instance without a database. The natural solution is to use a right join, with the associated inner join to Status:

    SELECT
        INS.InstanceName,
        INSST.StatusName,
        DBS.DatabaseName,
        DBSST.StatusName
    FROM dbo.[Database] AS DBS
    JOIN dbo.[Status] AS DBSST
        ON DBSST.StatusID = DBS.DatabaseStatusID
    RIGHT JOIN dbo.Instance AS INS
        ON INS.InstanceID = DBS.InstanceID
    JOIN dbo.[Status] AS INSST
        ON INSST.StatusID = INS.InstanceStatusID;
    

    There is no need for 'nested' joins here. We quite naturally add in any instances without a database, adding null-extended database rows as necessary. All instances are accounted for, so the inner join to Status is perfectly correct.

    We follow the same approach to add in rows from the Server table, along with its status:

    SELECT
        SRV.ServerName,
        SVS.StatusName,
        INS.InstanceName,
        INSST.StatusName,
        DBS.DatabaseName,
        DBSST.StatusName
    FROM dbo.[Database] AS DBS
    JOIN dbo.[Status] AS DBSST
        ON DBSST.StatusID = DBS.DatabaseStatusID
    RIGHT JOIN dbo.Instance AS INS
        ON INS.InstanceID = DBS.InstanceID
    JOIN dbo.[Status] AS INSST
        ON INSST.StatusID = INS.InstanceStatusID
    RIGHT JOIN dbo.[Server] AS SRV
        ON SRV.ServerID = INS.ServerID
    JOIN dbo.[Status] AS SVS
        ON SVS.StatusID = SRV.ServerStatusID;
    

    This produces the results you want, without invoking any magic.

    There are any number of ways to write a correct query specification for your requirement. I show a RIGHT JOIN alternative mostly because people seem somewhat scared by right outer joins or consider them redundant.

    • 2

相关问题

  • SQL Server - 使用聚集索引时如何存储数据页

  • 我需要为每种类型的查询使用单独的索引,还是一个多列索引可以工作?

  • 什么时候应该使用唯一约束而不是唯一索引?

  • 死锁的主要原因是什么,可以预防吗?

  • 如何确定是否需要或需要索引

Sidebar

Stats

  • 问题 205573
  • 回答 270741
  • 最佳答案 135370
  • 用户 68524
  • 热门
  • 回答
  • Marko Smith

    连接到 PostgreSQL 服务器:致命:主机没有 pg_hba.conf 条目

    • 12 个回答
  • Marko Smith

    如何让sqlplus的输出出现在一行中?

    • 3 个回答
  • Marko Smith

    选择具有最大日期或最晚日期的日期

    • 3 个回答
  • Marko Smith

    如何列出 PostgreSQL 中的所有模式?

    • 4 个回答
  • Marko Smith

    列出指定表的所有列

    • 5 个回答
  • Marko Smith

    如何在不修改我自己的 tnsnames.ora 的情况下使用 sqlplus 连接到位于另一台主机上的 Oracle 数据库

    • 4 个回答
  • Marko Smith

    你如何mysqldump特定的表?

    • 4 个回答
  • Marko Smith

    使用 psql 列出数据库权限

    • 10 个回答
  • Marko Smith

    如何从 PostgreSQL 中的选择查询中将值插入表中?

    • 4 个回答
  • Marko Smith

    如何使用 psql 列出所有数据库和表?

    • 7 个回答
  • Martin Hope
    Jin 连接到 PostgreSQL 服务器:致命:主机没有 pg_hba.conf 条目 2014-12-02 02:54:58 +0800 CST
  • Martin Hope
    Stéphane 如何列出 PostgreSQL 中的所有模式? 2013-04-16 11:19:16 +0800 CST
  • Martin Hope
    Mike Walsh 为什么事务日志不断增长或空间不足? 2012-12-05 18:11:22 +0800 CST
  • Martin Hope
    Stephane Rolland 列出指定表的所有列 2012-08-14 04:44:44 +0800 CST
  • Martin Hope
    haxney MySQL 能否合理地对数十亿行执行查询? 2012-07-03 11:36:13 +0800 CST
  • Martin Hope
    qazwsx 如何监控大型 .sql 文件的导入进度? 2012-05-03 08:54:41 +0800 CST
  • Martin Hope
    markdorison 你如何mysqldump特定的表? 2011-12-17 12:39:37 +0800 CST
  • Martin Hope
    Jonas 如何使用 psql 对 SQL 查询进行计时? 2011-06-04 02:22:54 +0800 CST
  • Martin Hope
    Jonas 如何从 PostgreSQL 中的选择查询中将值插入表中? 2011-05-28 00:33:05 +0800 CST
  • Martin Hope
    Jonas 如何使用 psql 列出所有数据库和表? 2011-02-18 00:45:49 +0800 CST

热门标签

sql-server mysql postgresql sql-server-2014 sql-server-2016 oracle sql-server-2008 database-design query-performance sql-server-2017

Explore

  • 主页
  • 问题
    • 最新
    • 热门
  • 标签
  • 帮助

Footer

AskOverflow.Dev

关于我们

  • 关于我们
  • 联系我们

Legal Stuff

  • Privacy Policy

Language

  • Pt
  • Server
  • Unix

© 2023 AskOverflow.DEV All Rights Reserve