Haskell,如何实现SQL之类的操作?

时间:2014-08-22 02:55:54

标签: list haskell vector

我正在尝试使用Haskell进行类似SQL的操作,但我不知道要使用哪些数据结构。我有3个不同的表:客户,销售和订单。模式如下:

客户

  • custid - 整数(主键)
  • name - 字符串

示例:

1|Samson Bowman
2|Zelda Graves
3|Noah Hensley
4|Noelle Haynes
5|Paloma Deleon

销售

  • orderid - 整数(主键)
  • custid - 整数
  • date - 字符串

示例:

1|3|20/3/2014
2|4|25/4/2014
3|5|17/7/2014
4|9|5/1/2014
5|5|9/6/2014

顺序

  • orderid - 整数
  • item - 字符串

示例:

2|gum
4|sandals
3|pen
1|gum
2|pen
3|chips
1|pop
5|chips

我想要做的是将这三个表“合并”到一个新表中,新表的模式是:

Customername    Order#     Date       Items
Samson Bowman   17      20/3/2014 shoes, socks, milk
Samson Bowman   34      19/5/2014 gum, sandals, butter, pens, pencils
Paloma Deleon   41      6/1/2014  computer
…

所以是的,它非常像SQL。我知道SQL非常简单,但是如何在没有SQL的情况下实现它,而是使用内置数据结构?

TEXT PRINT ERROR 当我运行该函数时,它显示以下错误:

Couldn't match type `[Char]' with `Char'
    Expected type: Customer -> String
      Actual type: Customer -> [String]
    In the first argument of `map', namely `formatCustomer'
    In the second argument of `($)', namely `map formatCustomer result'

我认为缩减的返回类型是[Customer],但formatCustomer只使用Customer。这是什么原因?

2 个答案:

答案 0 :(得分:1)

你所有的协会都是一对多的,他们并不是指彼此;它是严格分层的。客户有销售,销售有订单。鉴于此,您可能不会单独存储每一位信息,而是按层次结构存储。我可能会把它放到像这样的数据类型中:

data Customer = Customer { customerName :: String
                         , sales        :: [Sale]
                         } deriving (Eq, Read, Show)

data Sale = Sale { saleDate  :: Day
                 , soldItems :: [String]
                 } deriving (Eq, Read, Show)

这可能很容易在Haskell中进行操作,而且,作为奖励,很容易变成你想要最终的表格,只是因为它首先非常接近。


但也许我误解了你的问题,你不只是要求最好的数据结构来保持它,而是如何从你的平面数据结构转换成这种结构。幸运的是,这很容易。由于所有内容都是键控的,因此我会构建一个Map并开始unionWith内容,或者甚至更好地使用fromListWith同时执行这两项操作。更具体地说,就是说你有这些数据结构:

data DBCustomer = DBCustomer { dbCustomerName :: String
                             , dbCustomerID   :: Int
                             } deriving (Eq, Read, Show)

data DBSale = DBSale { saleOrderID    :: Int
                     , saleCustomerID :: Int
                     , dbSaleDate     :: Day
                     } deriving (Eq, Read, Show)

data DBOrder = DBOrder { dbOrderID   :: Int
                       , dbOrderItem :: String
                       } deriving (Eq, Read, Show)

如果我想要一个类型为[DBSale] -> [DBOrder] -> [Sale]的函数,我可以轻松地编写它:

condense :: [DBSale] -> [DBOrder] -> [Sale]
condense dbSales dbOrders = flip map dbSales $ \dbSale ->
    Sale (dbSaleDate dbSale)
       $ fromMaybe [] (Map.lookup (saleOrderID dbSale) ordersByID) where
  ordersByID = Map.fromListWith (++) . flip map dbOrders
             $ \dbOrder -> (dbOrderID dbOrder, [dbOrderItem dbOrder])

这里我放弃了客户ID,因为Sale中没有插槽,但你肯定可以投入另一个Map并获取整个Customer个对象:

condense :: [DBCustomer] -> [DBSale] -> [DBOrder] -> [Customer]
condense dbCustomers dbSales dbOrders = flip map dbCustomers $ \dbCustomer ->
    Customer (dbCustomerName dbCustomer)
           $ lookupDef [] (dbCustomerID dbCustomer) salesByCustomerID where
  lookupDef :: (Ord k) => a -> k -> Map.Map k a -> a
  lookupDef def = (fromMaybe def .) . Map.lookup
  salesByCustomerID = Map.fromListWith (++) . flip map dbSales
                    $ \dbSale -> (saleCustomerID dbSale,
                                  [ Sale (dbSaleDate dbSale)
                                  $ lookupDef [] (saleOrderID dbSale)
                                              ordersByID])
  ordersByID = Map.fromListWith (++) . flip map dbOrders
             $ \dbOrder -> (dbOrderID dbOrder, [dbOrderItem dbOrder])

打印

这应该相当容易。我们将使用Text.Printf,因为它可以更轻松地将列表放入列中。总的来说,结果中的每一行都是Sale。首先,我们可以尝试格式化单行:

formatSale :: Customer -> Sale -> String
formatSale customer sale = printf "%-16s%-8d%-10s%s"
                                  (customerName customer)
                                  (orderID sale)
                                  (show $ saleDate sale)
                                  (intercalate "," $ soldItems sale)

(实际上,我们放弃了订单ID;如果要在输出中保留它,则必须将其添加到Sale数据结构中。)然后获取每个客户的行列表很容易:

formatCustomer :: Customer -> [String]
formatCustomer customer = map (formatSale customer) $ sales customer

然后为所有客户执行此操作并将其打印出来,如果customerscondense的输出:

putStr . unlines $ concatMap formatCustomer customers

答案 1 :(得分:0)

我遇到了一些类似的问题,我发现做SQL Join 操作的最佳方法是使用these包中的align函数,并结合{{1} }(键在你想要加入的地方)。

Map的结果会为您提供align的地图或列表,其中包含These a ba或两者。那非常整洁。