SQL Server: строка присутствует в одном запросе, отсутствует в другом

Хорошо, поэтому я думаю, что я должен не понимать что-то о SQL-запросах. Это довольно многословный вопрос, поэтому спасибо, что нашли время, чтобы прочитать его (моя проблема в конце концов, все остальное - это просто контекст).

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

Некоторые строки TransactionParts могут быть помечены как связанные с налогами, чтобы система могла составлять отчет об общем объеме продаж/покупок НДС и т.д., поэтому возможно, что одна транзакция может иметь два TransactionParts, ссылающихся на одну и ту же учетную запись - один НДС, а другой нет. Чтобы упростить представление пользователю, у меня есть представление о объединении нескольких строк для одной и той же учетной записи и транзакции:

create view Accounting.CondensedEntryView as
select p.[Transaction], p.Account, sum(p.Amount) as Amount
    from Accounting.TransactionParts p 
    group by p.[Transaction], p.Account

Затем я имею представление для расчета столбца текущего баланса следующим образом:

create view Accounting.TransactionBalanceView as
with cte as
(
    select ROW_NUMBER() over (order by t.[Date]) AS RowNumber, 
                         t.ID as [Transaction], p.Amount, p.Account
        from Accounting.Transactions t
            inner join Accounting.CondensedEntryView p on p.[Transaction]=t.ID
)
select b.RowNumber, b.[Transaction], a.Account, 
                coalesce(sum(a.Amount), 0) as Balance
    from cte a, cte b
    where a.RowNumber <= b.RowNumber AND a.Account=b.Account
    group by b.RowNumber, b.[Transaction], a.Account

По причинам, которые я еще не разработал, определенная транзакция (ID = 30) не отображается в выписке по счету для пользователя. Я подтвердил это, запустив

select * from Accounting.TransactionBalanceView where [Transaction]=30

Это дало мне следующий результат:

RowNumber            Transaction Account Balance
-------------------- ----------- ------- ---------------------
72                   30          23      143.80

Как я уже говорил, для каждой транзакции должно быть не менее двух TransactionParts, поэтому один из них не представляется на мой взгляд. Я предположил, что должна быть проблема с тем, как я написал свое представление, и запустить запрос, чтобы увидеть, нет ли чего-то еще:

select [Transaction], count(*) 
    from Accounting.TransactionBalanceView 
    group by [Transaction] 
    having count(*) < 2

Этот запрос не возвращает никаких результатов - даже для транзакции 30! Думаю, что я должен быть идиотом, я запускаю следующий запрос:

select [Transaction] 
    from Accounting.TransactionBalanceView 
    where [Transaction]=30

Он возвращает две строки! Таким образом, select * возвращает только одну строку, а select [Transaction] возвращает оба. После многократного поглаживания и повторного запуска последних двух запросов я пришел к выводу, что у меня нет ни малейшего представления о том, что происходит. Любые идеи?

Большое спасибо, если вы застряли со мной так далеко!

Edit:

Вот планы выполнения:

выберите *
выберите [Транзакция]

1000 строк каждый, следовательно, найти где-то еще место для хоста.

Изменить 2:

Для полноты, вот таблицы, которые я использовал:

create table Accounting.Accounts
(
ID              smallint        identity primary key,
[Name]          varchar(50)     not null
    constraint UQ_AccountName unique,
[Type]          tinyint         not null
    constraint FK_AccountType foreign key references Accounting.AccountTypes
);

create table Accounting.Transactions
(
ID              int             identity primary key,
[Date]          date            not null default getdate(),
[Description]   varchar(50)     not null,
Reference       varchar(20)     not null default '',
Memo            varchar(1000)   not null
);

create table Accounting.TransactionParts
(
ID              int             identity primary key,
[Transaction]   int             not null
    constraint FK_TransactionPart foreign key references Accounting.Transactions,
Account         smallint        not null
    constraint FK_TransactionAccount foreign key references Accounting.Accounts,
Amount          money           not null,
VatRelated      bit             not null default 0
);

Ответ 1

Демонстрация возможного объяснения.

Создать таблицу Script

SELECT *
INTO #T
 FROM master.dbo.spt_values

 CREATE NONCLUSTERED INDEX [IX_T] ON  #T ([name] DESC,[number] DESC);

Запрос один (возвращает 35 результатов)

WITH cte AS
(
SELECT *, ROW_NUMBER() OVER (ORDER BY NAME) AS rn
 FROM  #T
)
SELECT c1.number,c1.[type] 
FROM cte c1
JOIN cte c2 ON c1.rn=c2.rn AND c1.number <> c2.number

Query Two (То же, что и раньше, но добавление c2. [type] в список выбора позволяет вернуть 0 результатов) ;

WITH cte AS
(
SELECT *, ROW_NUMBER() OVER (ORDER BY NAME) AS rn
 FROM  #T
)
SELECT c1.number,c1.[type] ,c2.[type] 
FROM cte c1
JOIN cte c2 ON c1.rn=c2.rn AND c1.number <> c2.number

Почему?

row_number() для повторяющихся NAME не указывается, поэтому он просто выбирает, какой из них подходит, с лучшим планом выполнения для требуемых выходных столбцов. Во втором запросе это одно и то же для обоих вызовов cte, в первом выбирает другой путь доступа с результирующим другим номером row_numbering.

План выполнения

Рекомендуемое решение

Вы присоединяетесь к CTE на ROW_NUMBER() over (order by t.[Date])

В отличие от ожидаемого, CTE, скорее всего, не будет реализовано, что обеспечило бы согласованность для самоподключения и, следовательно, вы предполагали корреляцию между ROW_NUMBER() с обеих сторон, которые, возможно, не существуют для записей, где в данных существует дубликат [Date].

Что делать, если вы попробуете ROW_NUMBER() over (order by t.[Date], t.[id]), чтобы гарантировать, что в случае привязанных дат row_numbering находится в гарантированном согласованном порядке. (Или другой столбец/комбинация столбцов, которые могут отличать записи, если id не будет делать этого)

Ответ 2

Если цель этой части представления состоит в том, чтобы убедиться, что одна и та же строка не присоединена к себе

where a.RowNumber <= b.RowNumber

то как изменить эту часть на

where a.RowNumber <> b.RowNumber

влияют на результаты?

Ответ 3

Кажется, вы читаете грязные записи. (Кто-то еще удаляет/вставляет новые данные)

попробуйте выполнить УСТАНОВИТЬ УРОВЕНЬ УРОВНЯ ИЗОЛЯЦИИ ОПЕРАЦИИ.

Я пробовал этот код (кажется, равный вашему)

IF object_id('tempdb..#t') IS NOT NULL DROP TABLE #t
CREATE TABLE #t(i INT, val INT, acc int)

INSERT #t 
SELECT 1, 2, 70 
UNION ALL SELECT 2, 3, 70

;with cte as
(
    select ROW_NUMBER() over (order by t.i) AS RowNumber, 
                         t.val as [Transaction], t.acc Account
        from #t t
)
select b.RowNumber, b.[Transaction], a.Account
    from cte a, cte b
    where a.RowNumber <= b.RowNumber AND a.Account=b.Account
    group by b.RowNumber, b.[Transaction], a.Account

и получил две строки Счет транзакции RowNumber 1 2 70 2 3 70