tags:

views:

279

answers:

8

I using this in Access 2003. Pardon me if this is in the wrong spot, but I hoped someone could help.

I have a table with multiple records that have a text represented date field. The date is in a format like: "06/01/2009" I need to select all the fields from the table but only the 6 oldest rows that fall in a range for each group of:

COUPONS.DocType, COUPONS.PayTo, COUPONS.ContactName, COUPONS.ContactNumber,
COUPONS.DocFooter, COUPONS.PQBName, COUPONS.LetterDate, COUPONS.RetireeFirstName,
COUPONS.RetireeLastName, COUPONS.Address1, COUPONS.Address2, COUPONS.City, 
COUPONS.State, COUPONS.ZIP, COUPONS.PQBSSN, COUPONS.EmployerCode
ordered by the COUPONS.DateDue.

Like: select only records with a date range 01/01/2009 - 12/01/2009, and of those only select the 6 oldest entries.

I have monkeyed with this for a bit and am having no luck. I know this is pretty basic, but I just cant seem to make this work. Here is the SQL select I use to get the date from the table now.

SELECT COUPONS.DocType, COUPONS.PayTo, COUPONS.ContactName, COUPONS.ContactNumber,
COUPONS.DocFooter, COUPONS.PQBName, COUPONS.LetterDate, COUPONS.RetireeFirstName, 
COUPONS.RetireeLastName, COUPONS.Address1, COUPONS.Address2, COUPONS.City, 
COUPONS.State, COUPONS.ZIP, COUPONS.PQBSSN, COUPONS.EmployerCode, COUPONS.AmountDue, 
COUPONS.DateDue, Right([DateDue],4)+Left([DateDue],2)+Mid([datedue],4,2) AS SORTDATE
FROM COUPONS
ORDER BY COUPONS.DocType, COUPONS.PayTo, COUPONS.ContactName, COUPONS.ContactNumber, 
COUPONS.DocFooter, COUPONS.PQBName, COUPONS.LetterDate, Right([DateDue],4)+Left
([DateDue],2)+Mid([datedue],4,2);
A: 

Probably you are adding the date values like this

Right([DateDue],4)+Left([DateDue],2)+Mid([datedue],4,2);

(eg : 1996 + 04 + 21 = 2021)

So try this in your order clause

Right([DateDue],4),Left([DateDue],2),Mid([datedue],4,2);

Or try concatening the values rather than adding them :

Right([DateDue],4) & Left([DateDue],2) & Mid([datedue],4,2);

(eg : 1996 + 04 + 21 = 19960421)

After that indeed just use the top clause. I didn't realized that could be a problem, sorry.

Peter
A: 

I'm not an expert of Access, but I think you could use a "SELEC TOP", if your version of Access supports it:

SELECT TOP 6 COUPONS.DocType, COUPONS.PayTo, COUPONS.ContactName, COUPONS.ContactNumber, COUPONS.DocFooter, COUPONS.PQBName, COUPONS.LetterDate, COUPONS.RetireeFirstName, COUPONS.RetireeLastName, COUPONS.Address1, COUPONS.Address2, COUPONS.City, COUPONS.State, COUPONS.ZIP, COUPONS.PQBSSN, COUPONS.EmployerCode, COUPONS.AmountDue, COUPONS.DateDue, Right([DateDue],4)+Left([DateDue],2)+Mid([datedue],4,2) AS SORTDATE FROM COUPONS

ORDER BY Right([DateDue],4)+Left([DateDue],2)+Mid([datedue],4,2), COUPONS.DocType, COUPONS.PayTo, COUPONS.ContactName, COUPONS.ContactNumber, COUPONS.DocFooter, COUPONS.PQBName, COUPONS.LetterDate

Diego
+1  A: 

If you have control over the database but MUST use a text-based date, store your dates using the ODBC canonical format:

yyyy-mm-dd               // if there's no time element
yyyy-mm-dd HH:MM:ss      // if time is needed as well

This has a few distinct advantages:

  • World-friendly, for users who aren't in the US and may think mm-dd-yyyy means dd-mm-yyyy
  • Sorts by date naturally, so normal < and > operators work just fine (and those operations are doing a textual comparison, they never actually convert the text to a date).
  • Your business layer will likely be able to read dates in this format correctly without adjusting your code at all
  • If you have fields that don't have actual dates, this won't generate a CONVERT() error like many of the suggestions already posted. (For instance, if you are also dealing with dirty values like "Next Tuesday" or "N/A" that you can't clean out of the database.)

Converting your existing date data is a simple exercise of UPDATE with RIGHT(), LEFT(), etc., assuming your current date data is in a consistent format.

Once your data is stored in a format that can be queried more readily, it's a simple problem:

SELECT TOP 6 * FROM mytable WHERE mydate BETWEEN startdate AND enddate ORDER BY mydate DESC

As for your grouping problem, I don't understand the question well enough to propose an answer. But getting your date data stored in the most efficient text format will help sort everything else out.

Ok, I'm going to take a stab at your grouping problem:

SELECT DISTINCT DueDate, DocType, PayTo, ContactName, ContactNumber, [...other fields...]
FROM coupons c1
WHERE CDate(c1.DueDate) BETWEEN '01/01/2000' AND '01/01/2009'
  /* Here's where the "grouping" happens--actually just filtering out the others */
  AND (SELECT COUNT(*) FROM coupons c2 WHERE
    CDATE(c1.DueDate) >= CDATE(c2.DueDate)
    AND c2.DocType=c1.DocType
    AND c2.ContactName=c1.ContactName
    AND c2.ContactNumber=c1.ContactNumber
    [...test the other fields...]
    ) <= 6

I can't remember the ins and outs of Jet SQL enough to know if this subquery will work, but I think it will.

richardtallent
A: 

Since your question is hard to read (format dude!!) I'm going to go off your initial question.

how do you select the six oldest dates, if the dates are represented as chars.

You need to cast the datedue as a datetime, then do an order by:

SELECT top 6 CAST(datedue as datetime) as DateDue from test order by DateDue asc
Jack Marchetti
OP wants oldest six rows from EACH GROUP
spencer7593
CAST() is not a Jet SQL function. The "sql" tag does not mean MS SQL when used in conjunction with the MS-ACCESS tag. It just means that the question is about Jet SQL used in Access.
David-W-Fenton
A: 

If I'm reading your problem right, it appears that you have a text field that contains date-like data, and you want to be able to sort on it chronologically (within a range).

After some quick Googling, it appears that Access has a CDate() function that you can use to convert the string data into dates. Secondly, once you've got that problem addressed, the actual query to select a date range will probably look something like this (at least in Sql Server -- the syntax for Access will probably be similar but may differ slightly):

SELECT TOP 6 [...]
FROM [...]
WHERE DateDue BETWEEN @BeginDate AND @EndDate
ORDER BY DateDue ASC

That is the general idea of how you can get the six oldest entries within a date range.

rmz
OP wants oldest six record from EACH GROUP, not within a date range.
spencer7593
Re-reading the somewhat confusing OP, it sounds like both to me. In either case, though...
rmz
It's just a horrid idea to store dates as text. Not only do you lose the usefulness of indexes, but you lose all the data operation functions. It's just a really bad thing to do.
David-W-Fenton
A: 

answer not complete, working on it...

To restate the problem: the query is returning too many rows, you want ONLY six rows for each 'group' of distinct values for the seven first six columns listed in the ORDER BY clause.

You've already got the date issue worked out. Contrary to popular opinion, it is NOT necessary to cast to a DATETIME to get this query to work. The problem is the same whether you're ordering on a DATETIME expression or VARCHAR expression. You just want the "lowest" n values for EACH GROUP.

To get this result set in a single query, I think this is going to require a stopkey predicate with an inline view, does Access support common table expresssions?

for example.

WITH cte AS 
( SELECT ROW_NUMBER() OVER (PARTITION BY ... ORDER BY ...) AS ROWNUM
       , ...
    FROM COUPONS c
)
SELECT ...
  FROM cte
 WHERE cte.ROWNUM <= 6

-or-

SELECT TOP 6 ... 
  FROM ...
 GROUP
    BY ...

answer not complete


sample SQL statement from OP, reformatted to be "human readable":

SELECT c.DocType
     , c.PayTo
     , c.ContactName
     , c.ContactNumber
     , c.DocFooter
     , c.PQBName
     , c.LetterDate
     , c.RetireeFirstName
     , c.RetireeLastName
     , c.Address1
     , c.Address2
     , c.City
     , c.State
     , c.ZIP
     , c.PQBSSN
     , c.EmployerCode
     , c.AmountDue
     , c.DateDue
     , Right(c.[DateDue],4)+Left(c.[DateDue],2)+Mid(c.[DateDue],4,2) AS SORTDATE
  FROM COUPONS c
 ORDER
    BY c.DocType
     , c.PayTo
     , c.ContactName
     , c.ContactNumber
     , c.DocFooter
     , c.PQBName
     , c.LetterDate
     , Right(c.[DateDue],4)+Left(c.[DateDue],2)+Mid(c.[DateDue],4,2)
spencer7593
Spencer7593: Yes this is exactly the issue. I had the reformated date in there just so I could easily sort the thing and see it clearly sorted in a date fashion.
Spencer7593: All comments dead on. The date isnt a problem, and neither is optimization particularly for this application. The amount of data being returned is small. The DateDue is always being populated and validation/bounds checking is done prior to the data getting imported. If it helps, I could add more than one select. If I can't work this out, I can write a rountine to copy the data into a temp table, iterate through the data, and delete entries that fall outside my parameters. I just had a delision that it could be done directly in the SELECT. Thanks ALL for your patients and help!
@Scott: my answer is attracting negative votes. If i get more, I will be a "good citizen" and remove my answer. Maybe I will join the crowd and post an answer to the question you didn't ask "How do I convert a formatted string into a datetime?"
spencer7593
+1  A: 

If you are sure that you have date strings in ALL of your rows, the easiest VB for converting to a datevalue is: CDate([DateDue]). It will fail on NULL, though.

So then you can get the oldest rows with:

Select Top 6 *
From myTable
ORDER BY CDate([DateDue]) ASC
Bill
+1 for making the point that ALL of the data must have a value that can be converted to a date for this function to work.
richardtallent
OP needs oldest 6 rows from EACH GROUP, not just oldest six records.
spencer7593
+1  A: 

I think I understand your problem - let me give you a solution that doesn't get into dealing with your date issue - there are a number of solutions to that above.

Given this data:

   PQBSSN   DATE PQBNAME
1   1/1/2009 A
1   1/2/2009 A
1   1/3/2009 A
1   1/4/2009 Z
1   1/5/2009 Z
1   1/6/2009 Z
2   1/1/2009 B
2   1/2/2009 B
2   1/3/2009 B
2   1/4/2009 B
2   1/5/2009 B
2   1/6/2009 B
3   1/1/2009 C
3   1/2/2009 C
3   1/3/2009 C
3   1/4/2009 C
3   1/5/2009 C
3   1/6/2009 C

SELECT C1.PQBSSN, C1.PQBNAME, C3.Date
FROM [SELECT DISTINCT CA.PQBSSN, CA.PQBNAME FROM COUPONS AS CA]. AS C1, 
     [SELECT DISTINCT CB.DATE FROM COUPONS AS CB]. AS C3
WHERE C3.DATE IN 
     (SELECT TOP 2 C2.DATE FROM COUPONS AS C2 WHERE C2.PQBSSN = C1.PQBSSN ORDER BY C2.DATE);

The breakdown:

The CA select gives the unique rows of non-date information

The CB select gives all the dates in the table

The "WHERE C3.DATE" select gives you the dates that apply to each matching group. You need to put checks in the WHERE of this select for every independent field if there isn't a unique key for the grouping rows.

This Gives:

   PQBSS PQBNAME Date
    1   A 1/1/2009
    1   Z 1/1/2009
    2   B 1/1/2009
    3   C 1/1/2009
    1   A 1/2/2009
    1   Z 1/2/2009
    2   B 1/2/2009
    3   C 1/2/2009

I know this is a simplified version of your table, but I think it achieves your ends.

JeffP
I had to add another step to get the AmountDue back into the rows, but a modified version of this did produce the results I was looking for. I ended up splitting the thing into two different queries. One returned the Dates I needed, the other added the AmountDue.
STEP 1: (I substituted the long list of fields with ...)SELECT C1.DocType, ... C3.DateDueFROM [SELECT DISTINCT CA.DocType, CA.PayTo, CA.ContactName, CA.ContactNumber, CA.DocFooter, CA.PQBName, CA.LetterDate, CA.RetireeFirstName, CA.RetireeLastName, CA.Address1, CA.Address2, CA.City, CA.State, CA.ZIP, CA.PQBSSN, CA.EmployerCode FROM COUPONS1 AS CA]. AS C1, [SELECT DISTINCT CB.DateDue FROM COUPONS1 AS CB]. AS C3WHERE C3.DateDue IN (SELECT TOP 6 C2.DateDue FROM COUPONS1 AS C2 WHERE C2.PQBSSN = C1.PQBSSN ORDER BY C2.DateDue)ORDER BY C1.PQBSSN, C1.DocFooter, C1.LetterDate, C3.DateDue;
Step 2: I just linked the STEP 1 query to the original table to recover the AmountDue field.Thanks everyone for your help on this. All answers and insites were very helpful.