30

我正在尝试为从数据集中选择的每一行生成上午 8:00 到晚上 8:00 之间的随机时间,但是,我总是为每一行得到相同的随机值——我希望它不同每一行

表架构和数据:

╔══════╦════════════════╗
║  ID  ║  CREATED_DATE  ║
╠══════╬════════════════╣
║ ID/1 ║   26/04/2014   ║
║ ID/2 ║   26/04/2014   ║
║ ID/3 ║   26/04/2014   ║
║ ID/4 ║   26/04/2014   ║
║ ID/5 ║   26/04/2014   ║
╚══════╩════════════════╝

当前的 SQL 语句:

SELECT [ID]
     , MyFunction.dbo.AddWorkDays(14, [CREATED_DATE]) AS [New Date]
     , CONVERT(VARCHAR, DATEADD(MILLISECOND, CAST(43200000 * RAND() AS INT), CONVERT(TIME, '08:00')), 114) AS [New Time]
FROM [RandomTable]

当前结果(列中每一行的时间相同[New Time]):

╔══════╦════════════════╦════════════════╗
║  ID  ║    New Date    ║    New Time    ║
╠══════╬════════════════╬════════════════╣
║ ID/1 ║   10/05/2014   ║    09:41:43    ║
║ ID/2 ║   10/05/2014   ║    09:41:43    ║
║ ID/3 ║   10/05/2014   ║    09:41:43    ║
║ ID/4 ║   10/05/2014   ║    09:41:43    ║
║ ID/5 ║   10/05/2014   ║    09:41:43    ║
╚══════╩════════════════╩════════════════╝

期望的结果(列中每一行的不同时间[New Time]):

╔══════╦════════════════╦════════════════╗
║  ID  ║    New Date    ║    New Time    ║
╠══════╬════════════════╬════════════════╣
║ ID/1 ║   10/05/2014   ║    09:41:43    ║
║ ID/2 ║   10/05/2014   ║    15:05:23    ║
║ ID/3 ║   10/05/2014   ║    10:01:05    ║
║ ID/4 ║   10/05/2014   ║    19:32:45    ║
║ ID/5 ║   10/05/2014   ║    08:43:15    ║
╚══════╩════════════════╩════════════════╝

有想法该怎么解决这个吗?以上所有只是示例数据——我的真实表有大约 2800 条记录(不确定这是否会对任何人的建议产生影响)。

4

8 回答 8

28

OP在使用 just 时遇到的问题rand()是由于它对每个查询进行一次评估。

文档中:

如果未指定种子,SQL Server 数据库引擎会随机分配一个种子值。对于指定的种子值,返回的结果始终相同。

下面描述的方法消除了优化并抑制了这种行为,因此每行rand()评估一次:

dateadd( second
       , rand(cast(newid() as varbinary)) * 43200
       , cast('08:00:00' as time) )
  • newid()生成类型的唯一值uniqueidentifier
  • 该值被转换cast为用作函数中的种子以生成从0 到 1rand([seed])的伪随机float值,并且由于种子始终是唯一的,因此返回值也是唯一的。

SQLFiddle

于 2014-04-26T17:15:43.367 回答
15

您也可以使用:

SELECT DATEADD(s, ABS(CHECKSUM(NewId()) % 43201), CAST('08:00:00' AS Time))

ABS(CHECKSUM(NewId()) % 43201)生成一个介于0和之间的 随机数43200。请参阅此处的讨论

SQL小提琴

MS SQL Server 2008 架构设置

查询 1

SELECT DATEADD(s, ABS(CHECKSUM(NewId()) % 43201), CAST('08:00:00' AS Time)) AS [RandomTime]
FROM 
( VALUES (1), (2), (3), (4), (5)
) Y(A)
CROSS JOIN
( VALUES (1), (2), (3), (4), (5)
) Z(A)

结果

|    RANDOMTIME    |
|------------------|
| 16:51:58.0000000 |
| 10:42:44.0000000 |
| 14:01:38.0000000 |
| 13:33:51.0000000 |
| 18:00:51.0000000 |
| 11:29:03.0000000 |
| 10:21:14.0000000 |
| 16:38:27.0000000 |
| 09:55:37.0000000 |
| 13:21:13.0000000 |
| 11:29:37.0000000 |
| 10:57:49.0000000 |
| 14:56:42.0000000 |
| 15:33:11.0000000 |
| 18:49:45.0000000 |
| 16:23:28.0000000 |
| 09:00:05.0000000 |
| 09:20:01.0000000 |
| 11:26:23.0000000 |
| 15:26:23.0000000 |
| 10:38:44.0000000 |
| 11:46:30.0000000 |
| 16:00:59.0000000 |
| 09:29:18.0000000 |
| 09:09:19.0000000 |
于 2015-01-05T13:57:01.220 回答
14

原始问题的解释:

问题指出:

  • 在上午 8:00 到晚上 8:00 之间生成一个随机时间(即 12 小时窗口)
  • 每行应该是不同的(即在所有行中都是唯一的)
  • 真实表有大约 2800 条记录

现在考虑以下几点:

  • 示例数据仅显示一个日期
  • 24 小时有 86,400 秒,因此 12 小时有 43,200 秒

以下领域存在一些歧义:

  • 在“每一行都不同”的上下文中,究竟什么是随机的,因为不能保证真正的随机值对于每一行都是不同的。事实上,理论上,真正的随机数对于每一行可能都是相同的。那么是强调“随机”还是“不同”?还是我们真的在谈论不同但不是按顺序排列的(为了呈现随机性而不是实际上是随机的)?
  • 如果有超过 2800 行怎么办?如果有 100 万行怎么办?
  • 如果可以有超过 43,200 行,如何处理“每行不同”(因为不可能在所有行中都具有唯一性)?
  • 日期会有所不同吗?如果是这样,我们真的在谈论“每个日期的每一行都不同”吗?
  • 如果“每个日期的每一行都不同”:
    • 每个日期的时间可以遵循相同的非顺序模式吗?或者每个日期的模式是否需要不同?
    • 任何特定日期都会有超过 43,200 行吗?如果是这样,则每组 43,200 行的时间只能是唯一的。

鉴于上述信息,有几种方法可以解释请求:

  1. 强调“随机”:日期和行数无关紧要。使用其他答案中显示的三种方法之一 生成非常有可能但不能保证是唯一的真正随机时间:
    • @notulysses:RAND(CAST(NEWID() AS VARBINARY)) * 43200
    • @史蒂夫福特:ABS(CHECKSUM(NewId()) % 43201)
    • @弗拉基米尔·巴拉诺夫:CAST(43200000 * (CAST(CRYPT_GEN_RANDOM(4) as int) / 4294967295.0 + 0.5) as int)
  2. 强调“每行不同”,总是 <= 43,200 行:如果行数从不超过可用秒数,则很容易保证所有行的唯一时间,无论日期相同或不同,并且看起来是随机排序。
  3. 强调“每行不同”,可能 > 43,200 行:如果行数可以超过可用秒数,则无法保证所有行的唯一性,但仍然可以保证跨行的唯一性任何特定日期的行,前提是没有特定日期的行数 > 43,200。

因此,我的回答基于以下想法:

  • 即使 OP 的行数从不超过 2800,大多数遇到类似随机性需求的其他人更有可能使用更大的数据集(即,对于任何数字,很容易有 100 万行日期:1、5000 等)
  • 样本数据在对所有 5 行使用相同的日期时过于简单化,或者即使在此特定情况下所有行的日期都相同,在大多数其他情况下不太可能发生
  • 唯一性优于随机性
  • 如果每个日期的“看似随机”的秒数排序存在模式,则至少应该在日期间的序列开始处有一个不同的偏移量(当日期按顺序排序时),以呈现随机性在任何一小组日期之间。

回答:

如果情况需要唯一的时间,那么任何生成真正随机值的方法都无法保证。我真的很喜欢CRYPT_GEN_RANDOM@Vladimir Baranov 的使用,但几乎不可能生成一组独特的值:

DECLARE @Table TABLE (Col1 BIGINT NOT NULL UNIQUE);

INSERT INTO @Table (Col1)
    SELECT CONVERT(BIGINT, CRYPT_GEN_RANDOM(4))
    FROM [master].sys.objects so
    CROSS JOIN [master].sys.objects so2
    CROSS JOIN [master].sys.objects so3;
    -- 753,571 rows

将随机值增加到 8 个字节似乎确实有效:

DECLARE @Table TABLE (Col1 BIGINT NOT NULL UNIQUE);

INSERT INTO @Table (Col1)
    SELECT CONVERT(BIGINT, CRYPT_GEN_RANDOM(8))
    FROM [master].sys.objects so
    CROSS JOIN [master].sys.objects so2
    CROSS JOIN [master].sys.objects so3;
    -- 753,571 rows

当然,如果我们生成到第二个,那么只有 86,400 个。缩小范围似乎有所帮助,因为以下方法有时会起作用:

DECLARE @Table TABLE (Col1 BIGINT NOT NULL UNIQUE);

INSERT INTO @Table (Col1)
    SELECT TOP (86400) CONVERT(BIGINT, CRYPT_GEN_RANDOM(4))
    FROM [master].sys.objects so
    CROSS JOIN [master].sys.objects so2
    CROSS JOIN [master].sys.objects so3;

但是,如果每天都需要唯一性,事情就会变得有点棘手(这似乎是这类项目的合理要求,而不是在所有日子里都是唯一的)。但是随机数生成器不会知道在每个新的一天重置。

如果仅具有随机外观是可以接受的,那么我们可以保证每个日期的唯一性,而无需:

  • 循环/游标结构
  • 将已使用的值保存在表中
  • 使用RAND(), NEWID(), 或CRYPT_GEN_RANDOM()

以下解决方案使用了我在这个答案中了解到的模乘逆(MMI)的概念:在 SQL Server 中生成看似随机的唯一数字 ID。当然,这个问题没有像我们这里那样严格定义的值范围,每天只有 86,400 个值。因此,我使用了 86400 的范围(作为“模数”)并在在线计算器中尝试了一些“互质”值(作为“整数”)来获得他们的 MMI:

  • 13 (MMI = 39877)
  • 37 (MMI = 51373)
  • 59 (MMI = 39539)

ROW_NUMBER()在 CTE 中使用,分区(即分组)CREATED_DATE作为为一天中的每一秒分配一个值的方法。

但是,虽然按顺序为 0、1、2、... 等秒生成的值看起来是随机的,但在不同的日子里,该特定秒将映射到相同的值。因此,第二个 CTE(名为“WhichSecond”)通过将日期转换为 INT(将日期转换为从 1900-01-01 开始的顺序偏移量)然后乘以 101 来移动每个日期的起点。

DECLARE @Data TABLE
(
  ID INT NOT NULL IDENTITY(1, 1),
  CREATED_DATE DATE NOT NULL
);

INSERT INTO @Data (CREATED_DATE) VALUES ('2014-10-05');
INSERT INTO @Data (CREATED_DATE) VALUES ('2014-10-05');
INSERT INTO @Data (CREATED_DATE) VALUES ('2014-10-05');
INSERT INTO @Data (CREATED_DATE) VALUES ('2014-10-05');
INSERT INTO @Data (CREATED_DATE) VALUES ('2014-10-05');
INSERT INTO @Data (CREATED_DATE) VALUES ('2015-03-15');
INSERT INTO @Data (CREATED_DATE) VALUES ('2016-10-22');
INSERT INTO @Data (CREATED_DATE) VALUES ('2015-03-15');

;WITH cte AS
(
  SELECT tmp.ID,
         CONVERT(DATETIME, tmp.CREATED_DATE) AS [CREATED_DATE],
         ROW_NUMBER() OVER (PARTITION BY tmp.CREATED_DATE ORDER BY (SELECT NULL))
                      AS [RowNum]
  FROM   @Data tmp
), WhichSecond AS
(
  SELECT cte.ID,
         cte.CREATED_DATE,
         ((CONVERT(INT, cte.[CREATED_DATE]) - 29219) * 101) + cte.[RowNum]
                      AS [ThisSecond]
  FROM   cte
)
SELECT parts.*,
       (parts.ThisSecond % 86400) AS [NormalizedSecond], -- wrap around to 0 when
                                                         -- value goes above 86,400
       ((parts.ThisSecond % 86400) * 39539) % 86400 AS [ActualSecond],
       DATEADD(
                 SECOND,
                 (((parts.ThisSecond % 86400) * 39539) % 86400),
                 parts.CREATED_DATE
              ) AS [DateWithUniqueTime]
FROM WhichSecond parts
ORDER BY parts.ID;

回报:

ID  CREATED_DATE  ThisSecond  NormalizedSecond  ActualSecond  DateWithUniqueTime
1   2014-10-05    1282297     72697             11483         2014-10-05 03:11:23.000
2   2014-10-05    1282298     72698             51022         2014-10-05 14:10:22.000
3   2014-10-05    1282299     72699              4161         2014-10-05 01:09:21.000
4   2014-10-05    1282300     72700             43700         2014-10-05 12:08:20.000
5   2014-10-05    1282301     72701             83239         2014-10-05 23:07:19.000
6   2015-03-15    1298558      2558             52762         2015-03-15 14:39:22.000
7   2016-10-22    1357845     61845             83055         2016-10-22 23:04:15.000
8   2015-03-15    1298559      2559              5901         2015-03-15 01:38:21.000

如果我们只想生成上午 8:00 到晚上 8:00 之间的时间,我们只需要做一些小的调整:

  1. 将范围(作为“模数”)从 86400 更改为它的一半:43200
  2. 重新计算 MMI(可以使用与“整数”相同的“互质”值):39539(与之前相同)
  3. 添加28800DATEADD作为 8 小时偏移量的第二个参数

结果将是仅更改一行(因为其他行是诊断性的):

-- second parameter of the DATEADD() call
28800 + (((parts.ThisSecond % 43200) * 39539) % 43200)

另一种以不太可预测的方式改变每一天的方法是通过传递“WhichSecond”CTERAND()中的 INT 形式来使用。CREATED_DATE这将为每个日期提供一个稳定的偏移量,因为对于传入的相同值RAND(x)将返回相同的值,但对于传入的不同值将返回不同的值。含义:yxyx

兰德(1) = y1
兰德(2) = y2
兰德(3) = y3
兰德(2) = y2

第二次RAND(2)调用时,它仍然返回与y2第一次调用时相同的值。

因此,“WhichSecond”CTE 可能是:

(
  SELECT cte.ID,
         cte.CREATED_DATE,
         (RAND(CONVERT(INT, cte.[CREATED_DATE])) * {some number}) + cte.[RowNum]
                      AS [ThisSecond]
  FROM   cte
)
于 2015-01-07T18:22:54.867 回答
7

有几种方法:

  • 预先生成一个带有随机数的表格,并在需要时使用它。或者从一些有信誉的来源获取这些数据。
  • 使用NEWID函数为RAND. 应谨慎使用,因为不能保证 NEWID 值的分布。使其或多或少均匀分布的最佳方法之一是通过CHECKSUM: RAND(CHECKSUM(NEWID()))。这种方法的好处是 NEWID 函数从 SQL Server 2000 开始可用。
  • 而不是NEWID使用某些列的 MD5 作为 : 的种子,RAND或者RAND(CHECKSUM(HASHBYTES('MD5', CAST(SomeID AS varbinary(4)))))只是行号:RAND(CHECKSUM(HASHBYTES('MD5', CAST(ROW_NUMBER() OVER(ORDER BY ...) AS varbinary(4)))))。此方法至少从 SQL Server 2005 开始可用。与NEWID方法的主要区别在于您可以完全控制随机序列。您无法控制NEWID返回的内容,也无法再次从相同的数字重新开始随机序列。如果您提供相同的行号集,例如,PARTITION BY您将获得相同的随机数集。当您需要多次使用相同的随机数序列时,它可能很有用。有可能为两个不同的种子获得相同的随机数。我测试了从 1 到 1,000,000 的行号。MD5他们都是不同的。CHECKSUM结果是MD5122 次碰撞。RANDCHECKSUM导致 246 次碰撞。当使用从 1 到 100,000 的行号进行测试时,CHECKSUM发生 1 次碰撞,RAND发生 3 次碰撞。
  • 另一种可能性是在 T-SQL 中简单地实现您自己的用户定义函数,该函数使用您喜欢的算法生成一个随机数。在这种情况下,您可以完全控制一切。通常伪随机生成器必须在调用之间存储其内部状态,因此您最终可能会拥有一个存储此数据的专用表。
  • 您可以使用 CLR 编写用户定义的函数。在这种情况下,您可以实现自己的生成器,或使用 .NET 内置的函数,如RandomclassRNGCryptoServiceProviderclass
  • 最后,从 SQL Server 2008 开始有一个内置函数CRYPT_GEN_RANDOM

我会详细描述最后一种方法,因为我认为它对于 SQL Server 2008 及以上版本是一个非常好的解决方案。CRYPT_GEN_RANDOM为结果集的每一行调用RAND,而 只调用一次。

CRYPT_GEN_RANDOM (Transact-SQL)

返回由 Crypto API (CAPI) 生成的加密随机数。输出是指定字节数的十六进制数。

此外,CRYPT_GEN_RANDOM应该提供比RAND. 在分布和加密强度方面更好。例子:

(CAST(CRYPT_GEN_RANDOM(4) as int) / 4294967295.0 + 0.5)

这会生成 4 个随机字节作为varbinary. 我们必须明确地将它们转换为int第一个。然后将结果转换为 0 到 1 之间的浮点数。

所以,原来的查询是这样的:

SELECT ID AS [ID]
     , MyFunction.dbo.AddWorkDays(14, S.CREATED_DATE) AS [New Date]
     , CONVERT(VARCHAR, DATEADD(MILLISECOND, 
     CAST(43200000 * (CAST(CRYPT_GEN_RANDOM(4) as int) / 4294967295.0 + 0.5) as int),
     CONVERT(TIME, '08:00')), 114) AS [New Time]
FROM RandomTable

这是一个易于复制粘贴和尝试的独立示例(我使用了来自@Steve Ford 的另一个答案的查询):

SELECT DATEADD(millisecond, 
    CAST(43200000 * (CAST(CRYPT_GEN_RANDOM(4) as int) / 4294967295.0 + 0.5) as int), 
    CAST('08:00:00' AS Time)) AS [RandomTime]
FROM 
    ( VALUES (1), (2), (3), (4), (5)
    ) Y(A)
    CROSS JOIN
    ( VALUES (1), (2), (3), (4), (5)
    ) Z(A)

这是结果:

RandomTime
10:58:24.7200000
19:40:06.7220000
11:04:29.0530000
08:57:31.6130000
15:03:14.9470000
09:15:34.9380000
13:46:43.1250000
11:27:00.8940000
14:42:23.6100000
15:07:56.2120000
11:39:09.8830000
08:16:44.3960000
14:23:38.4820000
17:28:31.7440000
16:29:31.4320000
09:09:15.0210000
12:31:09.8370000
11:23:09.8430000
15:35:45.5480000
17:42:49.3390000
08:07:05.4930000
18:17:16.2980000
11:49:08.2010000
10:20:21.7620000
15:56:58.6110000

添加

当我阅读原始问题时,我认为没有必要确保所有生成的随机数都是唯一的。我将问题中的“不同”一词解释为与在使用简单SELECT RAND(). 我认为在许多情况下,碰撞随机数是否很少并不重要。在许多情况下,这实际上是正确的行为。

所以,我的理解是,当需要一个唯一的随机数序列时,它在某种意义上相当于下面的任务。我们有一组一些值/行,例如,一组唯一 ID 或一天中的所有 86400 秒或给定一天的 2800 行。我们想打乱这些值/行。我们想以随机顺序重新排列这些行。

要打乱给定的一组行,我们只需要ORDER BY随机数(这些随机数在这里可能有合理数量的冲突)。随机数可以通过任何方法生成。像这样的东西:

ROW_NUMBER() OVER ([optional PARTITION BY ...] ORDER BY CRYPT_GEN_RANDOM(4)) 

或字面意思

SELECT ...
FROM ...
ORDER BY CRYPT_GEN_RANDOM(4)

取决于它的使用地点和方式。

于 2015-01-06T06:10:51.950 回答
3

测试这个:

 Declare @t table(ID int,CREATED_DATE datetime)
insert into @t values
 (1 ,  '04/26/2014'),
 (2 ,  '04/26/2014'),
 (3 ,  '04/26/2014'),
 (4 ,  '04/26/2014')

 ;WITH CTE AS
 (
   SELECT *,CONVERT(VARCHAR, DATEADD(SECOND, RAND(CAST(NEWID() AS VARBINARY)) * 43200, 
   CAST('08:00:00' AS TIME)),114) AS [New Time] FROM @t WHERE ID=1
   UNION ALL
   SELECT *,CONVERT(VARCHAR, DATEADD(SECOND, RAND(CAST(NEWID() AS VARBINARY)) * 43200, 
   CAST('08:00:00' AS TIME)), 114)  FROM @t WHERE ID>1 AND ID<=5
 )
 SELECT * FROM CTE
于 2015-01-06T08:40:20.997 回答
2

这是另一个选项,可让您更好地控制时间的生成方式。您可以指定随机时间之间的间隔。它也没有使用该RAND功能。

DECLARE @StartTime  VARCHAR(10) = '08:00',
        @EndTime    VARCHAR(10) = '20:00',
        @Interval   INT = 5 --(In Seconds)

WITH times AS(
    SELECT CONVERT(TIME, @StartTime) AS t
    UNION ALL
    SELECT DATEADD(SECOND, @Interval, t)
    FROM times
    WHERE t < @EndTime
)

SELECT *, 
(SELECT TOP 1 t FROM times WHERE d.Id > 0 ORDER BY NEWID())
FROM #data d
option (maxrecursion 0)

附带说明:
如果您删除上述WHERE子查询中的子句 ( WHERE d.Id > 0),则所有行都返回相同的时间值,即与您开始时相同的问题

于 2015-01-10T22:24:19.613 回答
0

全部,

我想我会分享我的问题的答案。我不记得我在哪里找到了详细信息——我认为是通过 sgeddes 提供的链接之一。

我使用以下查询来获取上午 8 点到晚上 7:55 之间的随机时间(大约)

SELECT convert(varchar,CONVERT(varchar, DATEADD(ms, dbo.MyRand(335 ,830) * 86400, 0), 114),114)

MyRand 函数如下:

SET ANSI_NULLS ON;
GO
SET QUOTED_IDENTIFIER ON;
GO
CREATE FUNCTION dbo.myRand(@Min INT, @Max INT) RETURNS decimal(18,15) AS
BEGIN
DECLARE @BinaryFloat BINARY(8)
SELECT @BinaryFloat = CAST(Id AS BINARY) FROM vwGuid

DECLARE
@PartValue TINYINT,
@Mask TINYINT,
@Mantissa FLOAT,
@Exponent SMALLINT,
@Bit TINYINT,
@Ln2 FLOAT,
@BigValue BIGINT,
@RandomNumber FLOAT

SELECT
@Mantissa = 1,
@Bit = 1,
@Ln2 = LOG(2),
@BigValue = CAST(@BinaryFloat AS BIGINT),
@Exponent = (@BigValue & 0x7ff0000000000000) / EXP(52 * @Ln2)

WHILE @Part <= 8
BEGIN
SELECT
@PartValue = CAST(SUBSTRING(@BinaryFloat, @Part, 1) AS TINYINT),
@Mask =

WHILE @Mask > 0
BEGIN
IF @PartValue & @Mask > 0
SET @Mantissa = @Mantissa + EXP(-@Bit * @Ln2)

SELECT
@Mask = @Mask / 2
END
END

SET @RandomNumber = CASE @Exponent WHEN 0 THEN 0 ELSE CAST(@Exponent AS FLOAT) / 2047 END

RETURN CAST((@RandomNumber * (@Max - @Min)) + @Min AS DECIMAL(18,15))

END
GO
END

我希望这有帮助。我还没有阅读上面的许多回复,如果有人有更好的答案,我深表歉意 - 这就是我解决它的方法。

谢谢

于 2015-01-07T08:53:30.177 回答
0

获取给定范围内的随机时间:Sql Server

SELECT
    X.Value,
    RT.RandomTime,
    DateObject = CONVERT(SMALLDATETIME, CONVERT(DATE, GETDATE())) + CONVERT(SMALLDATETIME, RT.RandomTime)
 FROM (VALUES(101),(204),(77),(54),(75),(66)) X(Value)  /* YOUR TABLE */
 CROSS APPLY(SELECT FromTime = '08:20:00', ToTime = '08:33:00') FT
 CROSS APPLY(SELECT MaxSeconds = DATEDIFF(ss, FT.FromTime, FT.ToTime)) MS
 CROSS APPLY(SELECT RandomTime = CONVERT(TIME, DATEADD(SECOND, (MS.MaxSeconds + 1) * RAND(CONVERT(VARBINARY, NEWID() )) , FT.FromTime))) RT

在此处输入图像描述

于 2021-06-14T05:35:45.890 回答