【问题标题】:How to get First and Last record from a sql query?如何从 sql 查询中获取第一条和最后一条记录?
【发布时间】:2009-09-28 04:16:31
【问题描述】:

我在PostgreSQL 中有一个表,我对它运行了一个查询,其中包含几个返回多行的条件,按其中一列排序。一般是:

SELECT <some columns> 
FROM mytable
<maybe some joins here>
WHERE <various conditions>
ORDER BY date DESC

现在我只对从此查询中获取第一行和最后一行感兴趣。我可以将它们放在数据库之外,在我的应用程序中(这就是我实际所做的),但我想知道是否为了获得更好的性能,我不应该只从数据库中获取我真正感兴趣的那两条记录。

如果是这样,我该如何修改我的查询?

【问题讨论】:

标签: sql postgresql


【解决方案1】:

[警告:可能不是最有效的方法]:

(SELECT <some columns>
FROM mytable
<maybe some joins here>
WHERE <various conditions>
ORDER BY date DESC
LIMIT 1)

UNION ALL

(SELECT <some columns>
FROM mytable
<maybe some joins here>
WHERE <various conditions>
ORDER BY date ASC    
LIMIT 1)

【讨论】:

  • 我认为 'Top' 关键字仅适用于 SQL server,MySQL/Postgre 使用 'Limit'
  • 使用 UNION ALL 会稍微加快速度,因为它消除了对重复项的检查。当然,如果第一行和最后一行相同,它的工作方式会有所不同 - UNION 将只返回一行,UNION ALL 将返回同一行两次。
  • @Magnus Hagander:我不确定当最多有 2 行时它会更快。当然,我通常会区分 UNION 和 UNION ALL。
  • 按原样运行查询会在 UNION 附近出现语法错误,可能是因为必须只有一个限制和排序依据。我解决了它用括号括起来的查询,比如(SELECT ... LIMIT 1) UNION ALL (SELECT ... LIMIT 1)
  • 谁能解释为什么这可能没有效率?
【解决方案2】:

您可能想试试这个,可能比执行两个查询更快:

select <some columns>
from (
    SELECT <some columns>,
           row_number() over (order by date desc) as rn,
           count(*) over () as total_count
    FROM mytable
    <maybe some joins here>
    WHERE <various conditions>
) t
where rn = 1
   or rn = total_count
ORDER BY date DESC

【讨论】:

    【解决方案3】:

    第一条记录:

    SELECT <some columns> FROM mytable
    <maybe some joins here>
    WHERE <various conditions>
    ORDER BY date ASC
    LIMIT 1
    

    最后一条记录:

    SELECT <some columns> FROM mytable
    <maybe some joins here>
    WHERE <various conditions>
    ORDER BY date DESC
    LIMIT 1
    

    【讨论】:

    • 另一条评论中提到的UNION ALL方法肯定比发出两个查询要快。
    【解决方案4】:

    最后一条记录:

    SELECT * FROM `aboutus` order by id desc limit 1
    

    第一条记录:

    SELECT * FROM `aboutus` order by id asc limit 1
    

    【讨论】:

    • 这对于 PostgreSQL 来说是无效的 SQL(它使用标准双引号 " 来引用对象名称 - 无论如何这里都不需要它们)
    • @souleiman 每个查询都尽可能快。查询规划器将使用适当的索引并尽可能快地返回 O(log(N))...但是如果您总是 i> 想要 both 如 OP 所示的第一条和最后一条记录。只需在 2 个查询之间使用 UNION ALL(更快)(如果您不想重复,则使用 UNION)。
    【解决方案5】:

    到目前为止所有暴露的do方式,都必须扫描两次,第一行一次,最后一行一次。

    使用窗口函数“ROW_NUMBER() OVER (...)”加上“WITH Queries”,您可以只扫描一次并获得两项。

    窗口功能: https://www.postgresql.org/docs/9.6/static/functions-window.html

    有查询: https://www.postgresql.org/docs/9.6/static/queries-with.html

    例子:

    WITH scan_plan AS (
    SELECT
        <some columns>,
        ROW_NUMBER() OVER (ORDER BY date DESC) AS first_row, /*It's logical required to be the same as major query*/
        ROW_NUMBER() OVER (ORDER BY date ASC) AS last_row /*It's rigth, needs to be the inverse*/
    FROM mytable
    <maybe some joins here>
    WHERE <various conditions>
    ORDER BY date DESC)
    
    SELECT
        <some columns>
    FROM scan_plan
    WHERE scan_plan.first_row = 1 OR scan_plan.last_row = 1;
    

    这样,您将只进行一次关系、过滤和数据操作。

    在这两种方式上尝试一些 EXPLAIN ANALYZE。

    【讨论】:

    • 感谢您也提供对关键概念的参考
    • 上面的count(*) over () as total_count 性能更高一些,因为它只使用了一个WindowAgg,而且数据集也只排序了一次。
    【解决方案6】:
    SELECT <rows> FROM TABLE_NAME WHERE ROWID=(SELECT MIN(ROWID) FROM TABLE_NAME) 
    UNION
    SELECT <rows> FROM TABLE_NAME WHERE ROWID=(SELECT MAX(ROWID) FROM TABLE_NAME)
    

    SELECT * FROM TABLE_NAME WHERE ROWID=(SELECT MIN(ROWID) FROM TABLE_NAME) 
                                OR ROWID=(SELECT MAX(ROWID) FROM TABLE_NAME)
    

    【讨论】:

    • PostgreSQL 没有rowid,它在那里被称为ctid(Oracle 的 rowid 和 PostgreSQL 的 ctid 都不保证任何排序)
    • 为什么不让这个更简单:SELECT * FROM TABLE_NAME WHERE rowid=(SELECT MIN(rowid) FROM TABLE_NAME) OR rowid=(SELECT MAX(rowid) FROM TABLE_NAME)
    【解决方案7】:

    我知道这是一个有 7 年历史的线程,但问题几乎相同,并且接受的答案是我开始使用并最终优化为以下内容,在我的情况下,它始终返回 85ms +-5ms 是一个索引 int 字段。

    note1:已接受答案中的 UNION ALL 示例也有效,但在我的情况下性能较差,时间为 300 毫秒 +-20 毫秒。

    note2:下一个最受好评的答案(行计数器示例)也有效,但在我的情况下性能最低,时间为 800 毫秒 +-70 毫秒。

    select
      (select <some_column> from <some_table>
        order by <some_field> limit 1)        as oldest,
      (select <some_column> from <some_table> 
        order by <some_field> desc limit 1)   as newest
    ;
    

    我确实注意到 op 引用了可能的连接。我不需要为了我自己的目的而包含连接(只是在相当动态的视图中获取当前的低 ID 和高 ID),但是使用这个模型,最旧和最新的子查询应该能够成为完整的查询。尚未测试,因此不确定它是否有效或最佳。

    我确实测试过这个模型(上面可能也已经建议过),它可能更容易加入,但性能只是比上面示例的一半少一点,始终返回 220 毫秒 +在我的情况下为 -10 毫秒。

    select oldest.<some_field> as old, 
           newest.<some_field> as new  
    from
      (select <some_column> from <some_table>
        order by <some_field> limit 1)        as oldest,
      (select <some_column> from <some_table> 
        order by <some_field> desc limit 1)   as newest
    ;
    

    【讨论】:

    • 那么两个代码 sn-ps 中的哪一个是 85ms 的那个?
    【解决方案8】:

    在某些情况下,WINDOW 函数 FIRST_VALUE() 和 LAST_VALUE() 很有用。主要优势 - 此查询是可读的,仅对数据进行一次排序,并且它只有一个查询可用于多列。

     SELECT
        FIRST_VALUE(timestamp) over w as created_dt,
        LAST_VALUE(timestamp) over w as last_update_dt,
        LAST_VALUE(action) over w as last_action
    FROM events
    WINDOW w as (ORDER BY timestamp ASC)
    

    它可以用于通过某个ID获取第一行和最后一行

    SELECT DISTINCT
        order_id,
        FIRST_VALUE(timestamp) over w as created_dt,
        LAST_VALUE(timestamp) over w as last_update_dt,
        LAST_VALUE(action) over w as last_action
        
    FROM events as x
    WINDOW w as (PARTITION BY order_id ORDER BY timestamp ASC)
    

    【讨论】:

    • 为避免重复窗口函数,您可以对每个窗口行为使用WINDOW 子句,然后在OVER 中引用它:WINDOW w as (PARTITION BY order_id ORDER BY timestamp ASC)
    【解决方案9】:
    select *
    from {Table_Name}
    where {x_column_name}=(
        select d.{x_column_name} 
        from (
            select rownum as rno,{x_column_name}
            from {Table_Name})d
            where d.rno=(
                select count(*)
                from {Table_Name}));
    

    【讨论】:

      【解决方案10】:
      -- Create a function that always returns the first non-NULL item
      CREATE OR REPLACE FUNCTION public.first_agg ( anyelement, anyelement )
      RETURNS anyelement LANGUAGE SQL IMMUTABLE STRICT AS $$
              SELECT $1;
      $$;
      
      
      -- And then wrap an aggregate around it
      CREATE AGGREGATE public.FIRST (
              sfunc    = public.first_agg,
              basetype = anyelement,
              stype    = anyelement
      );
      
      -- Create a function that always returns the last non-NULL item
      CREATE OR REPLACE FUNCTION public.last_agg ( anyelement, anyelement )
      RETURNS anyelement LANGUAGE SQL IMMUTABLE STRICT AS $$
              SELECT $2;
      $$;
      
      -- And then wrap an aggregate around it
      CREATE AGGREGATE public.LAST (
              sfunc    = public.last_agg,
              basetype = anyelement,
              stype    = anyelement
      );
      

      从这里得到它: https://wiki.postgresql.org/wiki/First/last_(aggregate)

      【讨论】:

        【解决方案11】:
        SELECT 
            MIN(Column), MAX(Column), UserId 
        FROM 
            Table_Name
        WHERE 
            (Conditions)
        GROUP BY 
            UserId DESC
        

        SELECT        
            MAX(Column) 
        FROM            
            TableName
        WHERE        
            (Filter)
        
        UNION ALL
        
        SELECT        
            MIN(Column)
        FROM            
            TableName AS Tablename1
        WHERE        
            (Filter)
        ORDER BY 
            Column
        

        【讨论】:

          【解决方案12】:

          为什么不使用order by asc limit 1 和相反的order by desc limit 1

          【讨论】:

            【解决方案13】:

            如何在c#中获取DB的第一条和最后一条记录。

            SELECT TOP 1 * 
              FROM ViewAttendenceReport 
             WHERE EmployeeId = 4 
               AND AttendenceDate >='1/18/2020 00:00:00' 
               AND AttendenceDate <='1/18/2020 23:59:59'
             ORDER BY Intime ASC
             UNION
            SELECT TOP 1 * 
              FROM ViewAttendenceReport 
             WHERE EmployeeId = 4 
               AND AttendenceDate >='1/18/2020 00:00:00' 
               AND AttendenceDate <='1/18/2020 23:59:59' 
             ORDER BY OutTime DESC; 
            

            【讨论】:

              【解决方案14】:

              我认为这段代码是一样的并且更容易阅读。

              SELECT <some columns> 
              FROM mytable
              <maybe some joins here>
              WHERE date >= (SELECT date from mytable)
              OR date <= (SELECT date from mytable);
              

              【讨论】:

              • 虽然此代码可能会回答问题,但提供有关此代码为何和/或如何回答问题的额外上下文可提高其长期价值。
              猜你喜欢
              • 1970-01-01
              • 2017-05-08
              • 1970-01-01
              • 1970-01-01
              • 2014-10-23
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2019-12-28
              相关资源
              最近更新 更多