使用 INTERVAL 查询日期范围

问题描述

我们使用 clickhouse 作为我们的数据库,并且有一个列类型为 DateTime(UTC) 的表。

我需要查询日期范围,例如这样的:

SELECT * 
FROM some_table 
WHERE date_time_column BETWEEN ? AND ?

我有参数 StartEnd,它们的类型为 time.Time,但客户要求附加选项:

基本上,他们想提供 Start一个偏移量。偏移量可以是天、小时、周或月。

由于我是新员工,之前从未与 clickhouse 合作过,所以我来这里寻求帮助。

解决问题的努力:

我调查了 clickhouse 文档并发现了 INTERVAL

它看起来很适合我的任务,因为我有这样的想法:

SELECT * 
FROM some_table 
WHERE date_time_column  BETWEEN ? AND ? + INTERVAL ? MONTH + INTERVAL ? WEEK + INTERVAL ? DAY + INTERVAL ? HOUR

这样我就可以对不需要的参数使用零,例如:

  • 如果我们需要 3 小时,我们会在伪代码中得到类似的东西:

      sqlx.db.Query(SELECT * FROM some_table WHERE date_time_column
                    BETWEEN ? AND ? + INTERVAL ? MONTH + INTERVAL ? WEEK + INTERVAL ? DAY + INTERVAL ? HOUR,Start,3)
    
  • 如果我们需要 3 小时 2 天,我们会在伪代码中得到类似的内容

      sqlx.db.Query(SELECT * FROM some_table WHERE date_time_column
                    BETWEEN ? AND ? + INTERVAL ? MONTH + INTERVAL ? WEEK + INTERVAL ? DAY + INTERVAL ? HOUR,2,3)
    

问题:

  • 我的想法是否高于可行的解决方案?
  • 如果不是,你能告诉我如何实现上述要求吗?

除了 clickhouse 数据库,我们还使用 Golang 和 sqlx 库与 clickhouse 通信。

解决方法

对我来说,你的决定看起来不错。

我会通过计算 sql-script 之外的 Stop-date 来简化它:

// controller
[HttpPost]
public IActionResult Save([FromForm] CompanyViewModel company,IFormFileCollection files)
{            
    // TODO           
    return Ok();
}

// startup.cs
services.AddControllers(options => {
    options.ModelBinderProviders.Insert(0,new FromFormModelBinderProvider());
});

public class FromFormModelBinderProvider : IModelBinderProvider
{
    public IModelBinder GetBinder(ModelBinderProviderContext context)
    {
        if (context == null) {
            throw new ArgumentNullException(nameof(context));
        }
        if (!context.Metadata.ModelType.IsPrimitive &&
            !context.Metadata.ModelType.Equals(typeof(string)) &&
            context.Metadata.BindingSource.Id == "Form") {
            return new FromFormModelBinder();
        }
        return null;
    }
}

public class FromFormModelBinder : IModelBinder
{
    public Task BindModelAsync(ModelBindingContext bindingContext)
    {
        if (bindingContext == null) throw new ArgumentNullException(nameof(bindingContext));

        // Fetch the value of the argument by name and set it to the model state
        string fieldName = bindingContext.FieldName;
        var valueProviderResult = bindingContext.ValueProvider.GetValue(fieldName);
        if (valueProviderResult == ValueProviderResult.None) return Task.CompletedTask;
        else bindingContext.ModelState.SetModelValue(fieldName,valueProviderResult);

        // Do nothing if the value is null or empty
        string value = valueProviderResult.FirstValue;
        if (string.IsNullOrEmpty(value)) return Task.CompletedTask;

        try {
            // Deserialize the provided value and set the binding result
            var options = bindingContext.ActionContext.HttpContext.RequestServices.GetRequiredService<IOptions<MvcNewtonsoftJsonOptions>>().Value;
            object result = JsonConvert.DeserializeObject(value,bindingContext.ModelType,options.SerializerSettings);
            bindingContext.Result = ModelBindingResult.Success(result);
        }
        catch (JsonException) {
            bindingContext.Result = ModelBindingResult.Failed();
        }
        return Task.CompletedTask;
    }
}
import (
    "time"
)

Start := ..
Stop := Start.AddDate(0,months,days + weeks*7).Add(time.Hour * time.Duration(hours))

这种方式看起来更易于维护(不需要 CH SQL 知识)和可测试(从单元测试的角度来看)。