【问题标题】:Subqueries with Beam带有 Beam 的子查询
【发布时间】:2018-11-22 00:21:21
【问题描述】:

我正在尝试对表的每一行运行子查询。这是一个带有“students”表的最小工作示例。

data StudentT  f
  = StudentT
  { _studentId   :: C f Int
  , _studentName :: C f String
  , _score       :: C f Int
  } deriving Generic

type Student = StudentT Identity
type StudentId = PrimaryKey StudentT Identity

deriving instance Show Student

instance Beamable StudentT
instance Beamable (PrimaryKey StudentT)

instance Table StudentT where
  data PrimaryKey StudentT f = StudentId (Columnar f Int) deriving Generic

data SchoolDb f
  = SchoolDb
  { _students :: f (TableEntity StudentT)
  } deriving Generic

instance Database be SchoolDb

schoolDb :: DatabaseSettings be SchoolDb
schoolDb = defaultDbSettings

我想要实现的是这样的查询:

SELECT s.id,
       s.name,
       s.score,
       (SELECT COUNT(*) FROM students AS t where s.score >= t.score) AS percentile 
FROM students as S

我的尝试如下:

main = do
  conn <- open "test.db"
  runBeamSqliteDebug putStrLn conn $ do
    (students :: [(Student, Int)]) <- runSelectReturningList $ select tablePercentile
    liftIO $ mapM_ print students


tablePercentile :: Q _ _ _ _
tablePercentile = do
  student <- all_ (_students schoolDb)
  let percentile =  subquery_ $ aggregate_ (const countAll_) $ filter_ (\s -> _score s <=. (_score student)) (all_ (_students schoolDb))
  return (student, percentile)

有人能指出我正确的方向吗?

编辑:这是完整的错误消息。我认为subquery_ 返回一个QGenExpr,所以我没有绑定它(&lt;-),而是把它放到了一个 let 语句中。这稍微简化了错误消息。

src/Main.hs:52:71: error:
    • Couldn't match type ‘Database.Beam.Query.Internal.QNested s0’
                     with ‘Database.Beam.Query.QueryInaccessible’
      Expected type: Q SqliteSelectSyntax
                       SchoolDb
                       Database.Beam.Query.QueryInaccessible
                       (StudentT
                          (QExpr
                             Database.Beam.Sqlite.Syntax.SqliteExpressionSyntax
                             (Database.Beam.Query.Internal.QNested s0)),
                        QGenExpr
                          QValueContext
                          (Database.Beam.Backend.SQL.SQL92.Sql92SelectTableExpressionSyntax
                             (Database.Beam.Backend.SQL.SQL92.Sql92SelectSelectTableSyntax
                                SqliteSelectSyntax))
                          s0
                          Int)
        Actual type: Q SqliteSelectSyntax
                       SchoolDb
                       (Database.Beam.Query.Internal.QNested s0)
                       (StudentT
                          (QExpr
                             (Database.Beam.Backend.SQL.SQL92.Sql92SelectTableExpressionSyntax
                                (Database.Beam.Backend.SQL.SQL92.Sql92SelectSelectTableSyntax
                                   SqliteSelectSyntax))
                             (Database.Beam.Query.Internal.QNested s0)),
                        QGenExpr
                          QValueContext
                          (Database.Beam.Backend.SQL.SQL92.Sql92SelectTableExpressionSyntax
                             (Database.Beam.Backend.SQL.SQL92.Sql92SelectSelectTableSyntax
                                SqliteSelectSyntax))
                          s0
                          Int)
    • In the first argument of ‘select’, namely ‘tablePercentile’
      In the second argument of ‘($)’, namely ‘select tablePercentile’
      In a stmt of a 'do' block:
        (students :: [(Student, Int)]) <- runSelectReturningList
                                            $ select tablePercentile
   |
52 |     (students :: [(Student, Int)]) <- runSelectReturningList $ select tablePercentile
   |                                                                       ^^^^^^^^^^^^^^^

【问题讨论】:

  • 请包括完整的错误——它很长不是问题。特别是,此处发布的错误部分缺少有关导致错误的代码位的信息。

标签: haskell haskell-beam


【解决方案1】:

这是我第一次使用 Beam,我发现使用 the examples involving aggregates in the user guide 作为参考从头开始,而不是在此处修复代码更容易:

tablePercentile =
  aggregate_ (\(student, student') -> (group_ (_studentId student), countAll_))
    . filter_ (\(student, student') -> (_score student <=. _score student'))
    $ (,) <$> all_ (_students schoolDb) <*> all_ (_students schoolDb)

这相当于表与自身的内部连接,filter_ 设置连接条件,aggregate_ 处理分组和计数。请注意,此查询仅检索学生 ID,而不是完整记录。这是因为通常不可能从GROUP BY-using 查询中获得超过聚合和用于分组的列。解决这个问题的一种方法是使用子查询来传递 id:

tablePercentile = do
  (sid, cou) <- aggregate_ (\(student, student') -> (group_ (_studentId student), countAll_))
    . filter_ (\(student, student') -> (_score student <=. _score student'))
    $ (,) <$> all_ (_students schoolDb) <*> all_ (_students schoolDb)
  (\student -> (student, cou))
    <$> filter_ (\student -> _studentId student ==. sid) (all_ (_students schoolDb))
-- N.B.: The last line of the do-block might be written as
-- (,) <$> filter_ (\student -> _studentId student ==. sid) (all_ (_students schoolDb)) <*> pure cou

这按预期工作:

sqlite> SELECT * from Students;
Id|Name|Score
1|Alice|9
2|Bob|7
3|Carol|6
4|David|8
5|Esther|10
6|Francis|6
GHCi> :main
SELECT "t1"."id" AS "res0", "t1"."name" AS "res1", "t1"."score" AS "res2", "t0"."res1" AS "res3" FROM (SELECT "t0"."id" AS "res0", COUNT(*) AS "res1" FROM "students" AS "t0" INNER JOIN "students" AS "t1" WHERE ("t0"."score")<=("t1"."score") GROUP BY "t0"."id") AS "t0" INNER JOIN "students" AS "t1" WHERE ("t1"."id")=("t0"."res0");
-- With values: []
(StudentT {_studentId = 1, _studentName = "Alice", _score = 9},2)
(StudentT {_studentId = 2, _studentName = "Bob", _score = 7},4)
(StudentT {_studentId = 3, _studentName = "Carol", _score = 6},6)
(StudentT {_studentId = 4, _studentName = "David", _score = 8},3)
(StudentT {_studentId = 5, _studentName = "Esther", _score = 10},1)
(StudentT {_studentId = 6, _studentName = "Francis", _score = 6},6)

最后,据我了解,您的代码中出现的错误与尝试比较 (&lt;=.) 条件中的无法比较的事物有关。如果 filter_ 被注释掉,您的原始代码(使用 percentile 的一元绑定)将编译。这可能与我提到的GROUP BY 问题有关,但我不确定。

【讨论】:

  • 非常感谢您的回答。这绝对解决了我的问题。有趣的是,生成的 SQL 查询完全按照我的意图使用子查询,尽管它看起来更像是与临时表的连接......酷。
猜你喜欢
  • 2013-06-12
  • 1970-01-01
  • 2022-10-14
  • 1970-01-01
  • 2018-05-31
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多