July 26, 2005 at 2:18 am
I am having a problem when analysing a sum with data from 2 x tables (ITEMS,CUST):
I want to do the following (this is in english rather than SQL)
I want to return the value "ON STOP" when EITHER of the following conditions are met:
1. When the SUM(ITEMS.AMOUNT) grouped by ITEMS.CUSTOMER > CUST.CREDIT_LIMIT then "ON STOP"
or
2. When the SUM(ITEMS.AMOUNT) by ITEMS.CUSTOMER where ITEMS.DUE_DATE < GETDATE() -14 > 0 then "ON STOP"
The CUST table has single entries for the Customer (ie Customer main detail table)
The ITEMS table has multiple transaction records for customers.
mmmm...... Any ideas
Thanks in advance - Mark
July 26, 2005 at 2:47 am
You need to join the two tables. If you post the table definitions, I am sure we can help you write a join
I am a little puzzled by condition 2. Where does the sum come in? Do you want items or customers returned? And you probably don't mean "GETDATE() -14 > 0", do you?
July 26, 2005 at 3:06 am
Hi Jesper,
Thanks for the reply:
Sorry - to clear up what i mean in condition 2:
When the (SUM(ITEMS.AMOUNT) by ITEMS.CUSTOMER where ITEMS.DUE_DATE < GETDATE() -14) > 0 then "ON STOP"
in other words, sum the amount where the due_date is less than 2 weeks old, and when that sum is >0 then "ON STOP"
TABLES as follows:
CUST TABLE
CUSTOMER CREDIT_LIMIT
CUST01 1000
CUST02 800
CUST03 1500
ITEMS TABLE
CUSTOMER DUE_DATE AMOUNT
CUST01 12/07/2005 60.00
CUST01 20/07/2005 100
CUST02 01/07/2005 85.50
CUST03 01/08/2005 45
CUST03 30/06/2005 150.00
CUST03 14/06/2005 34.90
ETC....
Cheers,
Mark
July 26, 2005 at 3:09 am
oops... and to answer your other point Jesper,
I would like CUSTOMER,"ON STOP" returned - maybe also, where conditions are NOT met - "OK" instead of "ON STOP" ???
Thanks again
July 26, 2005 at 3:31 am
Maybe something like this?
create table CUST
(CUSTOMER varchar(10), CREDIT_LIMIT int)
go
create table ITEMS
(CUSTOMER varchar(10), DUE_DATE datetime, AMOUNT decimal(6,2))
go
insert into CUST
select
'CUST01', 1000
union all select
'CUST02', 800
union all select
'CUST03', 1500
insert into ITEMS
select
'CUST01', '2005-07-12', 60.00
union all select
'CUST01', '2005-07-20', 100
union all select
'CUST02', '2005-07-01', 85.50
union all select
'CUST03', '2005-08-01', 45
union all select
'CUST03', '2005-06-30', 150.00
union all select
'CUST03', '2005-06-14', 34.90
select cust.customer, case when total1.total > cust.credit_limit or total2.total > 0 then 'ON STOP' else 'OK' end
from cust inner join
(
select cust.customer as customer, sum(items.amount) as total
from cust left join items on cust.customer = items.customer
group by cust.customer
)
total1
on cust.customer = total1.customer
inner join
(
select cust.customer as customer, sum(items.amount) as total
from cust left join items on cust.customer = items.customer and
datediff(d, items.due_date, getdate()) < 14
group by cust.customer
)
total2
on cust.customer = total2.customer
July 26, 2005 at 3:45 am
PERFECT!
Thanks so much for that Jesper
July 27, 2005 at 12:02 pm
A couple of suggestions:
1. Don't query the same table twice if you can get both results in one pass
2. Group the "many" side of a one-to-many relation before joining
3. Compare the date column to a DATEADD(,,GETDATE()) expression (which will be evaluated once) rather than evaluating DATEDIFF(,fld,GETDATE()) for every row
It also seems that you need to filter out only unpaid bills, unless records are removed from the items table when the bill is paid.
select c.customer, ISNULL(i.AccountBalance, 0) as AccountBalance,
case when i.AccountBalance > c.credit_limit or i.PastDue > 1 then 'ON STOP' else '' end as Status
from customer c
left join (
select customer, sum(items.amount) as AccountBalance,
sum(case when due_date < dateadd(d, -14, getdate()) then 1 else 0 end) as PastDue
from items
{ where Unpaid = 1 ? }
group by customer
) i on c.customer = i.customer
July 28, 2005 at 12:57 am
You certainly have some points there and your query is definitely simpler
I am not sure your query meets condition 2 of the original query if amounts can be negative. Maybe they can't, but I guess there is a reason to require that the sum is > 0 and not just one of the amounts (in any case, i.PastDue should probably be > 0 in your query). Therefore I have rewritten your query as follows:
select c.customer, ISNULL(i.AccountBalance, 0) as AccountBalance,
case when i.AccountBalance > c.credit_limit or i.PastDue > 0 then 'ON STOP' else 'OK' end as Status
from cust c
left join (
select customer, sum(items.amount) as AccountBalance,
sum(case when due_date < dateadd(d, -14, getdate()) then items.amount else 0 end) as PastDue
from items
group by customer
) i on c.customer = i.customer
Thanks for your post, Scott, I feel that I have learned something (but I guess that the point in participating here ).
July 28, 2005 at 2:02 am
Guys,
Thanks for the further comments - i'm actually gonna stick with the original (Jesper) query as I understand that - the further posts make my brain go mushy as i'm from Devon 🙂
Again though, thanks for all the input.
Mark
July 28, 2005 at 6:09 am
Mark: If someone suggests a different query might be better, or you can think of two or more versions yourself but can't decide which to use, put both versions in Query Analyzer and get the execution plan (press control-L). It will tell you which one it thinks will be better for your data. If they split 50-50 or even 45-55, go with the one you find more readable (understandable, maintainable, etc). If they split 80-20 or 98-2, you will have learned something useful if you can figure out why. (Assuming both versions produce correct results.) I can't overemphasize how quickly my SQL improved after I discovered this technique.
Jesper: You're correct that "> 1" should have been "> 0".
Changing "else '' " to "else 'OK' " is not necessarily an improvement, it depends on user requirements. It the results are going to be printed or displayed directly then the 'ON STOP' will stand out much better if the other rows are blank. But this could be any literal string (or NULL) without affecting performance.
Changing the 1 to items.amount when calculating PastDue might be closer to the stated requirement of past due balance > 0. I was focusing on checking for unprocessed past due items in general, but whether negative amounts might occur is not stated. It does seem wrong to put an account on hold because they are owed a credit.
If credits are possible then a more detailed statement of the business rules is required. Are credits entered as separate items with a negative amount, or is the original item deleted or updated to a zero amount? If the desired test is whether there are outstanding payments due in spite of any credits, it might be " SUM(CASE WHEN due_date < dateadd(...) AND amount > 0 THEN 1 ELSE 0 END) ". There is usually a delay between the order and any credit, so there will be a period where the original item is older than 14 days but the corresponding credit isn't, and none of the queries suggested so far will handle this. Maybe past due payments AND all credits need to be summed separately, and the final test is whether past due payments are greater than all credits to date.
I'm still curious whether the items table only contains unpaid items, or whether a test for payment status is required. Here's another version, and I put the unpaid test back in.
select c.customer, ISNULL(i.AccountBalance, 0) as AccountBalance,
case when i.AccountBalance > c.credit_limit or i.PastDuePayment > TotalCredit then 'ON STOP' else '' end as Status
from customer c
left join (
select customer, sum(items.amount) as AccountBalance,
sum(case when due_date < dateadd(d, -14, getdate()) and amount > 0 then amount else 0 end) as PastDuePayment,
sum(case when amount < 0 then amount else 0 end) as TotalCredit
from items
{ where Unpaid = 1 ? }
group by customer
) i on c.customer = i.customer
July 28, 2005 at 7:24 am
Scott, Mark has stated that he wants 'OK' back. Furthermore, he has stated quite clearly what the condition on past due amounts should be. You may of course start a discussion of his business rules, or suggest simplifications or improvements, but I don't think you should write code that reflects what you think his business rules should be rather than what he has stated they are. After all, he knows that better than anyone here
July 28, 2005 at 7:58 am
Jesper: My orignal comment to Mark was to try to help him evaluate alternative queries that he may not immediatly understand. If the benefit of the alternate version is not obvious, then finding out whether it is a 1% improvement or a 1000% improvement will tell you whether it is worth spending time on. This is a useful learning tool outside of any discussion about this particular query.
I don't think the problem statement included every detail of the business rules, nor do I think it should. If you put a complete scope document at the front of a post, most people won't read all the way down to your actual question. But as far as inventing business rules, you're the one who brought up negative amounts. I think it is correct to point out that if your suggestion is possible, there are a lot of rules missing for trying to deal with them.
July 28, 2005 at 8:31 am
The negative amount only came up because I wanted to say that your query might produce a different output. As I said, this may not be the case, I don't know. But I think Mark has stated clearly what he wants. Of course you don't state every business rule when posing a question here, but that just means that you will carry out any modifications yourself. My impression is that he has the output he wants, and I think we should concentrate on finding the best query that produces this output.
Viewing 13 posts - 1 through 12 (of 12 total)
You must be logged in to reply to this topic. Login to reply