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


75

我在PostgreSQL中有一个表,我用几种条件对它运行查询,该条件返回按行之一排序的多行。通常是:

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

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

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



2
@rexem:最小值和最大值在多个列上不起作用-仅当您按此列排序时,它们才在单个列上起作用。

您可能还想看看SELECT DISTINCT ON (...) ... ORDER BY ...。参见PostgreSQL文档
RhinoDevel

Answers:


113

[注意:可能不是最有效的方法]:

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

10
我认为,“顶”关键字是SQL服务器只,MySQL的/ Postgre用途“限价”
机器人

2
使用UNION ALL可以稍微加快速度,因为它可以消除重复检查。如果第一行和最后一行相同,则工作方式将有所不同-UNION将仅返回一行,UNION ALL将两次返回同一行。
Magnus Hagander

@Magnus Hagander:我不确定最多两行时速度会不会更快。当然,我通常会区分UNION和UNION ALL。
米奇·

按原样运行查询会给我UNION附近的语法错误,可能是因为必须只有一个限制和排序依据。我用圆括号括起来的查询解决了问题,例如(SELECT ... LIMIT 1) UNION ALL (SELECT ... LIMIT 1)
Fermin Silva

谁能解释为什么这可能无效?
Joseph K.

34

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

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

24

第一记录:

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

1
另一条评论中提到的UNION ALL方法肯定比发出两个查询快。
Magnus Hagander

17

上一条记录:

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

第一条记录:

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

2
对于PostgreSQL来说,这是无效的SQL(它使用标准的双引号"来引用对象名称-无论如何这里都不需要它们)
a_horse_with_no_name 2012年

@souleiman每个查询都尽可能快。查询计划器将使用适当的索引并尽可能快地返回O(log(N))...但是,如果您始终希望同时使用第一个查询和第一个查询,则在两个单独的查询中执行此操作将比一个查询更慢和/或效率更低。OP指示的最后一条记录。只需在两个查询之间使用UNION ALL(更快)即可(如果您不想重复,则使用UNION)。
DrFriedParts '16

8

到目前为止,在所有公开的方式中,都必须进行两次扫描,一次扫描到第一行,最后一次扫描。

使用窗口功能“ 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。


感谢您也提供了关键概念的参考
Lino Bossio

count(*) over () as total_count上面的更高性能的一点点,因为它使用了只有一个WindowAgg和数据集进行排序一次为好。
ruloweb

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

9
PostgreSQL没有a rowid,它在ctid那里被调用(并且Oracle的rowid和PostgreSQL的ctid都不保证任何顺序)
a_horse_with_no_name 2012年

5
为什么不做得这么简单: SELECT * FROM TABLE_NAME WHERE rowid=(SELECT MIN(rowid) FROM TABLE_NAME) OR rowid=(SELECT MAX(rowid) FROM TABLE_NAME)
Matt Kneiser 2012年

1
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}));

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


1

在某些情况下(当列数不多时),WINDOW函数FIRST_VALUE()和LAST_VALUE()很有用。

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

该查询仅对数据排序一次。

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

SELECT DISTINCT
    order_id,
    FIRST_VALUE(timestamp) over (PARTITION BY order_id ORDER BY timestamp ASC) as created_dt,
    LAST_VALUE(timestamp) over (PARTITION BY order_id ORDER BY timestamp ASC) as last_update_dt,
    LAST_VALUE(action) over (PARTITION BY order_id ORDER BY timestamp ASC) as last_action

FROM events as x

0
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

0

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

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; 

0

我认为这段代码变得相同并且更易于阅读。

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

2
尽管此代码可以回答问题,但提供有关此代码为何和/或如何回答问题的其他上下文,可以提高其长期价值。
伊戈尔·F

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.