简体   繁体   中英

SQL to return first recorded date from All-time Records

Fishing Database that stores All-time records. However, when someone has a joint record, I want to revert to the first capture as the true record.

select * 
from T 
inner join (select Type, 
                   Name, 
                   max(TotDrams) as maxdrams 
            from T 
            WHERE Type='Common Bream' 
            group by Type, Name 
           ) sq on T.Type = sq.Type 
                and T.Name = sq.Name 
                and sq.maxdrams = T.TotDrams 
ORDER BY Ranking ASC

The above returns the all time records with the best capture for each name, but when a record is joint it naturally returns the additional record. I only want the earliest date record to be included in the all time records.

Is there a way of adapting the above code to remove the additional joint records and only pick the earliest recorded?

Fishname     Rank            Weight           Angler            Date

Slimey       Rank 1        2 lb   3 oz      John Budd         30/11/2013
Fishy        Rank 2        1 lb   15 oz     Chris Clot        12/01/2009
Scales       Rank 3        1 lb   12 oz     John Budd         21/03/2014
Scales       Rank 3        1 lb   12 oz     Harry White       01/04/2002

With the above example - which is what is currently happening, I would like to have John Budd's joint record removed as it is joint and is not the original.

One other note:- This SQL will be used with php.

You could add another join to the earliest dates that each fish was caught. Something like:

select * 
from T 
inner join (select Species, 
                   FishName, 
                   max(TotalDrams) as maxdrams 
            from T 
            WHERE Species='Common Bream' 
            AND DateCaught <> ''
            group by Species, FishName 
           ) sq on T.Species = sq.Species 
                and T.FishName = sq.FishName 
                and sq.maxdrams = T.TotalDrams 
inner join (select Species, 
                   FishName, 
                   min(DateCaught) as minDate 
            from T 
            WHERE Species='Common Bream' 
            AND DateCaught <> ''
            group by Species, FishName 
           ) sq2 on T.Species = sq2.Species 
                and T.FishName = sq2.FishName 
                and sq2.minDate = T.DateCaught 
where T.DateCaught <> ''
ORDER BY Rank ASC

And following on from that, you could refactor the 2 criteria into a single join:

select * 
from T 
inner join (select Species, 
                   FishName, 
                   max(TotalDrams) as maxdrams,
                   min(DateCaught) as minDate 
            from T 
            WHERE Species='Common Bream' 
            AND DateCaught <> ''
            group by Species, FishName 
           ) sq on T.Species = sq.Species 
                and T.FishName = sq.FishName 
                and sq.maxdrams = T.TotalDrams 
                and sq.minDate = T.DateCaught 
where T.DateCaught <> ''
ORDER BY Rank ASC

EDIT:

Further analysis of the data structure shows that the answer above isn't quite right - it's filtering out several records because of the date being a varchar, and also an incorrect assumption about the data structure.. Revised answer below:

select distinct 
   T.species,
   t.fishname,
   t.rank,
   t.pounds,
   t.ounces,
   t.drams,
   t.totaldrams,
   t.peg,
   t.angler,
   sq.*,
   sq2.*
FROM (select Species, 
             FishName, 
              max(TotalDrams) as maxdrams
      from T 
      WHERE Species='Common Bream' 
      group by Species, FishName 
     ) sq 
inner join (select Species, 
                   FishName, 
                   TotalDrams,
                   min(if(DateCaught='',STR_TO_DATE('31/12/3099','%d/%m/%Y'),STR_TO_DATE(DateCaught,'%d/%m/%Y'))) as minDate 
            from T 
            WHERE Species='Common Bream' 
            group by Species, FishName, TotalDrams 
           ) sq2 on sq.Species = sq2.Species 
                 and sq.FishName = sq2.FishName 
                 and sq.MaxDrams = sq2.TotalDrams
inner join T on sq.species = T.species 
and sq.fishname = T.fishname
and sq.maxdrams = T.totaldrams
and sq2.mindate = if(DateCaught='',STR_TO_DATE('31/12/3099','%d/%m/%Y'),STR_TO_DATE(DateCaught,'%d/%m/%Y'))

Sort by date after ranking, then group by fish name. Looks like that works?

select * from T inner join (select Species, FishName, max(TotalDrams) as maxdrams from T WHERE Species='Common Bream' group by Species, FishName ) sq on T.Species = sq.Species and T.FishName = sq.FishName and sq.maxdrams = T.TotalDrams GROUP BY T.FishName ORDER BY Rank, DateCaught ASC

The way I would approach this is slightly different, rather than trying to limit the results to the maximum, I would exclude the results that aren't. So using something like:

SELECT  T.*
FROM    T
        LEFT JOIN T AS T2
            ON T2.Species = T.Species
            AND T2.FishName = T.FishName
            AND (T2.TotalDrams < T.TotalDrams
                OR (T.TotalDrams = T2.TotalDrams AND T2.DateCaught > T.DateCaught))
WHERE   T.Species = 'Common Bream'
AND     T2.Species IS NULL
ORDER BY T.Rank ASC;

This uses the standard LEFT JOIN/IS NULL approach to exclude records where they are for the same fish type and either:

  • Have a lower value for TotalDrams

OR

  • Have the same value for TotalDrams AND a later DateCaught

Example on SQL Fiddle

Due to the way MySQL materialises sub-queries you might also find that this performs better than the original query that didn't give the required results!


EDIT

Okay, new approach. I think the best way to go about this is using variables to store a new Row number for each record, then you can filter for the top 1. The following will assign your row number based on ordering criteria:

SELECT  @r:= CASE WHEN @f = t.FishName AND @s:= t.Species 
                    THEN @r + 1 
                ELSE 1 
            END AS RowNum,
        @f:= t.FishName AS FishName,
        @s:= t.Species AS Species,
        t.Rank,
        t.Pounds,
        t.Ounces,
        t.Drams,
        t.TotalDrams,
        t.Peg,
        t.Angler,
        STR_TO_DATE(IF(t.DateCaught = '', '31/12/2050', t.DateCaught), '%d/%m/%Y')  AS DateCaught
FROM    T
        CROSS JOIN (SELECT  @f:= '',@s:='', @r:= 0) AS v
ORDER BY t.FishName, t.Species, t.TotalDrams DESC, DateCaught ASC;

Then you can put this into a subquery, and limit the records to the top 1:

SELECT  *
FROM    (   SELECT  @r:= CASE WHEN @f = t.FishName AND @s = t.Species 
                                THEN @r + 1 
                            ELSE 1 
                        END AS RowNum,
                    @f:= t.FishName AS FishName,
                    @s:= t.Species AS Species,
                    t.Rank,
                    t.Pounds,
                    t.Ounces,
                    t.Drams,
                    t.TotalDrams,
                    t.Peg,
                    t.Angler,
                    t.DateCaught
            FROM    T
                    CROSS JOIN (SELECT  @f:= '',@s:='', @r:= 0) AS v
            ORDER BY t.FishName, t.Species, t.TotalDrams DESC, STR_TO_DATE(IF(t.DateCaught = '', '31/12/2050', t.DateCaught), '%d/%m/%Y') ASC
        ) AS t
WHERE   t.RowNum = 1
ORDER BY t.Rank ASC;

Example on SQL Fiddle

This is the most flexible approach, if you want to add more rules, ie if you have two the same weight, and on the same date, you can add further ordering to your subquery, eg Angler . This guarantees only one record for each tuple of (fishname, species), and deterministic results given enough ordering.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM