tags:

views:

141

answers:

4

This question is best phrased with a simple example.

Why can't I do this?

select (lastname + ', ' + firstname) as fullname
from people
where fullname = 'Bloggs, Joe'

instead I have to do this:

select (lastname + ', ' + firstname) as fullname
from people
where (lastname + ', ' + firstname) = 'Bloggs, Joe'

which smells bad to me.

The more complex the query, the worse this problem becomes.

Follow-up

Here is a better example based on the real-world problem from which the question originated.

SELECT ClientID, 
       Name, 
       ContractStartDate, 
       ContractDetails.ContractLength, 
       DATEADD(MONTH, ContractDetails.ContractLength, ContractStartDate) 
           as ContractEndDate
FROM Clients
LEFT OUTER JOIN ContractDetails 
    ON Clients.ClientID = ContractDetails.ClientID
WHERE DATEADD(MONTH, ContractDetails.ContractLength, ContractStartDate) 
      > '2009-06-30'

I have rewritten the query to use an embedded view as suggested. However it still contains repetition - but this time of a join.

SELECT ClientID, 
       Name, 
       contractStartDate, 
       ContractDetails.ContractLength, 
       contractEndDate
FROM (
      SELECT ClientID, 
             Name, 
             ContractStartDate, 
             DATEADD(MONTH, ContractDetails.ContractLength, contractStartdate) 
               AS contractEndDate
      FROM Clients
      LEFT OUTER JOIN ContractDetails 
        on Clients.ClientID = ContractDetails.ClientID
      ) myview
LEFT OUTER JOIN ContractDetails 
  on myview.ClientID = ContractDetails.ClientID
WHERE myview.ContractEndDate > '2009-06-30'
ORDER BY ClientID

The point of this query is to find all live clients as at a particular point in time, where no historical state data is kept (i.e. calculating the contract end date from a known contract start date and length).

Can anyone come up with a way of removing this repetition?

final follow-up

Robin Day helped me out with the key thing I was missing here that actually allowed me to remove the duplication. However KM has a point where he is saying that the WHERE should be on the nested view, rather than the end result, which would require part of the statement to be duplicated (which is what I am trying to avoid). In this particular case I can get away with it because I know there's not millions of records in the ContractDetails table, and never will be.

SELECT ClientID, 
   Name, 
   ContractStartDate, 
   myview.ContractLength, 
   ContractEndDate
FROM (
  SELECT ClientID, 
         Name, 
         ContractStartDate, 
         DATEADD(MONTH, ContractDetails.ContractLength, ContractStartdate) 
           AS ContractEndDate,
         ContractDetails.ContractLength as Length
  FROM Clients
  LEFT OUTER JOIN ContractDetails
    on Clients.ClientID = ContractDetails.ClientID
  ) myview
WHERE myview.ContractEndDate > '2009-06-30'
ORDER BY ClientID
+1  A: 

try:

select
    (lastname + ', ' + firstname) as fullname
    from people
    where lastname = 'Bloggs' AND firstname='Joe'

don't filter based on the formatted output "fullname", filter based on the columns, which should be in an index.

EDIT
based on your revised question:

put the condition in the derived table to limit it (and keep it as small as possible). I have seen queries made much faster by doing this alone. I'm sure the query engine is smart enough to not do the DATEADD() twice, so don't worry about it.

SELECT ClientID, 
       Name, 
       contractStartDate, 
       ContractDetails.ContractLength, 
       contractEndDate
FROM (
      SELECT ClientID, 
             Name, 
             ContractStartDate, 
             DATEADD(MONTH, ContractDetails.ContractLength, contractStartdate) 
               AS contractEndDate
      FROM Clients
      LEFT OUTER JOIN ContractDetails 
        on Clients.ClientID = ContractDetails.ClientID
      WHERE DATEADD(MONTH, ContractDetails.ContractLength, contractStartdate) > '2009-06-30'
      ) myview
LEFT OUTER JOIN ContractDetails 
  on myview.ClientID = ContractDetails.ClientID
ORDER BY ClientID
KM
-1, I think you missed the point - I gave a simplistic example. Perhaps too simple. The aliased bit of the select statement might contain a great big calculation, subquery, whatever. Of course you can filter using the raw fields - that wasn't the point.
tomfanning
@tomfanning, edit your question with a better example. I answer what you ask, not what you are thinking (I can't read your mind).
KM
@KM, the question was "why can't I do this specific thing", not "solve this query problem". Nevertheless I have added a domain specific problem and a reworking using an embedded view based on Robin Day's answer.
tomfanning
@KM thanks for updating your answer. However it still hasn't removed the duplication.
tomfanning
@tomfanning, there is no way to remove the duplication, write your query for speed and live with the trivial duplication
KM
@tomfanning said _the question was "why can't I do this specific thing"_, No, there was no quetion? just a few statements and some code examples.
KM
@KM not sure what I've done to p*** you off but chill out man.
tomfanning
@tomfanning, you wrote a lame question, I answered it, you down voted me, I defended my answer, you revised your question so it became a good question, I edited my answer based on that, end of story, I can handle loosing 2 points, the sun will rise tomorrow
KM
+3  A: 

You can used a derived table / nested view...

select
    fullname
from
(
    select
        (lastname + ', ' + firstname) as fullname
    from
        people
) myview
where
    myview.fullname = 'Bloggs, Joe'

EDIT: Just to clarify, this is to show the concept you are asking about. In this specific example your WHERE clause should check firstname = 'Joe' and lastname = 'Bloggs' as KM has answered rather than checking fullname.

Robin Day
I like your EDIT message, basically repeating my answer. However, my answer has already received two down votes so far...
KM
@Robin Day : You didn't really answer the question, you just provided a work around to get him the abstraction he wanted.
CptSkippy
@CptSkippy, there is no way
KM
@CptSkippy which is why this hasn't been marked as the answer and yours has. This is great, because it teaches me a way to accomplish what I need, but you've answered the question I asked.
tomfanning
@tomfanning, don't ever do it this way!!!! if people has a million rows, do you really want to format all those names to just then juse select out a single one? that is crazy and slow. **ALWAYS ALWAYS ALWAYS filter as much as possible in derived tables**, just like I say in my answer...
KM
@km, thanks for the heads-up, that of course makes perfect sense.
tomfanning
+1  A: 

The select list is a transformation of a virtual table returned by the from, where and order clauses. The clauses are not aware of the select list. Additionally any transformations of columns defined in the where clause are not sargable and force SQL to perform table or index scans. In other words, to do so would absolutely kill performance.

CptSkippy
A concise answer to the original question, indicating to me that it simply has to be worked around elsewhere in the query. Thanks very much.
tomfanning
+1  A: 

Isn't your edited example overly complex? What's wrong with:

SELECT *
FROM (
      SELECT ClientID, 
             Name, 
             ContractStartDate, 
             ContractLength,
             DATEADD(MONTH, ContractDetails.ContractLength, contractStartdate) 
               AS contractEndDate
      FROM Clients
      LEFT OUTER JOIN ContractDetails 
        on Clients.ClientID = ContractDetails.ClientID
      ) myview
WHERE myview.ContractEndDate > '2009-06-30'
ORDER BY ClientID
Matt Nizol
If I'm understanding his logic correctly, that strategy has already been, um, recommended against by KM because DATEADD() is called for everything in the Clients table. That's what's wrong with it.
tomfanning
Your "final follow-up" Is exactly the same as my suggestion above, except for the asterisk in my SELECT statement which simply removes some verbosity.
Matt Nizol