Guid.NewGuid() 总是 return 所有行的 Guid 相同

Guid.NewGuid() always return same Guid for all rows

我从源转换的每一行都需要唯一的 GUID。
以下是示例脚本;代码 Guid.NewGuid() returns 所有行都相同

@Person =
    EXTRACT SourceId          int,
            AreaCode          string,
            AreaDetail         string,
            City        string
    FROM "/Staging/Person"
    USING Extractors.Tsv(nullEscape:"#NULL#");

@rs1 =
    SELECT 
    Guid.NewGuid() AS PersonId,
    AreaCode,
    AreaDetail,
    City    
    FROM @Person;

OUTPUT @rs1   
    TO "/Datamart/DimUser.tsv"
      USING Outputters.Tsv(quoting:false, dateTimeFormat:null);

对该问题的快速总结是,您不应尝试通过依赖于生成新 Guid 的技术或任何其他具有 are "time-based" 的方法来分配唯一值。这样做的原因是,U-SQL 中的行可能会重新计算 - 由于顶点重试、性能优化等。

在这些情况下,值将重新分配一个新值并最终导致错误,而 运行 U-SQL 脚本 - 因为 U-SQL 需要行对于输入数据是确定性的。

而不是分配新的 Guid,使用 ROW_NUMBER Window 函数可以安全地向行添加新的唯一编号。我

@result =
    SELECT 
        *,
        ROW_NUMBER() OVER () AS UID
    FROM @querylog;

请注意,U-SQL 是一种声明性语言,因此会将已知的非确定性函数(例如 Guid.NewGuid()DateTime.Now 快照为每个脚本的一个值。

虽然您可以通过将此类函数包装到 C# 函数中来解决这个问题,但这种做法是非常不鼓励的,因为您使脚本具有不确定性,如果执行中的节点必须重试并且不会产生可重复的结果!

那么如何才能提供唯一编号呢?

选项是:

  1. 如果可以更改数据生成,请添加外部数据中已有的值。
  2. Skolemization:编写一个确定性表达式,将关键属性组合成一个唯一值。
  3. 对您阅读的数据使用ROW_NUMBER() OVER ()。如果您已经拥有需要保证唯一性的数据,请根据您的要求添加作业时间 运行 的时间标记,或者获取现有的最高值,或者获取足够大的间隔时间。

这是一个示例,它使用时间标记加上 ROW_NUBER() 来确保每次您 运行 脚本时每一行的 ID 都是唯一的,因为如上所述,U-SQL 将在每次脚本调用时评估 DateTime.Now 一次:

@data =
SELECT *
FROM (VALUES
      ( "John", "Doe" ),
      ( "Paul", "Miller" ),
      ( "Tracy", "Smith" ),
      ( "Jane", "Doe")
     ) AS T(firstname, lastname);

@res = 
SELECT DateTime.Now.Ticks+ROW_NUMBER() OVER () AS id, 
       firstname, lastname
FROM @data;

OUTPUT @res
TO "/output/data.csv"
USING Outputters.Csv();

在代码隐藏中创建一个 udf:

namespace USQL_Namespace
{
    public static class Udfs
    {
        public static string newGuidString()
        {
            return Guid.NewGuid().ToString();
        }
    }

并内联引用它:

@o = 
    SELECT USQL_Namespace.Udfs.newGuidString() AS newId;