繁体   English   中英

SQL-使用INNER JOIN的WHERE子句中的“ NOT IN”不起作用

[英]SQL - “NOT IN” in WHERE clause using INNER JOIN not working

我需要过滤基于子表数据的表。

我将以催眠数据为例,以便于解释:

  • 主表: Cars
  • 子表: Attributes (例如Colorcar typeaccessories

这些属性具有一个id( idOption )和所选值( idList

因此,在一个示例中,我需要过滤所有颜色为( idOption = 10 )黄色( idList = 45 )的汽车。 我无法直接对此进行过滤,因为搜索需要考虑其他选项的结果(包括类型,附件)。

当我仅将NOT IN用于一张桌子时,它可以工作。 但是,当我使用INNER JOIN合并这两个表时,它不起作用。

因此,总而言之,我需要使用给定值过滤3 idOption(当不为NULL时),这需要反映在主表中(按产品分组)。

台式

idProduct  |  Description
1             Product A
2             Product B
3             Product C

属性

idRow   idProduct   idOption    idList
---------------------------------------
1       1           10          45
2       2           10          46
3       3           10          47
4       1           11          10
5       2           11          98
6       1           14          56
7       3           16          28
8       2           20          55

这是我创建的不起作用的存储过程:

ALTER PROCEDURE [dbo].[SP_GET_TestSearch]
    (@Param1 BIGINT = NULL,
     @PValue1 BIGINT = NULL,
     @Param2 BIGINT = NULL,
     @PValue2 BIGINT = NULL,
     @Param3 BIGINT = NULL,
     @PValue3 BIGINT = NULL)
AS
    SET NOCOUNT ON;

    SELECT
        Cars.idProduct,
        Cars.[Description]
    FROM 
        Cars
    INNER JOIN 
        Attributes ON Cars.idProduct = Attributes.idProduct
    WHERE
        ((@Param1 IS NULL OR (idOption NOT IN (@Param1)))
        AND
        (@Param2 IS NULL OR (idOption NOT IN (@Param2)))
        AND
        (@Param3 IS NULL OR (idOption NOT IN (@Param3))))
        OR
        (idOption = ISNULL(@Param1, NULL) 
         AND idList = ISNULL(@PValue1, NULL))
        OR
        (idOption = ISNULL(@Param2, NULL) 
         AND idList = ISNULL(@PValue2, NULL))
        OR
        (idOption = ISNULL(@Param3, NULL) 
         AND idList = ISNULL(@PValue3, NULL))
    GROUP BY 
        Cars.idProduct, Cars.[Description]

这里有几件事。

首先,出于各种原因,这种捕获所有程序有点反模式,请参见此处以获取完整说明:-https: //sqlinthewild.co.za/index.php/2018/03/13/revisiting -捕获所有查询/

其次,您需要非常小心,不要将NOT IN与列表中的可为空值一起使用: http : //www.sqlbadpractices.com/using-not-in-operator-with-null-values/

我为表添加了DDL:-

IF OBJECT_ID('Attributes') IS NOT NULL
    DROP TABLE Attributes;

IF OBJECT_ID('Cars') IS NOT NULL
    DROP TABLE Cars;

IF OBJECT_ID('SP_GET_TestSearch') IS NOT NULL
    DROP PROCEDURE SP_GET_TestSearch

CREATE TABLE Cars
(idProduct INT PRIMARY KEY
, Description VARCHAR(20) NOT NULL);

CREATE TABLE Attributes
(idRow INT PRIMARY KEY
, idProduct INT NOT NULL FOREIGN KEY REFERENCES dbo.Cars(idProduct)
, idOption INT NOT NULL
, idList INT NOT NULL);

INSERT INTO dbo.Cars
VALUES
(1, 'Product A')
,(2 , 'Product B')
,(3, 'Product C');

INSERT INTO dbo.Attributes
(
    idRow,
    idProduct,
    idOption,
    idList
)
VALUES (1,1,10,45)
,(2,2,10,46)
,(3,3,10,47)
,(4,1,11,10)
,(5,2,11,98)
,(6,1,14,56)
,(7,3,16,28)
,(8,2,20,55);
GO

查询的问题是,对于您指定的任何idOption,块的第一部分始终被评估为TRUE:

((@Param1 IS NULL OR (idOption NOT IN (@Param1)))
AND
(@Param2 IS NULL OR (idOption NOT IN (@Param2)))
AND
(@Param3 IS NULL OR (idOption NOT IN (@Param3))))

解释; 如果我通过以下内容:

DECLARE @Param1 BIGINT
, @Param2 BIGINT
, @Param3 BIGINT
, @PValue1 BIGINT
, @PValue2 BIGINT
, @PValue3 BIGINT;

SET @Param1 = 11
SET @Pvalue1 = 42
SET @Param2 = 11
SET @Pvalue2 = 10
SET @Param3 = 14
SET @PValue3= 56

EXEC dbo.SP_GET_TestSearch @Param1, @PValue1, @Param2, @PValue2, @Param3, @PValue3

然后,您实际上将WHERE idOption NOT IN (11,14)作为该子句第一部分的评估,因此将返回所有其他行。

我怀疑您真的希望WHERE子句是:

WHERE 
       (@Param1 IS NULL AND @Param2 IS NULL AND @Param3 IS NULL)
       OR
       (idOption = @Param1 
         AND idList = @PValue1)
        OR
        (idOption = @Param2 
         AND idList = @PValue2)
        OR
        (idOption = @Param3 
         AND idList = @PValue3)

以下代码演示了如果车辆具有任何 “不良”属性值,则如何实现将车辆从查询结果中排除的逻辑。 拒绝由... where not exists ... )处理... where not exists ...用于检查每辆车的“不良”属性值。

而不是使用各种(希望的)配对参数来传递不希望的属性,而是在表中传递值。 用于实现此目的的存储过程应使用表值参数 (TVP)来传递表。

-- Sample data.
declare @Cars as Table ( CarId Int Identity, Description VarChar(16) );
insert into @Cars ( Description ) values
  ( 'Esplanade' ), ( 'Tankigator' ), ( 'Land Yacht' );
select * from @Cars;

declare @Properties as Table ( PropertyId Int Identity, Description VarChar(16) );
insert into @Properties ( Description ) values
  ( 'Turbochargers' ), ( 'Superchargers' ), ( 'Hyperchargers' ), ( 'Color' ), ( 'Spare Tires' );
select * from @Properties;

declare @CarProperties as Table ( CarId Int, PropertyId Int, PropertyValue Int );
insert into @CarProperties ( CarId, PropertyId, PropertyValue ) values
  ( 1, 1, 1 ), ( 1, 4, 24 ), ( 1, 4, 42 ), -- Two tone!
  ( 2, 2, 1 ), ( 2, 4, 7 ),
  ( 3, 1, 2 ), ( 3, 4, 0 ), ( 3, 5, 6 );
select C.CarId, C.Description as CarDescription,
  P.PropertyId, P.Description as PropertyDescription,
  CP.PropertyValue
  from @Cars as C inner join
    @CarProperties as CP on CP.CarId = C.CarId inner join
    @Properties as P on P.PropertyId = CP.PropertyId
  order by C.CarId, P.PropertyId;

-- Test data: Avoid vehicles that have _any_ of these property values.
--   This should be passed to the stored procedure as a table-value parameter (TVP).
declare @BadProperties as Table ( PropertyId Int, PropertyValue Int );
insert into @BadProperties ( PropertyId, PropertyValue ) values
  ( 2, 1 ), ( 2, 2 ), ( 2, 4 ),
  ( 4, 62 ), ( 4, 666 );
select BP.PropertyId, BP.PropertyValue, P.Description
  from @BadProperties as BP inner join
    @Properties as P on P.PropertyId = BP.PropertyId;

-- Query the data.
select C.CarId, C.Description as CarDescription
  from @Cars as C
  where not exists ( 
    select 42
      from @CarProperties as CP inner join
        @BadProperties as BP on BP.PropertyId = CP.PropertyId and BP.PropertyValue = CP.PropertyValue
      where CP.CarId = C.CarId )
  order by C.CarId;

暂无
暂无

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

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM