【问题标题】:How do I get the latest record for each item in CosmosDB using SQL如何使用 SQL 获取 CosmosDB 中每个项目的最新记录
【发布时间】:2020-11-06 09:38:23
【问题描述】:

我有一个类似于

的架构
"id": "uuid",
"deviceId": "uuid",
"message": {
    "content": "string",
    "ts": 1
},
"data": {
    "temperature": 21
}

我想获取每个“deviceId”的最新“数据”(使用 message.ts 作为时间戳)。

到目前为止,我已经设法使用查询按时间戳顺序取回数据 SELECT c.deviceId, c.message.ts, c.data FROM c ORDER BY c.message.ts DESC 但我不知道如何删除重复的设备记录。

这可以在 CosmosDB SQL 引擎中完成吗?

【问题讨论】:

  • 在您的选择中使用前 1 个
  • 只检索一条记录 - 我基本上需要它,但它要按“deviceId”分组
  • 目前不支持基于不同的分组。今天这样做的方法是使用更改提要实现物化视图。
  • @MarkBrown - 谢谢。这看起来正是我所追求的!我已经创建了一个答案并记入了你的功劳。

标签: sql azure-cosmosdb azure-cosmosdb-sqlapi


【解决方案1】:

您可以采取的另一种方法是在 CosmosDb 中使用触发器函数。这样做的好处是您不需要部署 Azure 函数,只需使用 SQL 即可获取最新项目。例如,当你得到一个新的项目时,你可以使用预触发设置一个字段如下:latest = true,同时将上一个最近的项目的最新字段更改为false。然后,您的 SQL 查询只需要 WHERE latest = true 即可返回每个项目的最新记录。 下面是一个大体思路的触发函数:

function setLatest() {  
    var context = getContext();  
    var request = context.getRequest();  
  
    // item to be created in the current operation  
    var itemToCreate = request.getBody();  
  
    // validate properties  
    if (!("latest" in itemToCreate)) {
        itemToCreate["latest"] = true;  
    }
    // update the old latest to false
    removeCurrentLatest(itemToCreate["id"],..., );
    // save the newer item that will be created  
    request.setBody(itemToCreate);
}

function removeCurrentLatest(id, ...) {
    var collection = getContext().getCollection();
    var collectionLink = collection.getSelfLink();
        // Get the document. We keep it in the same collection.
        var isAccepted = collection.queryDocuments
            (collectionLink, `SELECT * FROM root r WHERE r.id = "${id}" AND .... AND r.latest = true`,
            function (err, feed, options) {
            if (err) throw err;
            if (feed && feed[0] != null) 
            {
                var oldDoc = feed[0];
                oldDoc.latest = false;
                var isAccepted = collection.replaceDocument(oldDoc._self, oldDoc, function (err) {
                    if (err) throw err;
                    });
                if (!isAccepted) throw new Error("The call replaceDocument(oldDoc) returned false.");
            }
        });
        if (!isAccepted) throw new Error("The call queryDocuments for oldDoc returned false.");
    }

我已经删除了一些您可能希望包含的其他条件,以确保您选择正确的项目先前版本 - 希望如何为您的特定资产添加这些条件应该很明显。 这是一篇关于在 CosmosDB 中使用触发器的优秀文章:http://www.johndowns.co.nz/blog/2018/1/30/cosmos-db-server-side-programming-with-typescript-part-4-triggers

【讨论】:

    【解决方案2】:

    现在用一条 SQL 是不可能做到这一点的。

    这可能是一个替代方案。

    首先,运行这条 SQL SELECT c.deviceId,max(c.message.ts) as lastest FROM c group by c.deviceId

    那么,你就可以通过这条SQL获取数据了,SELECT * FROM c WHERE c.deviceId = 'xxx' AND c.message.ts = xxxx

    【讨论】:

    • 感谢您的回答,但这不是我想要的方法。如果您有数百台设备,那么这对于“快速总结”来说是无法扩展的。它在技术上确实有效,所以无论如何感谢您的回复。
    【解决方案3】:

    感谢Mark Brown 的评论,我发现以下似乎是解决此问题的正确方法。不像一次性使用一些 SQL 那样优雅,但确实是需要的。

    https://docs.microsoft.com/en-us/samples/azure-samples/cosmosdb-materialized-views/real-time-view-cosomos-azure-functions/

    本质上,您创建了一个无服务器函数,它由 Cosmos 更改提要触发并更新一个物化视图,它本质上只是一个文档,其中(在这种情况下)具有最新的 datadeviceId

    特别是对于这种情况,它很可能会使用其最新数据更新相应的device 文档。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2016-05-15
      • 2021-01-10
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多