什么声明性语言擅长分析树状数据?

时间:2016-08-08 14:33:20

标签: sql programming-languages declarative

我正在考虑开发一个系统来对嵌套(但树状)数据执行高度并行查询。潜在用户是数据分析师(特别是物理学家),而不是程序员。对于用户界面,我想使用一种众所周知的查询语言来避免扩散新语言。

大多数数据的结构都是这样的(想象一下数十亿event结构的以下模式):

event: struct
  |
  +--- timestamp: bigint
  +--- missing energy: float
  +--- tracks: array of struct
  |      |
  |      +--- momentum: float
  |      +--- theta angle: float
  |      +--- hits: array of struct
  |             |
  |             +--- detector id: int
  |             +--- charge: float
  |             +--- time: float
  |             +--- ...
  +--- showers: array of struct
         |
         +--- ...

数据库是只读的,大多数查询都是:

  • 赛道的动量最大,其中theta在-2.4和2.4之间
  • 在动量大于10 GeV / c
  • 的所有轨道上,所有命中的平均电荷在0-10 ps
  • 具有最高动量的两个轨道的加权平均θ

等等。这些查询的共同之处在于,它们都会解决每个事件的一个标量,尽管它们会深入研究结构数组。它们执行“减少”操作(在Scala中通常为fold,在Spark中为aggregate,在SQL中为DAF“,跨过这些数组的已过滤,已转换的子集。我可以像这样在Scala中编写它们:

// missing check for when zero tracks passed filter!
{event => event.tracks                      // get list of tracks
               .filter(abs(_.theta) < 2.4)  // in theta range
               .maxBy(_.hits.size)          // take the one with the most hits
               .momentum                    // return its momentum
}

{event => mean(
            event.tracks                    // get list of tracks
                 .filter(_.momentum > 10)   // in momentum range
                 .flatMap(_.hits)           // explode to hits
                 .filter(_.time < 10)       // in time range
                 .map(_.charge)             // return their charges
              )}                            // ... to the mean function

// again missing check for less than two tracks!
{event => val List(one, two) =              // unpack and assign "one" and "two"
              event.tracks                  // get list of tracks
                   .sortBy(_.momentum)      // sort by momentum
                   .take(2)                 // take the first two
          // now compute the weighted mean of structs "one" and "two"
          (one.theta*one.momentum + two.theta*two.momentum) /
              (one.momentum + two.momentum)
}

为什么不直接使用Scala?我的程序是用C实现的,可以在GPU上运行。无论我带来什么Scala都将是一个重新实现的子集 - 换句话说,是一种发明的语言。 (Haskell,Javascript或其他大量使用函数作为参数的语言也是如此。)

此外,这些查询应该是声明性的。如果我实现了太多的通用编程语言,那么函数调用顺序等细节可能会变得相关。

为什么不直接使用SQL?是否可以轻松地编写上述之类的查询,以便除了作者以外的任何人都可以阅读?像上面这样的查询是常态,而不是复杂的极端。

SQL支持嵌套的结构数组,但是我能找到使用这个子结构的所有例子都非常复杂。一个人必须将事件表分解为一个轨道表(或者双击以获得命中),并且需要进行一些复杂的会计以进行未爆炸并且每个事件返回一个标量。

我想我可以使用带有MAXIMAL(collection, function)等新函数的SQL来返回数组中的结构,类似于track[12]但是使用用户提供的函数作为目标函数来最大化,最小化,查找顶部/底部N等。我不认为SQL支持将函数作为参数传递。如果我编写一个SQL,它将是非标准的。

是否有广泛使用的SQL方言支持将函数作为参数传递?

或者我应该考虑另一种声明性语言吗?

4 个答案:

答案 0 :(得分:3)

JSONiq专为查询树而设计,即使在数据高度嵌套且异构的情况下也是如此。根据W3C标准,该比例为95%。

Rumble是JSONiq的开源实现,可处理数十亿条记录的集合。它在引擎盖下使用Spark,但对用户完全透明(并向用户隐藏)。

这三个查询看起来像这样。借助Rumble,它们可以无缝地在笔记本电脑上运行少量数据,但也可以并行运行在群集上潜在的数十亿个对象上,只要为其简化了底层解析器即可。声明性代码相同。

查询1:

(
  for $track in root-file("events.root", "Events").tracks
  where abs($track.theta) lt 2.4
  order by size($track.hits) descending
  return track
)[1].momentum

查询2:

root-file("events.root", "Events").tracks[$$.momentum gt 10].hits[][$$.time lt 10].charge

查询3:

let $tracks := (
    for $track in root-file("events.root", "Events").tracks
    order by $track.momentum
    return $track
  )[position() le 2]
return
  sum(
    for $t in $tracks
    return $t.theta * $t.momentum
  ) div sum($tracks.momentum)

答案 1 :(得分:1)

即使您拥有类似数据结构的纯树,也可能需要查看图形数据库。特别是,NEO4J支持一种称为Cypher的声明性查询语言:

https://neo4j.com/developer/cypher-query-language/

Titan也可能对您所谈论的规模感兴趣,它支持来自Apache TinkerPop项目的Gremlin,该项目是多平台的(但不是声明性的):

http://tinkerpop.apache.org/docs/3.0.1-incubating/#preface

答案 2 :(得分:1)

我之前在评论中发布了此信息,但是将其移到此处。

我和其他人一起使用图形数据库。我不熟悉Neo4j查询,但我希望它们有能力。同样,SPARQL也适用于此类事情。

对于第一个查询,SPARQL查询可能如下所示:

PREFIX : <http://yournamespace.com/accelerator/> .

SELECT ?momentum (MAX(?hitcount) as ?maxhits)
WHERE {
    SELECT ?momentum (COUNT(?hits) AS ?hitcount)
    WHERE ?track :momentum ?momentum .
          ?track :theta ?theta .
          FILTER (?theta > -2.4 AND ?theta < 2.4) .
          ?track :hits ?hits
    GROUP BY ?track
}
GROUP BY ?momentum;

标识符在它们上面有:前缀,因为它们需要编码为URI。但这是转移到RDF(SPARQL数据库的数据格式)的内部细节。

以上查询正在进行子查询,因为您希望聚合(在计数上),然后再次聚合(使用最大计数)。但是你可以看到它都是以类似SQL的方式处理的,并且不需要后处理。

答案 3 :(得分:0)

Scala示例1 ...

// missing check for when zero tracks passed filter!
{event => event.tracks                      // get list of tracks
               .filter(abs(_.theta) < 2.4)  // in theta range
               .maxBy(_.hits.size)          // take the one with the most hits
               .momentum                    // return its momentum
}

潜在的SQL ....

WITH
   hit_stats
AS
(
   SELECT
       hit.track_id,
       COUNT(*)    as hit_count
   FROM
       hit
   GROUP BY
       hit.track_id
),
   track_sorted
AS
(
    SELECT
       track.*,
       ROW_NUMBER() OVER (PARTITION BY track.event_id
                              ORDER BY hit_stats.hit_count DESC
                         )
                            track_ordinal
    FROM
       track
    INNER JOIN
       hit_stats
           ON  hit_stats.track_id = track.id
    WHERE
           track.theta > -2.4
       AND track.theta <  2.4
)
SELECT
    *
FROM
    event
INNER JOIN
    track_sorted
        ON  track_sorted.event_id = event.id
WHERE
    track_sorted.track_ordinal = 1

或使用MS SQL Server中的APPLY

SELECT
   event.*,
   track.momentum
FROM
   event
OUTER APPLY
(
    SELECT TOP 1
        track.*,
        stat.hit_count
    FROM
        track
    OUTER APPLY
    (
        SELECT
            COUNT(*) hit_count
        FROM
            hit
        WHERE
            track_id = track.id
    )
        stat
    WHERE
            track.event_id = event.id
        AND track.theta    > -2.4
        AND track.theta    <  2.4
    ORDER BY
       stat.hit_count DESC
)
   track

这是非常嵌套的,我发现它比CTE版本更难阅读和维护。但最终可能会有一个非常相似的执行计划。

Oracle和其他方言有其他方法实现类似的“函数”,因为MS SQL Server使用APPLY完成。