MySQL SELECT 语句的两行之间的区别
Posted
技术标签:
【中文标题】MySQL SELECT 语句的两行之间的区别【英文标题】:MySQL difference between two rows of a SELECT Statement 【发布时间】:2013-01-29 05:05:19 【问题描述】:我正在尝试区分 mysql 数据库中的两行。 我有这张表,其中包含 ID、公里、日期、car_id、car_driver 等... 由于我并不总是以正确的顺序在表格中输入信息,因此我最终可能会得到如下信息:
ID | Kilometers | date | car_id | car_driver | ...
1 | 100 | 2012-05-04 | 1 | 1
2 | 200 | 2012-05-08 | 1 | 1
3 | 1000 | 2012-05-25 | 1 | 1
4 | 600 | 2012-05-16 | 1 | 1
使用 select 语句,我可以正确地对表格进行排序:
SELECT * FROM mytable ORDER BY car_driver ASC, car_id ASC, date ASC
我会得到这个:
ID | Kilometers | date | car_id | car_driver | ...
1 | 100 | 2012-05-04 | 1 | 1
2 | 200 | 2012-05-08 | 1 | 1
4 | 600 | 2012-05-16 | 1 | 1
3 | 1000 | 2012-05-25 | 1 | 1
现在我想做一个视图,基本上我有这个额外的信息:自上次日期以来的公里数,我想获得这样的东西:
ID | Kilometers | date | car_id | car_driver | number_km_since_last_date
1 | 100 | 2012-05-04 | 1 | 1 | 0
2 | 200 | 2012-05-08 | 1 | 1 | 100
4 | 600 | 2012-05-16 | 1 | 1 | 400
3 | 1000 | 2012-05-25 | 1 | 1 | 400
我想过做一个 INNER JOIN 来执行我想要的,但我觉得我不能对我的 ID 做 join,因为它们没有正确排序。 有没有办法实现我想要的?
我应该创建一个带有某种 row_number 的视图,然后我可以在我的 INNER JOIN 中使用它吗?
【问题讨论】:
这真的很难读。你能用正确的代码部分重新格式化吗? 是否有理由只使用 MySQL 完成此操作? 如果两个日期相同怎么办? 使用支持窗口功能的现代 DBMS 和lag()
会如此简单
您是否希望每个car_id
或每个car_driver
的公里差重新从0 开始?因为这是一个稍微不同的问题(可以在其他 DBMS 中使用 PARTITION BY
轻松解决,但是)会使 MySQL 解决方案更加复杂。
【参考方案1】:
SELECT
mt1.ID,
mt1.Kilometers,
mt1.date,
mt1.Kilometers - IFNULL(mt2.Kilometers, 0) AS number_km_since_last_date
FROM
myTable mt1
LEFT JOIN myTable mt2
ON mt2.Date = (
SELECT MAX(Date)
FROM myTable mt3
WHERE mt3.Date < mt1.Date
)
ORDER BY mt1.date
Sql Fiddle
或者,通过 MySql hackiness 模拟 lag()
函数...
SET @kilo=0;
SELECT
mt1.ID,
mt1.Kilometers - @kilo AS number_km_since_last_date,
@kilo := mt1.Kilometers Kilometers,
mt1.date
FROM myTable mt1
ORDER BY mt1.date
Sql Fiddle
【讨论】:
谢谢你真的回答了我的问题,但是我应该以更完整的方式问它。实际上,我的 Select 语句不仅在日期上完成,而且在其他标准上完成,例如:car_id、car_driver 等……所以我的 ORDER BY 中的列比我的列要多。 @user1108276 如果您使用其他详细信息/标准更新您的问题,我可以为您提供更好的答案... Mickael 还有其他想法吗?那会很有帮助 我认为我们不需要第三张表,``` LEFT JOIN myTable mt2 ON mt2.Date = ( SELECT MAX(Date) FROM myTable mt2 WHERE mt2.Date 【参考方案2】:在 Postgres、Oracle 和 SQL-Server 2012 中,这很简单,使用 LAG()
函数:
SELECT
id, kilometers, date,
kilometers
- COALESCE( LAG(kilometers) OVER (ORDER BY date ASC, car_driver ASC, id ASC)
, kilometers)
AS number_km_since_last_date
FROM
mytable ;
在 MySQL 中,我们必须做一些讨厌的构造。内联子查询(性能可能不是很好):
SELECT
id, kilometers, date,
kilometers - COALESCE(
( SELECT p.kilometers
FROM mytable AS p
WHERE ( p.date = m.date AND p.car_driver = m.car_driver
AND p.id < m.id
OR p.date = m.date AND p.car_driver < m.car_driver
OR p.date < m.date
)
ORDER BY p.date DESC, p.car_driver DESC
LIMIT 1
), kilometers)
AS number_km_since_last_date
FROM
mytable AS m ;
或自连接(已由@Michael Fredrickson 提供)或使用 MySQL 变量(也已提供)。
如果您希望计数器对每个 car_id
重新从 0 开始,这将在许多其他 DBMS 中使用 PARTITION BY
完成:
SELECT
id, kilometers, date,
kilometers
- COALESCE( LAG(kilometers) OVER (PARTITION BY car_id
ORDER BY date ASC, car_driver ASC, id ASC)
, kilometers)
AS number_km_since_last_date
FROM
mytable ;
在 MySQL 中可以这样完成:
SELECT
id, kilometers, date,
kilometers - COALESCE(
( SELECT p.kilometers
FROM mytable AS p
WHERE p.car_id = m.car_id
AND ( p.date = m.date AND p.car_driver = m.car_driver
AND p.id < m.id
OR p.date = m.date AND p.car_driver < m.car_driver
OR p.date < m.date
)
ORDER BY p.date DESC, p.car_driver DESC
LIMIT 1
), kilometers)
AS number_km_since_last_date
FROM
mytable AS m ;
【讨论】:
@MichaelMcGowan 是的,我正在写如何在 MySQL 中完成它,但 Michael 更快。 explainextended.com/2009/03/10/… 展示了在 MySQL 中获取 lag() 的通用解决方案 问题是我可以有两次相同的日期。因此,为什么我的 ORDER BY 是根据日期以外的其他标准完成的:car_id、car_driver、km 等...... 然后你可以根据需要更改ORDER BY p.date DESC
部分。
但是它如何进入下一条记录呢?我看到你有 WHERE p.date < m.date
在我的情况下日期可能没有正确排序,你可以在同一天有两个条目......【参考方案3】:
对于未排序的数据,我只能想到内联子查询(在大表上不是一个好主意):
select t1.*,
t1.Kilometers - (select top 1 kilometers from mytable t2 where t2.date < t1.date order by t2.date desc) as number_km_since_last_date
from mytable t1
如果你得到数据排序,你可以使用左连接
select t1.*
t1.Kilometers - t2.Kilometers as number_km_since_last_date
from mytable t1
left join mytable t2
on t1.id = t2.id + 1
你可能会说我更像是一个 TSQL 人,所以你可能需要调整 MySQL 的语法。
【讨论】:
【参考方案4】:这是一个在这个用例中也使用 CURSOR 的示例
CREATE TABLE TEMP1
(
MyDate DATETIME,
MyQty INT
)
INSERT INTO TEMP1 VALUES ('01/08/17', 100)
INSERT INTO TEMP1 VALUES ('01/09/17', 120)
INSERT INTO TEMP1 VALUES ('01/10/17', 180)
DECLARE @LastDate DATETIME = NULL
DECLARE @LastQty INT = NULL
DECLARE @MyDate DATETIME = NULL
DECLARE @MyQty INT = NULL
DECLARE mycursor CURSOR FOR
SELECT MyDate, MyQty FROM TEMP1 ORDER BY MyDate
OPEN mycursor
FETCH NEXT FROM mycursor INTO @MyDate, @MyQty
WHILE @@FETCH_STATUS = 0
BEGIN
SELECT @MyDate, @MyQty - @LastQty
SET @LastDate = @MyDate
SET @LastQty = @MyQty
FETCH NEXT FROM mycursor INTO @MyDate, @MyQty
END
CLOSE mycursor
DEALLOCATE mycursor
【讨论】:
【参考方案5】:在 MySQL 8 中,您可以使用 CTE 和 ROW_NUMBER 窗口函数来进行更具可读性的查询
WITH cte_name AS (
SELECT
ROW_NUMBER() OVER (ORDER BY update_time) as row_num,
id,
other_data,
update_time
FROM table_name WHERE condition = 'some_condition'
)
SELECT t2.id, t2.other_data, TIMEDIFF(t2.update_time, t1.update_time) AS time_taken
FROM
cte_name t1
JOIN cte_name t2 ON t1.row_num = t2.row_num-1
ORDER BY time_taken;
在此示例中,我尝试获取日期时间值之间的差异。
想法是使用 ROW_NUMBER 窗口函数在按 update_time 排序后为每一行分配一个递增编号。 CTE 允许我们编写子查询,而无需重复编写相同的代码。 我们自行加入 CTE。连接条件基本上是 - 第二个子查询的每个 nᵗʰ 项与第一个子查询的 n-1ᵗʰ 项连接(这也意味着第一行将从结果集中消失。如果需要,可以使用 UNION 添加第一行开始)。有一些很好的教程: CTE (Common Table Expression)、ROW_NUMBER 甚至是window functions
【讨论】:
以上是关于MySQL SELECT 语句的两行之间的区别的主要内容,如果未能解决你的问题,请参考以下文章