SQL 从所有时间记录中返回第一个记录日期

Posted

技术标签:

【中文标题】SQL 从所有时间记录中返回第一个记录日期【英文标题】:SQL to return first recorded date from All-time Records 【发布时间】:2014-10-13 16:29:13 【问题描述】:

存储历史记录的钓鱼数据库。但是,当某人有联合记录时,我想恢复到第一次捕获作为真实记录。

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

上面返回每个名称的最佳捕获的所有时间记录,但是当记录是联合时,它自然会返回附加记录。我只想将最早的日期记录包含在所有时间记录中。

有没有办法修改上面的代码来删除额外的联合记录,只选择最早记录的?

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

对于上面的例子 - 这是目前正在发生的事情,我想删除 John Budd 的联合记录,因为它是联合的,而不是原始记录。

另一个注意事项:- 此 SQL 将与 php 一起使用。

【问题讨论】:

T 有 ID 列吗? 你能提供表格的样本数据吗? 不,它没有。我有一个 SQL Fiddle 设置。不同的字段名称但相同的最终结果。零条目的记录也会产生这个问题,但我有一个计划。如果您查看结果并看到排名 2 的三个人的记录相同 - 完美的问题 :) sqlfiddle.com/#!2/9d09cd/1 你能解释一下为什么 john budd 会被移除吗?是不是因为你只想要他钓到的中奖鱼,所以每人一张记录?还是其他标准? 这个数据集需要标准化。 【参考方案1】:

您可以将另一个联接添加到每条鱼被捕获的最早日期。比如:

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

然后,您可以将 2 个条件重构为单个连接:

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

编辑:

对数据结构的进一步分析表明,上面的答案并不完全正确——它过滤掉了几条记录,因为日期是一个 varchar,而且对数据结构的假设也不正确。修改后的答案如下:

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'))

【讨论】:

为什么你有两个内连接...你不能只做第一个内连接内的最小日期吗? +1 虽然 SEE HERE 他们有相同的结果,并且没有第二个内部连接,查询更清晰:) 这个 sql 似乎只返回一些。共有 25 种不同名称的普通鲷鱼。 嗨..你是对的..我花了更多时间在上面,我所做的一些假设是不正确的。再加上日期在 varchar 中,结果不太正确。修改后的答案应该有效,尽管它会让你对从未被捕获的鱼产生欺骗...... @JohnRuddell - 毕竟我确实需要第二次加入!查找最小日期时分组不同!【参考方案2】:

我的处理方式略有不同,而不是试图将结果限制在最大值,我会排除不是的结果。所以使用类似的东西:

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;

这使用标准的LEFT JOIN/IS NULL 方法来排除相同鱼类的记录,并且:

TotalDrams 的值较低

TotalDrams 具有相同的值 AND 之后的DateCaught

Example on SQL Fiddle

由于 mysql 实现子查询的方式,您可能还会发现这比没有给出所需结果的原始查询执行得更好!


编辑

好的,新方法。我认为解决此问题的最佳方法是使用变量为每条记录存储一个新的行号,然后您可以过滤前 1 个。以下将根据排序条件分配您的行号:

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;

然后你可以把它放到一个子查询中,并将记录限制在前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

这是最灵活的方法,如果您想添加更多规则,即如果您有两个相同的重量,并且在同一日期,您可以在子查询中添加进一步的排序,例如Angler。这保证了(鱼名、物种)的每个元组只有一个记录,并且在给定足够的顺序的情况下得到确定性结果。

【讨论】:

唯一的问题是空日期被认为比有日期的日期小.. 是的,我注意到了这一点,但数据存在严重缺陷。由于日期存储为 VARCHAR,因此它还将 '30/11/2013' 视为在 '21/03/2014' 之后。在关于如何处理空日期的问题中没有任何细节的情况下,MySQL 将空日期处理得更早的逻辑似乎是一个很好的逻辑。 是的,这就是为什么我发布了一个转换日期方法的答案,以便操作人员可以尝试使用它。虽然它确实需要规范化 GarethD - 谢谢。绝对精彩。现在好幸福。非常感谢其他所有人和他们的时间。【参考方案3】:

排名后按日期排序,然后按鱼名分组。看起来可行?

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

【讨论】:

这很好。一个小问题,那就是 DateCaught。不是排序,我认为这是因为它是一个字符串? 什么是排序不正确?该列是 varchar 可能会有问题。 对我的 php 东西感到困惑!好的,它工作得很好,但有个小烦恼是最早的 DateCaught 没有被选中。应该是 2009 年的 Weezy G。我已经更新了 sql fiddle sqlfiddle.com/#!2/a87dd/2 这不是确定性的 - 通过使用 GROUP BY T.FishName,您告诉 MySQL 您希望 fishname 的每个值都有一行,然而,您在ORDER BY 不确定选择了未包含在GROUP BY 中的字段的哪些值。如果您正确使用GROUP BY,则只有一个值可供选择,如果没有,则从可用值中随机选择一个值,然后根据您的顺序对这些随机值进行排序。这是有据可查的行为,不应依赖此类查询。 我已经在this answer 中更详细地解释了这个扩展在不正确使用时的缺点。

以上是关于SQL 从所有时间记录中返回第一个记录日期的主要内容,如果未能解决你的问题,请参考以下文章

如何按特定顺序从 select 中执行 Oracle SQL 更新?

SQL 返回另一个日期前 18 个月的所有记录(到月底)

我需要从第一个实例返回所有字段 - SQL

SQL:如何从重复行中选择第一条记录?

如何从 sql 查询中获取第一条和最后一条记录?

Oracle SQL 返回日期内的所有记录