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