【区块链 | Uniswap】3.剖析DeFi交易产品之Uniswap:V2下篇

article/2025/9/25 19:52:34

前言

上篇我们主要讲了 UniswapV2 整体分为了哪些项目,并重点讲解了 uniswap-v2-core 的核心代码实现;中篇主要对 uniswap-v2-periphery 的路由合约实现进行了剖析;现在剩下 V2 系列的最后一篇,我会介绍剩下的一些内容,主要包括:TWAP、FlashSwap、质押挖矿

TWAP

TWAP = Time-Weighted Average Price,即时间加权平均价格,可用来创建有效防止价格操纵的链上价格预言机

TWAP 的实现机制其实很简单。首先,在配对合约里会存储三个相关变量:

  • price0CumulativeLast

  • price1CumulativeLast

  • blockTimestampLast

前两个变量是两个 token 的累加价格,最后一个变量则用来记录更新的区块时间。我们可以直接来看看其代码实现:

这是 UniswapV2Pair 合约的 _update 函数,每次 mintburnswapsync 时都会触发更新。实现逻辑很容易理解,主要就以下几步:

  1. 读取当前的区块时间 blockTimestamp

  2. 计算出与上一次更新的区块时间之间的时间差 timeElapsed

  3. 如果 timeElapsed > 0 且两个 token 的 reserve 都不为 0,则更新两个累加价格

  4. 更新两个 reserve 和区块时间 blockTimestampLast

有些人可能还是不太理解累加价格的意义,要把它理解透彻,先从当前时刻的价格说起,即 token0 和 token1 的当前价格,其实可以根据以下公式计算所得:

price0 = reserve1 / reserve0
price1 = reserve0 / reserve1

比如,假设两个 token 分别为 WETH 和 USDT,当前储备量分别为 10 WETH 和 40000 USDT,那么 WETH 和 USDT 的价格分别为:

price0 = 40000/10 = 4000 USDT
price1 = 10/40000 = 0.00025 WETH

现在,再加上时间维度来考虑。比如,当前区块时间相比上一次更新的区块时间,过去了 5 秒,那就可以算出这 5 秒时间的累加价格:

price0Cumulative = reserve1 / reserve0 * timeElapsed = 40000/10*5 = 20000 USDT
price1Cumulative = reserve0 / reserve1 * timeElapsed = 10/40000*5 = 0.00125 WETH

假设之后再过了 6 秒,最新的 reserve 分别变成了 12 WETH 和 32000 USDT,则最新的累加价格变成了:

price0CumulativeLast = price0Cumulative + reserve1 / reserve0 * timeElapsed = 20000 + 32000/12*6 = 36000 USDT
price1CumulativeLast = price1Cumulative + reserve0 / reserve1 * timeElapsed = 0.00125 + 12/32000*6 = 0.0035 WETH

这就是合约里所记录的累加价格了。

另外,每次计算时因为有 timeElapsed 的判断,所以其实每次计算的是每个区块的第一笔交易。而且,计算累加价格时所用的 reserve 是更新前的储备量,所以,实际上所计算的价格是之前区块的,因此,想要操控价格的难度也就进一步加大了。

有了前面的基础,接下来就可以计算 TWAP 即时间加权平均价格了。计算公式也很简单,如下图:

代入我们的例子,为了简化,我们将前面 5 秒时间的时刻记为 T1,累加价格记为 priceT1,而 6 秒时间后的时刻记为 T2,累加价格记为 priceT2。如此,可以计算出,在后面 6 秒时间里的平均价格:

twap = (priceT2 - priceT1)/(T2 - T1) = (36000 - 20000)/6 = 2666.66

在实际应用中,一般有两种计算方案,一是固定时间窗口的 TWAP,二是移动时间窗口的 TWAP。在 uniswap-v2-periphery 项目中,examples 目录下提供了这两种方案的示例代码,分为是 ExampleOracleSimple.sol 和 ExampleSlidingWindowOracle.sol,具体代码就不展开讲解了。

现在,Uniswap TWAP 已经被广泛应用于很多 DeFi 协议,很多时候会结合 Chainlink 一起使用。比如 Compound 就使用 Chainlink 进行喂价并加入 Uniswap TWAP 进行边界校验,防止价格波动太大。

FlashSwap

FlashSwap,翻译过来就是闪电兑换,和闪电贷(FlashLoan) 有点类似。

从代码层面来说,闪电兑换的触发在 UniswapV2Pair 合约的 swap 函数里的,该函数里有这么一行代码:

if (data.length > 0) IUniswapV2Callee(to).uniswapV2Call(msg.sender, amount0Out, amount1Out, data);

这行代码主要说明了三个信息:

  1. to 地址是一个合约地址

  2. to 地址的合约实现了 IUniswapV2Callee 接口

  3. 可以在 uniswapV2Call 函数里执行 to 合约自己的逻辑

一般情况下的兑换流程,是先支付 tokenA,再得到 tokenB。但闪电兑换却可以先得到 tokenB,最后再支付 tokenA。如下图:

即是说,通过闪电兑换,可以实现无前置成本的套利。

比如,在 Uniswap 上可以用 3000 DAI 兑换出 1 ETH,而在 Sushi 上可以将 1 ETH 兑换成 3100 DAI,这就存在 100 DAI 的套利空间了。但是,如果用户钱包里没有 DAI 的话,该怎么套利呢?通过 Uniswap 的闪电兑换,就可以先获得 ETH,再将 ETH 在 Sushi 卖出得到 DAI,最后支付 DAI 给到 Uniswap,这样就实现了无需前置资金成本的套利了。

理论上,只要利润空间能覆盖两边的交易手续费和 GAS,就值得执行套利。这种套利行为能使得不同 DEX 之间的价格趋于一致。

闪电兑换还可以应用于另一种场景。假设用户想在 Compound 抵押 ETH 借出 DAI,再用借出的 DAI 到 Uniswap 兑换成 ETH,再抵押到 Compound 借出更多 DAI,如此重复操作,从而提高做多 ETH 的杠杆率。这么做的效率非常低。而使用闪电兑换,可以大大提高交易效率:

  1. 先从 Uniswap 得到 ETH

  2. 将用户的 ETH 和从 Uniswap 得到的 ETH 抵押进 Compound

  3. 从 Compound 借出 DAI

  4. 在 Uniswap 支付 DAI

上述步骤也不需要重复执行,一次流程就实现了用户想要的杠杆率,相比之下,明显高效很多。

在 uniswap-v2-periphery 项目中,examples 目录下有个 ExampleFlashSwap.sol,就是实现闪电兑换的一个示例,实现的是在 UniswapV1 和 UniswapV2 之间套利。

质押挖矿

质押挖矿项目也同样很小,这是项目的 github 地址:

  • https://github.com/Uniswap/liquidity-staker

总共只有四个 sol 文件:

  • IStakingRewards.sol

  • RewardsDistributionRecipient.sol

  • StakingRewards.sol

  • StakingRewardsFactory.sol

IStakingRewards.sol 是一个接口文件,定义了质押合约 StakingRewards 需要实现的一些函数,其中,Mutative 函数只有四个:

  • stake:充值,即质押

  • withdraw:提现,即解质押

  • getReward:提取奖励

  • exit:退出

剩下的则都是 View 函数:

  • lastTimeRewardApplicable:有奖励的最近区块数

  • rewardPerToken:每单位 Token 奖励数量

  • earned:用户已赚但未提取的奖励数量

  • getRewardForDuration:挖矿奖励总量

  • totalSupply:总质押量

  • balanceOf:用户的质押余额

RewardsDistributionRecipient.sol 则是一个抽象合约,跟常用的 Ownable 合约类似,我们可以直接看看其代码实现:

总共就 12 行代码,rewardsDistribution 其实就是管理员地址,还有一个 onlyRewardsDistribution 的 modifier,这不就是和我们熟知的 Ownable 一样的功能嘛。另外,还定义了一个抽象函数 notifyRewardAmount,所以实际上这就是一个抽象合约。而继承了该合约的是 StakingRewards 合约,后面再细说。

StakingRewards.sol 留到最后再说,先来看看 StakingRewardsFactory.sol,这是一个工厂合约,主要就是用来部署 StakingRewards 合约的。

StakingRewardsFactory

工厂合约里定义了四个变量:

  • rewardsToken:用作奖励的代币,其实就是 UNI 代币

  • stakingRewardsGenesis:质押挖矿开始的时间

  • stakingTokens:用来质押的代币数组,一般就是各交易对的 LPToken

  • stakingRewardsInfoByStakingToken:一个 mapping,用来保存质押代币和质押合约信息之间的映射

质押合约信息则是一个数据结构:

struct StakingRewardsInfo {address stakingRewards;uint rewardAmount;
}

其中,stakingRewards 其实就是 StakingRewards 合约(即质押合约)地址,rewardAmount 则是该质押合约每周期的奖励总量。

rewardsToken 和 stakingRewardsGenesis 在工厂合约的构造函数里就初始化的。除了构造函数,工厂合约还有三个函数:

  • deploy

  • notifyRewardAmounts

  • notifyRewardAmount

deploy 就是部署 StakingRewards 合约的函数,其代码实现如下:

function deploy(address stakingToken, uint rewardAmount) public onlyOwner {StakingRewardsInfo storage info = stakingRewardsInfoByStakingToken[stakingToken];require(info.stakingRewards == address(0), 'StakingRewardsFactory::deploy: already deployed');info.stakingRewards = address(new StakingRewards(address(this), rewardsToken, stakingToken));info.rewardAmount = rewardAmount;stakingTokens.push(stakingToken);
}

两个入参,stakingToken 就是质押代币,一般为 LPToken;rewardAmount 则是奖励数量。

实现逻辑,先从 mapping 中读取出 info,如果 info 的 stakingRewards 不为零地址说明该质押代币的质押合约已经部署过了,不能重复部署。接着,用 new 的方式创建了 StakeingRewards 合约,并将合约地址赋值给 info.stakingRewards,将合约地址保存起来。之后,再保存 rewardAmount。最后,将 stakingToken 加到质押代币数组里。至此,质押合约的部署工作就完成了。

部署合约之后,下一步应该将用来挖矿的代币转入到质押合约中,这就要通过 notifyRewardAmount 函数了,其代码实现如下:

function notifyRewardAmount(address stakingToken) public {require(block.timestamp >= stakingRewardsGenesis, 'StakingRewardsFactory::notifyRewardAmount: not ready');StakingRewardsInfo storage info = stakingRewardsInfoByStakingToken[stakingToken];require(info.stakingRewards != address(0), 'StakingRewardsFactory::notifyRewardAmount: not deployed');if (info.rewardAmount > 0) {uint rewardAmount = info.rewardAmount;info.rewardAmount = 0;require(IERC20(rewardsToken).transfer(info.stakingRewards, rewardAmount),'StakingRewardsFactory::notifyRewardAmount: transfer failed');StakingRewards(info.stakingRewards).notifyRewardAmount(rewardAmount);}
}

调用该函数之前,其实还有一个前提条件要先完成,那就是需要先将用来挖矿奖励的 UNI 代币数量先转入该工厂合约。有个这个前提,工厂合约的该函数才能实现将 UNI 代币下发到质押合约中去。

代码逻辑就很简单了,先是判断当前区块的时间需大于等于质押挖矿的开始时间。然后读取出指定的质押代币 stakingToken 映射的质押合约 info,要求 info 的质押合约地址不能为零地址,否则说明还没部署。再判断 info.rewardAmount 是否大于零,如果为零也不用下发奖励。if 语句里面的逻辑主要就是调用 rewardsToken 的 transfer 函数将奖励代币转发给质押合约,再调用质押合约的 notifyRewardAmount 函数触发其内部处理逻辑。另外,将 info.rewardAmount 重置为 0,可以避免向质押合约重复下发奖励代币。

而 notifyRewardAmounts 函数,则是遍历整个质押代币数组,对每个代币再调用 notifyRewardAmount,实现逻辑非常简单。

至此,工厂合约的代码逻辑就讲完了。下面,就来看看 StakingRewards 合约了。

StakingRewards

StakingRewards 合约会继承 RewardsDistributionRecipient 合约和 IStakingRewards 接口。

StakingRewards 存储的变量则比较多,除了继承自 RewardsDistributionRecipient 抽象合约里的 rewardsDistribution 变量之外,还有 11 个变量:

  • rewardsToken:奖励代币,即 UNI 代币

  • stakingToken:质押代币,即 LPToken

  • periodFinish:质押挖矿结束的时间,默认时为 0

  • rewardRate:挖矿速率,即每秒挖矿奖励的数量

  • rewardsDuration:挖矿时长,默认设置为 60 天

  • lastUpdateTime:最近一次更新时间

  • rewardPerTokenStored:每单位 token 奖励数量

  • userRewardPerTokenPaid:用户的每单位 token 奖励数量

  • rewards:用户的奖励数量

  • _totalSupply:私有变量,总质押量

  • _balances:私有变量,用户质押余额

前面讲工厂合约的 notifyRewardAmount 函数时,提到最后其实会调用到 StakingRewards 合约的 notifyRewardAmount 函数,我们就来看看这个函数是如何实现的:

function notifyRewardAmount(uint256 reward) external onlyRewardsDistribution updateReward(address(0)) {if (block.timestamp >= periodFinish) {rewardRate = reward.div(rewardsDuration);} else {uint256 remaining = periodFinish.sub(block.timestamp);uint256 leftover = remaining.mul(rewardRate);rewardRate = reward.add(leftover).div(rewardsDuration);}// Ensure the provided reward amount is not more than the balance in the contract.// This keeps the reward rate in the right range, preventing overflows due to// very high values of rewardRate in the earned and rewardsPerToken functions;// Reward + leftover must be less than 2^256 / 10^18 to avoid overflow.uint balance = rewardsToken.balanceOf(address(this));require(rewardRate <= balance.div(rewardsDuration), "Provided reward too high");lastUpdateTime = block.timestamp;periodFinish = block.timestamp.add(rewardsDuration);emit RewardAdded(reward);
}

该函数由工厂合约触发执行,而且根据工厂合约的代码逻辑,该函数也只会被触发一次。

由于 periodFinish 默认值为 0 且只会在该函数中更新值,所以只会执行 block.timestamp >= periodFinish 的分支逻辑,将从工厂合约转过来的挖矿奖励总量除以挖矿奖励时长,得到挖矿速率 rewardRate,即每秒的挖矿数量。理论上,else 分支是执行不到的,除非以后工厂合约升级为可以多次触发执行该函数。之后,读取 balance 并校验下 rewardRate,可以保证收取到的挖矿奖励余额也是充足的,rewardRate 就不会虚高。最后,更新 lastUpdateTime 和 periodFinish。periodFinish 就是在当前区块时间上加上挖矿时长,就得到了挖矿结束的时间。

接着,再来看看几个核心业务函数的实现,包括 stake、withdraw、getReward。

stake 就是质押代币的函数,实现代码如下:

function stake(uint256 amount) external nonReentrant updateReward(msg.sender) {require(amount > 0, "Cannot stake 0");_totalSupply = _totalSupply.add(amount);_balances[msg.sender] = _balances[msg.sender].add(amount);stakingToken.safeTransferFrom(msg.sender, address(this), amount);emit Staked(msg.sender, amount);
}

函数体内的代码逻辑很简单,将用户指定的质押量 amount 增加到 _totalSupply(总质押量)和 _balances(用户的质押余额),最后调用 stakingToken 的 safeTransferFrom 将代币从用户地址转入当前合约地址。

withdraw 则是用来提取质押代币的,代码实现也同样很简单,_totalSupply 和 _balances 都减掉提取数量,且将代币从当前合约地址转到用户地址:

function withdraw(uint256 amount) public nonReentrant updateReward(msg.sender) {require(amount > 0, "Cannot withdraw 0");_totalSupply = _totalSupply.sub(amount);_balances[msg.sender] = _balances[msg.sender].sub(amount);stakingToken.safeTransfer(msg.sender, amount);emit Withdrawn(msg.sender, amount);
}

getReward 是领取挖矿奖励的函数,内部逻辑主要就是从 rewards 中读取出用户有多少奖励并清零和转账给到用户:

function getReward() public nonReentrant updateReward(msg.sender) {uint256 reward = rewards[msg.sender];if (reward > 0) {rewards[msg.sender] = 0;rewardsToken.safeTransfer(msg.sender, reward);emit RewardPaid(msg.sender, reward);}
}

这几个核心业务函数体内的逻辑都非常好理解,值得一说的其实是每个函数声明最后的 updateReward(msg.sender),这是一个更新挖矿奖励的 modifer,我们来看其代码:

modifier updateReward(address account) {rewardPerTokenStored = rewardPerToken();lastUpdateTime = lastTimeRewardApplicable();if (account != address(0)) {rewards[account] = earned(account);userRewardPerTokenPaid[account] = rewardPerTokenStored;}_;
}

主要逻辑就是更新几个字段,包括 rewardPerTokenStored、lastUpdateTime 和用户的奖励相关的 rewards[account] 和 userRewardPerTokenPaid[account]。

其中,还调用到其他三个函数:rewardPerToken()、lastTimeRewardApplicable()、earned(account)。先来看看这三个函数的实现。最简单的就是 lastTimeRewardApplicable:

function lastTimeRewardApplicable() public view returns (uint256) {return Math.min(block.timestamp, periodFinish);
}

其逻辑就是从当前区块时间挖矿结束时间两者中返回最小值。因此,当挖矿未结束时返回的就是当前区块时间,而挖矿结束后则返回挖矿结束时间。也因此,挖矿结束后,lastUpdateTime 也会一直等于挖矿结束时间,这点很关键。

rewardPerToken 函数则是获取每单位质押代币的奖励数量,其实现代码如下:

function rewardPerToken() public view returns (uint256) {if (_totalSupply == 0) {return rewardPerTokenStored;}returnrewardPerTokenStored.add(lastTimeRewardApplicable().sub(lastUpdateTime).mul(rewardRate).mul(1e18).div(_totalSupply));
}

这其实就是用累加计算的方式存储到 rewardPerTokenStored 变量中。当挖矿结束后,则不会再产生增量,rewardPerTokenStored 就不会再增加了。

earned 函数则是计算用户当前的挖矿奖励,代码实现也只有一行代码:

function earned(address account) public view returns (uint256) {return _balances[account].mul(rewardPerToken().sub(userRewardPerTokenPaid[account])).div(1e18).add(rewards[account]);
}

其逻辑也是计算出增量的每单位质押代币的挖矿奖励,再乘以用户的质押余额得到增量的总挖矿奖励,再加上之前已存储的挖矿奖励,就得到当前总的挖矿奖励。

至此,StakingRewards 合约的主要实现逻辑也都讲解完了。

总结

至此,所有 UniswapV2 的合约项目就都讲解完了。虽然分为了好几个小项目,但从架构设计上来说,能够大大减低不同模块之间的耦合性,不同项目也可以由不同的小团队单独维护,而且项目小而简单,那出 BUG 的概率也会更低。所以,这样的架构设计其实更适合 Dapp。


http://chatgpt.dhexx.cn/article/PwwkcUD7.shtml

相关文章

量化交易中VWAP/TWAP算法的基本原理和简单源码实现(C++和python)

参考文献&#xff1a; https://en.wikipedia.org/wiki/Time-weighted_average_price https://en.wikipedia.org/wiki/Volume-weighted_average_price http://blog.sina.com.cn/s/blog_163a2b9700102wdy0.html https://www.douban.com/note/214362575 算法交易其实主要是用在…

用高频交易的方式对TWAP算法优化(TWAP算法、算法实现、高频交易、冰山算法)

用高频交易的方式对TWAP算法进行优化。 以下内容主要基于执行算法的讨论。 什么是TWAP算法&#xff08;时间加权平均价格&#xff09;&#xff1f; TWAP交易时间加权平均价格Time Weighted Average Price 模型是把一个母单的数量平均地分配到一个交易时段上。该模型将交易时…

算法交易简介以及TWAP、VWAP算法原理

算法交易视频&#xff1a;算法交易视频 1&#xff0c;交易成本&#xff1a; 交易成本分成两类&#xff0c;一类是显性成本&#xff0c;包括佣金&#xff08;包括券商佣金&#xff08;券商收取&#xff09;&#xff0c;交易经手费&#xff08;交易所收取&#xff0c;千分之0.0…

时间加权平均价格算法(TWAP)和成交量平均算法(VWAP)在量化回测的应用

为什么要引入TWAP和 VWAP&#xff1f; 为了评估策略的资金容量&#xff0c;我们对M.trade模块里买入点和卖出点这两个参数进行了更丰富的扩展&#xff0c;支持了策略能够按更丰富的算法交易价格&#xff08;WAP&#xff09;进行撮合。 如果资金是10万的话&#xff0c;那么在开…

平均价格算法:TWAP vs. VWAP

时间加权平均价格 (TWAP) 和成交量加权平均价格 (VWAP) 算法应用不同的方法来计算资产价格&#xff0c;这是所有去中心化金融 (DeFi) 原语的组成部分。 在本文中&#xff0c;我们介绍了 TWAP 和 VWAP 算法之间的差异&#xff0c;解释了它们如何在区块链环境中为资产定价&#…

json对象中的数组怎么转化为json字符串

注意&#xff1a;去掉数组下标&#xff0c;才有效&#xff1f; JSON是一种数据交换格式&#xff0c;与XML数据格式相比更加方便使用&#xff0c;互联网中的URL请求接口大部分都是已JSON数据格式进行交互&#xff0c;对接数据解析也是非常方便容易&#xff0c;在字符串与JSON对…

json对象转对象数组

对象的两种取值方式 let obj {name: wan}; console.log(obj.name); //wan这是最普通的一种方式&#xff0c;还有一种方式我们用的不太多&#xff0c;就是使用[]包住属性名取值 let obj {name: wan}; console.log(obj[name]); //wan将对象转化为数组 let obj {未完成:5, 已…

JSON对象/数组与JSON字符串之间的相互转换

文章目录 前言JSON介绍如何判断JS数据类型JSON数组转化为JSON字符串JSON对象转化为JSON字符串JSON字符串转化为JSON数组/对象注意点 前言 这里先介绍一个个人觉得很好用的谷歌浏览器的功能&#xff1a;snippet 就是类似收藏夹&#xff0c;平常你可能会用到的调试有关的东西都可…

json转数组(json数组对象)

哪位知道json格式怎么转换为word&#xff1f;哪位知道json格式 使用office工具进行转换 jquery如何读取并显示JSON数组 1、新建一个html文件&#xff0c;命名为test。html&#xff0c;用于讲解ajax怎么获取json数据并输出。 2、在test。html中&#xff0c;使用script标签加载j…

将数组转换为JSON数据

如何将数组转换为JSON数据&#xff1f;下面本篇就来给大家介绍一下将数组转换为JSON对象的方法&#xff0c;希望对大家有所帮助。 方法一&#xff1a;使用Object.assign() Object.assign()方法将枚举的所有属性的值从源对象&#xff08;一个或多个&#xff09;复制到目标对象 …

js中json对象转换为array

今天遇到了一个将json对象格式转换为js数组的问题&#xff0c;网上down了一下,发现只是对json进行了一些操作。在此写了一个小小的代码&#xff0c;以供有需要的人使用。 开门见山&#xff0c;先声明一个json对象数组&#xff0c;在此说一下&#xff0c;js中数组使用 " [ …

java json对象和json数组对象的处理

在开发过程中&#xff0c;经常需要和别的系统交换数据&#xff0c;数据交换的格式有XML、JSON等&#xff0c;JSON作为一个轻量级的数据格式比xml效率要高&#xff0c;XML需要很多的标签&#xff0c;这无疑占据了网络流量&#xff0c;JSON在这方面则做的很好&#xff0c;下面先看…

数组转JSON json对象 json字符串

入参格式JSON对象与JSON字符串的区别 1、Object.assign() Object.assign() 方法将所有可枚举&#xff08;Object.propertyIsEnumerable() 返回 true&#xff09;的自有&#xff08;Object.hasOwnProperty() 返回 true&#xff09;属性从一个或多个源对象复制到目标对象&#…

js 数组转json,json转数组

//数组转json串 var arr [1,2,3, { a : 1 } ]; JSON.stringify( arr );//json字符串转数组 var jsonStr [1,2,3,{"a":1}]; JSON.parse( jsonStr );

Java利用fastjson解析复杂嵌套json字符串、json数组;json字符串转Java对象,json数组转list数组

文章目录 前言一、什么是JSON对象&#xff1f;二、什么是json字符串&#xff1f;二、什么是JSON数组&#xff1f;三、复杂、嵌套的json字符串四、json字符串转换4.1 简单json字符串转换为java对象4.2 简单json字符串数组转换为list数组4.3 复杂嵌套json字符串数组转换为Java对象…

将嵌套的json对象转化为json数组(python列表推导式)

需求说明 想将一个json文件录入MongoDB&#xff0c;如下图所示。 但是原文件是嵌套json对象的形式&#xff0c;如果直接导入MongoDB&#xff0c;会整体变成一个Document&#xff1a; 注&#xff1a;MongoDB中的Document相当于SQL中Row的概念。 而我希望一个Document对应一个…

Oracle 基础总结:日期函数专题

Oracle 基础总结&#xff1a;日期函数专题 日期函数:1、SYSDATE2、日期函数&#xff1a;(1&#xff09; MONTHS_BETWEEN&#xff08;日期1&#xff0c;日期2&#xff09;&#xff1a;返回两个日期相差的月数。(2&#xff09; ADD_MONTHS&#xff08;日期&#xff0c;数值&#…

numtodsinterval mysql用法_Oracle函数 - 日期函数详解

Oracle中的时间类型只有date和TIMESTAMP&#xff0c;TIMESTAMP是比date更精确的类型。日期时间函数用于处理时间类型的数据&#xff0c;Oracle以7位数字格式来存放日期数据&#xff0c;包括世纪、年、月、日、小时、分钟、秒&#xff0c;并且默认日期显式格式为“DD-MON-YY”。…

常用Oracle日期函数及聚合函数

1、常用Oracle日期函数 &#xff08;1&#xff09;CURRENT_DATE CURRENT_DATE是一个SQL标准函数&#xff0c;返回当前日期&#xff08;不带时间&#xff09;&#xff0c;可以在SELECT语句中使用。例如&#xff1a; 1 SELECT CURRENT_DATE FROM DUAL; 返回当前日期&#xff…

Oracle数据库之日期函数

今天给大家介绍一下oracle数据中的日期函数的用法。废话不多说&#xff0c;我们看一下oracle给我们提供了那些函数&#xff1f; 1.sysdate   用途&#xff1a;获取当前系统时间。 2.to_date(字符类型,日期类型) 用途&#xff1a;将字符类型转化成日期类型的函数&#xff0c;…