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.
4 comments:
Very nice post. I just stumbled upon your weblog
and wished to say that I've truly enjoyed browsing your blog posts. In any case I will be subscribing to your rss feed and I hope you write again soon!
My website - joint pain
Thanks very interesting blog!
Also visit my website ; cosmetic surgery stretch marks
My partner and I stumbled over here from a different web address and thought I might check things out.
I like what I see so now i'm following you. Look forward to exploring your web page again.
Here is my web site: http://social.pitbull-net.com/blogs/user/FlorenciaH
Also see my web page > maid services
Good day! I could have sworn I've visited this site before but after browsing through some of the posts I realized it's new to me.
Nonetheless, I'm definitely happy I stumbled upon it and I'll be book-marking
it and checking back often!
Take a look at my webpage ... toe nail fungus treatments
Post a Comment