2016-04-28 3 views
1

Интересно, возможен ли следующий примерный пример без использования промежуточных переменных и условного предложения.Выполнение соединения только в том случае, если счетчик больше одного

Рассмотрите промежуточный запрос, который может создать набор результатов, который не содержит ни строк, ни одной строки, ни нескольких строк. Большую часть времени он производит только одну строку, но когда несколько строк, нужно присоединить полученные строки к другой таблице, чтобы сократить ее до одной или без строк. После этого, если есть одна строка (в отличие от строк), нужно было бы возвращать несколько столбцов, созданных исходным промежуточным запросом.

У меня в голове что-то вроде следующего, но он, очевидно, не будет работать (несколько столбцов в случае коммутатора, нет соединения и т. Д.), Но, возможно, это иллюстрирует точку. Я хотел бы просто вернуть то, что в настоящее время находится в пункте SELECT, в случае @@ROWCOUNT = 1, или в случае, если оно больше, сделайте INNER JOIN до Auxilliary, который уменьшает x до одной строки или без строк, а затем возвращает это. Я не хочу искать Main более одного раза и Auxilliary только тогда, когда x здесь содержит несколько строк.

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 
); 

Возможно ли это в одном запросе без временной переменной таблицы и условного? Без использования CTE?

Некоторые образцы данные будут

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); 

И пример запроса, который на самом деле ведет себя, как я хотел бы, чтобы вести себя с той оговоркой, что я хочу сделать только присоединиться, если запрос 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; 
+3

вы могли бы изменить этот вопрос и предоставить образец данные и желаемые результаты , Я уверен, что это можно сделать с помощью одного запроса, но трудно точно понять, что вы пытаетесь сделать. –

+0

Несомненно!Я постараюсь сделать это скоро (достаточно)! – Veksi

+0

@GordonLinoff Спасибо, что спросили! Я действительно заметил недостаток в моем вопросе также в том, что Id in Main не обязательно уникален. Дело в том, что у меня есть ситуация в существующей базе данных, которая, как мне кажется, я знаю, как ее решить, но я тоже пытаюсь воспитывать себя в этом процессе. Дело в том, что выполнение соединения в большинстве случаев не требуется и может быть довольно проблематичным из-за производительности. Тогда также возникает проблема с возвратом нескольких результатов в код (из-за изменений в приложении и больших данных). – Veksi

ответ

3

Вы обычно не используете , присоединяетесь, чтобы уменьшить набор результатов левой таблицы. Чтобы ограничить набор результатов, вы должны использовать , где вместо этого находится пункт. В сочетании с другой таблицей это будет WHERE [NOT] EXISTS.

Так скажем, это ваш главный вопрос:

select * from main where main.col1 = 1; 

возвращает один из следующих результатов:

  • нет строк, то мы сделали
  • один ряд, то мы также сделано
  • более одного ряда, тогда мы должны продлить место, где статья

Запрос с расширенной где предложение:

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

который возвращает один из следующих результатов:

  • нет строк, которые нормально
  • один ряд, который хорошо
  • более одного ряда - вы говорите, что это невозможно

Итак, задача состоит в том, чтобы сделать это за один шаг. Я подсчитываю записи и ищу матч в другой таблице для каждой записи. Тогда ...

  • , если счетчик равен нулю, мы не получим никакого результата в любом случае
  • если это один я беру эту строку
  • , если она больше, чем один, я беру строку, для которой существует совпадение в другой таблице или нет, если нет матча

Вот полный запрос:

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: Я понимаю, что вы хотите избежать ненужного доступа к другой таблице. Однако это довольно сложно сделать.

Для того, чтобы использовать только подзапрос в случае необходимости, мы могли бы переместить его снаружи:

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); 

Это выглядит намного лучше, на мой взгляд. Однако между двумя выражениями слева и справа от OR нет приоритета. Таким образом, СУБД может еще выполнить подзапрос на каждой записи перед оценкой cnt = 1.

Единственная операция, которую я знаю об использовании приоритета слева направо, т. Е. Не выглядит более подробно, как только условие на левой стороне соответствует COALESCE. Таким образом, мы могли бы сделать следующее:

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; 

Это может выглядеть немного странно, но должно препятствовать подзапросу от выполняются, когда УНТЫ 1.

+0

Не всегда ли это запрос «другой»? Я хотел бы избежать объединения, если 'main.col1 = 1' возвращает более одной строки. Тем не менее, мне нравятся ваши заметки о присоединении. – Veksi

+0

Да, да. И это довольно сложно обойти. См. Мое обновление. –

+0

Спасибо! Вы подробно объяснили это. Похоже, что получение набора результатов к переменной таблицы и с условием IF является самым простым способом. – Veksi

1

Вы можете попробовать что-то вроде

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

, но это вряд ли очень быстро запрос. Я бы лучше использовал временную таблицу или просто if и два запроса.

Смежные вопросы