简体   繁体   English

仅在计数大于1时进行联接

[英]Doing a join only if count is greater than one

I wonder if the following a bit contrived example is possible without using intermediary variables and a conditional clause. 我不知道下面的一些人为设计的示例是否可以在不使用中间变量和条件子句的情况下实现。

Consider an intermediary query which can produce a result set that contain either no rows, one row or multiple rows. 考虑一个中间查询,该查询可以生成不包含任何行,一行或多行的结果集。 Most of the time it produces just one row, but when multiple rows, one should join the resulting rows to another table to prune it down to either one or no rows. 在大多数情况下,它只产生一行,但是当产生多行时,应该将结果行连接到另一张表,以将其删减为一行或不行。 After this if there is one row (as opposed to no rows), one would want to return multiple columns as produced by the original intermediary query. 在此之后,如果只有一行(而不是没有行),则需要返回原始中间查询产生的多列。

I have in my mind something like following, but it won't obviously work (multiple columns in switch-case, no join etc.), but maybe it illustrates the point. 我在脑海中有以下类似的想法,但是它显然不起作用(切换情况下有多个列,没有联接等),但是也许可以说明这一点。 What I would like to have is to just return what is currently in the SELECT clause in case @@ROWCOUNT = 1 or in case it is greater, do a INNER JOIN to Auxilliary , which prunes down x to either one row or no rows and then return that. 我想有是只返回当前什么是在SELECT子句中的情况下, @@ROWCOUNT = 1或如果它是更大的,做一个INNER JOINAuxilliary ,其修剪下来x到一行或无行然后返回。 I don't want to search Main more than once and Auxilliary only when x here contains more than one row. 我不想仅在x包含多个行的情况下搜索MainAuxilliary

SELECT x.MainId, x.Data1, x.Data2, x.Data3,
CASE         
     WHEN @@ROWCOUNT IS NOT NULL AND @@ROWCOUNT = 1 THEN
        1
     WHEN @@ROWCOUNT IS NOT NULL AND @@ROWCOUNT > 1 THEN
        -- Use here @id or MainId to join to Auxilliary and there 
        -- FilteringCondition = @filteringCondition to prune x to either 
        -- one or zero rows.
END         
FROM
(
    SELECT
        MainId,
        Data1,
        Data2,
        Data3
    FROM Main
    WHERE
        MainId = @id
) AS x;

CREATE TABLE Main
(
    -- This Id may introduce more than row, so it is joined to
    -- Auxilliary for further pruning with the given conditions.
    MainId INT,
    Data1 NVARCHAR(MAX) NOT NULL,
    Data2 NVARCHAR(MAX) NOT NULL,
    Data3 NVARCHAR(MAX) NOT NULL,

    AuxilliaryId INT NOT NULL
);


CREATE TABLE Auxilliary
(
    AuxilliaryId INT IDENTITY(1, 1) PRIMARY KEY,
    FilteringCondition NVARCHAR(1000) NOT NULL
);

Would this be possible in one query without a temporary table variable and a conditional? 如果没有一个临时表变量和一个条件语句,是否可以在一个查询中实现? Without using a CTE? 不使用CTE?

Some sample data would be 一些样本数据将是

INSERT INTO Auxilliary(FilteringCondition)
VALUES
    (N'SomeFilteringCondition1'),
    (N'SomeFilteringCondition2'),
    (N'SomeFilteringCondition3');


INSERT INTO Main(MainId, Data1, Data2, Data3, AuxilliaryId)
VALUES
    (1, N'SomeMainData11', N'SomeMainData12', N'SomeMainData13', 1),
    (1, N'SomeMainData21', N'SomeMainData22', N'SomeMainData23', 2),
    (2, N'SomeMainData31', N'SomeMainData32', N'SomeMainData33', 3);

And a sample query, which actually behaves as I'd like it to behave with the caveat I'd want to do the join only if querying Main directly with the given ID produces more than one result. 还有一个示例查询,它的行为实际上与我希望的行为类似,仅当使用给定ID直接查询Main产生多个结果时,我才希望执行连接。

DECLARE @id AS INT = 1;
DECLARE @filteringCondition AS NVARCHAR(1000) = N'SomeFilteringCondition1';

SELECT *
FROM
    Main
    INNER JOIN Auxilliary AS aux ON aux.AuxilliaryId = Main.AuxilliaryId
WHERE MainId = @id AND aux.FilteringCondition = @filteringCondition;

You don't usually use a join to reduce the result set of the left table. 您通常不使用联接来减少左表的结果集。 To limit a result set you'd use the where clause instead. 为了限制结果集,您可以使用where子句 In combination with another table this would be WHERE [NOT] EXISTS . 结合另一个表,这将是WHERE [NOT] EXISTS

So let's say this is your main query: 因此,假设这是您的主要查询:

select * from main where main.col1 = 1;

It returns one of the following results: 它返回以下结果之一:

  • no rows, then we are done 没有行,那么我们就完成了
  • one row, then we are also done 一行,那么我们也完成了
  • more than one row, then we must extend the where clause 不止一行,那么我们必须扩展where子句

The query with the extended where clause: 带有扩展where子句的查询:

select * from main where main.col1 = 1
and exists (select * from other where other.col2 = main.col3);

which returns one of the following results: 返回以下结果之一:

  • no rows, which is okay 没有行,没关系
  • one row, which is okay 一排,还可以
  • more than one row - you say this is not possible 多行 -您说这不可能

So the task is to do this in one step instead. 因此,任务是一步执行此操作。 I count records and look for a match in the other table for every record. 我对记录进行计数,并在每张记录的另一个表中查找匹配项。 Then ... 然后 ...

  • if the count is zero we get no result anyway 如果计数为零,我们还是没有结果
  • if it is one I take that row 如果是那一个,我就去那排
  • if it is greater than one, I take the row for which exists a match in the other table or none when there is no match 如果大于1,则我认为另一表中存在匹配项的行,或者当不存在匹配项时不进行匹配的行

Here is the full query: 这是完整的查询:

select *
from
(
  select
    main.*,
    count(*) over () as cnt,
    case when exists (select * from other where other.col2 = main.col3) then 1 else 0 end
     as other_exists
  from main 
  where main.col1 = 1
) counted_and_checked
where cnt = 1 or other_exists = 1;

UPDATE: I understand that you want to avoid unnecessary access to the other table. 更新:我知道您要避免不必要地访问另一个表。 This is rather difficult to do however. 但是,这很难做到。

In order to only use the subquery when necessary, we could move it outside: 为了仅在必要时使用子查询,我们可以将其移到外部:

select *
from
(
  select
    main.*,
    count(*) over () as cnt
  from main 
  where main.col1 = 1
) counted_and_checked
where cnt = 1 or exists (select * from other where other.col2 = main.col3);

This looks much better in my opinion. 我认为这看起来要好得多。 However there is no precedence among the two expressions left and right of an OR . 但是,在OR左右两个表达式之间没有优先级。 So the DBMS may still execute the subselect on every record before evaluating cnt = 1 . 因此,DBMS 可以在评估cnt = 1之前对每个记录执行子选择。

The only operation that I know of using left to right precedence, ie doesn't look further once a condition on the left hand side is matched is COALESCE . 我知道的使用左到右优先级的唯一操作(即,一旦匹配左侧条件就不再看)是COALESCE So we could do the following: 因此,我们可以执行以下操作:

select *
from
(
  select
    main.*,
    count(*) over () as cnt
  from main 
  where main.col1 = 1
) counted_and_checked
where coalesce( case when cnt = 1 then 1 else null end , 
                (select count(*) from other where other.col2 = main.col3) 
              ) > 0;

This may look a bit strange, but should prevent the subquery from being executed, when cnt is 1. 这可能看起来有些奇怪,但是当cnt为1时,应阻止执行子查询。

You may try something like 您可以尝试类似

select * from  Main m
where mainId=@id 
and @filteringCondition = case when(select count(*) from Main m2 where m2.mainId=@id) >1 
then (select filteringCondition from Auxilliary a where a.AuxilliaryId = m.AuxilliaryId) else @filteringCondition end

but it's hardly very fast query. 但这并不是非常快速的查询。 I'd better use temp table or just if and two queries. 我最好使用临时表或只是if和两个查询。

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

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