Company XYZ WORKS has four vendors. Each vendor is paid based on goods supplied by them to XYZ WORKS. Based on the payment history, the company wants to know the least non-zero amount that was paid to each vendor. If a vendor wasn't ever paid any amount, then that vendor should also fall in ths list. It is this extra requirement that adds a little complication to the task. Lets solve the problem now.
First, we would create a table called dbo.Payment_Details as following:
if exists (select 1 from sys.tables
where Schema_NAME(schema_ID) = 'dbo'
and name = 'Payment_Details')
begin
drop table dbo.Payment_Details
end;
create table dbo.Payment_Details
(Name varchar(10)
,Amount int
,Payment_Date smalldatetime)
Next, lets populate this table with relevant data:
insert into dbo.Payment_Details
values ('Veronica', '0', '20110201')
,('Veronica', '10', '20110301')
,('Veronica', '20', '20110401')
,('Veronica', '30', '20110501')
,('Tiffany', '40', '20110201')
,('Tiffany', '50', '20110301')
,('Tiffany', '60', '20110401')
,('John', '0', '20110201')
,('John', '70', '20110301')
,('John', '80', '20110401')
,('Anthony', '0', '20110201')
Lets see the contents of the table, dbo.Payment_Details:
select * from Payment_Details;
What we expect in the final solution is following:
Name Amount
------------ ------------
Anthony 0
John 70
Tiffany 40
Veronica 10
Lets code some solutions:
Variation 1:
In this solution, we are using CTE but not recursively. In other words, a CTE does not reference itself. In this solution, the table, dbo.Payment_Details is being referenced twice, once per CTE.
with CTE_1 (Name, Amount)
as (select Name, min(Amount)
from dbo.Payment_Details with (nolock)
where Amount > 0
group by Name)
,CTE_2 (Name, Amount)
as (select b.Name, b.Amount from dbo.Payment_Details b with (nolock)
where b.Name not in (select Name from CTE_1)
and b.Amount = 0
union all
select Name, Amount from CTE_1)
select * from CTE_2;
Variation 2:
In this solution, we are again using CTE but not recursively. In this solution, the table, dbo.Payment_Details is being referenced twice, both times in the first CTE.
with CTE_1 (Name, Amount, RowNumber)
as (select Name, Amount, row_number() over(Partition by Name order by Name, Amount) RowNumber
from dbo.Payment_Details with (nolock)
where Amount = 0
union
select Name, Amount, row_number() over(Partition by Name order by Name, Amount desc) RowNumber
from dbo.Payment_Details with (nolock)
where Amount > 0)
,CTE_2 (Name, Amount, RowNumber)
as (select Name, Amount, row_number() over(partition by Name order by Name, RowNumber desc) RowNumber
from CTE_1)
select Name, Amount from CTE_2
where RowNumber = 1;
Variation 3:
In this solution, we are again using CTE but recursively. In other words, a CTE is referencing itself within itself. In this solution, the table, dbo.Payment_Details is being referenced only once.
with CTE_1 (Name, Amount, RowNumber)
as (select distinct Name, Amount, row_number() over(Partition by Name order by Name, Amount) RowNumber
from (select Name, Amount from dbo.Payment_Details with (nolock)) a
union ALL
select Name, Amount, row_number() over(Partition by Name order by Name, RowNumber desc) RowNumber
from CTE_1
where RowNumber = 2)
,CTE_2 (Name, Min_Amt, Max_Amt, RowNumber)
as (select distinct Name, min(Amount) over(partition by Name, RowNumber) Min_Amt, MAX(Amount) over(partition by Name, RowNumber) Max_Amt, RowNumber
from CTE_1
where RowNumber = 1)
SELECT Name, case
when Min_Amt = 0 then Max_Amt
else Min_Amt
end Amount
FROM CTE_2;
In this article, we have shown that Recursive CTE could be used effectively to reduce the number of Scans of the referenced table.
First, we would create a table called dbo.Payment_Details as following:
if exists (select 1 from sys.tables
where Schema_NAME(schema_ID) = 'dbo'
and name = 'Payment_Details')
begin
drop table dbo.Payment_Details
end;
create table dbo.Payment_Details
(Name varchar(10)
,Amount int
,Payment_Date smalldatetime)
Next, lets populate this table with relevant data:
insert into dbo.Payment_Details
values ('Veronica', '0', '20110201')
,('Veronica', '10', '20110301')
,('Veronica', '20', '20110401')
,('Veronica', '30', '20110501')
,('Tiffany', '40', '20110201')
,('Tiffany', '50', '20110301')
,('Tiffany', '60', '20110401')
,('John', '0', '20110201')
,('John', '70', '20110301')
,('John', '80', '20110401')
,('Anthony', '0', '20110201')
Lets see the contents of the table, dbo.Payment_Details:
select * from Payment_Details;
What we expect in the final solution is following:
Name Amount
------------ ------------
Anthony 0
John 70
Tiffany 40
Veronica 10
Lets code some solutions:
Variation 1:
In this solution, we are using CTE but not recursively. In other words, a CTE does not reference itself. In this solution, the table, dbo.Payment_Details is being referenced twice, once per CTE.
with CTE_1 (Name, Amount)
as (select Name, min(Amount)
from dbo.Payment_Details with (nolock)
where Amount > 0
group by Name)
,CTE_2 (Name, Amount)
as (select b.Name, b.Amount from dbo.Payment_Details b with (nolock)
where b.Name not in (select Name from CTE_1)
and b.Amount = 0
union all
select Name, Amount from CTE_1)
select * from CTE_2;
Variation 2:
In this solution, we are again using CTE but not recursively. In this solution, the table, dbo.Payment_Details is being referenced twice, both times in the first CTE.
with CTE_1 (Name, Amount, RowNumber)
as (select Name, Amount, row_number() over(Partition by Name order by Name, Amount) RowNumber
from dbo.Payment_Details with (nolock)
where Amount = 0
union
select Name, Amount, row_number() over(Partition by Name order by Name, Amount desc) RowNumber
from dbo.Payment_Details with (nolock)
where Amount > 0)
,CTE_2 (Name, Amount, RowNumber)
as (select Name, Amount, row_number() over(partition by Name order by Name, RowNumber desc) RowNumber
from CTE_1)
select Name, Amount from CTE_2
where RowNumber = 1;
Variation 3:
In this solution, we are again using CTE but recursively. In other words, a CTE is referencing itself within itself. In this solution, the table, dbo.Payment_Details is being referenced only once.
with CTE_1 (Name, Amount, RowNumber)
as (select distinct Name, Amount, row_number() over(Partition by Name order by Name, Amount) RowNumber
from (select Name, Amount from dbo.Payment_Details with (nolock)) a
union ALL
select Name, Amount, row_number() over(Partition by Name order by Name, RowNumber desc) RowNumber
from CTE_1
where RowNumber = 2)
,CTE_2 (Name, Min_Amt, Max_Amt, RowNumber)
as (select distinct Name, min(Amount) over(partition by Name, RowNumber) Min_Amt, MAX(Amount) over(partition by Name, RowNumber) Max_Amt, RowNumber
from CTE_1
where RowNumber = 1)
SELECT Name, case
when Min_Amt = 0 then Max_Amt
else Min_Amt
end Amount
FROM CTE_2;
In this article, we have shown that Recursive CTE could be used effectively to reduce the number of Scans of the referenced table.