F# query for "doesn't contain" - f#

There's a query to search for when an item is contained in a list, but there isn't one for when an item isn't.
This query finds customer objects which don't have ContactNum in the given list cdiffnums. What could I do to return just the customers that dont have a ContactNum in this list?
let q =
query {
for c in dc.Customers do
where (query { for n in cdiffnums do contains c.ContactNum })
select c
}

My F# is rusty, but have you tried:
let q =
query {
for c in dc.Customers do
where (not (query { for n in cdiffnums do contains c.ContactNum }))
select c
}

I think smth like this should work:
open System.Linq
let cdiffnums = [|1;2;3|]
let q =
query {
for c in dc.Customers do
where (not (cdiffnums.Contains c.ContactNum))
select c
}

Related

F# query concatenation

I am using SqlDataConnection data provider in F# to migrate some rows, part of that migration is to make a join between 3 tables like this, think of it as an inheritance of tables A, B, C where B and C inherit from A so the thing is I need to get is (in Linq-like):
Bs.Join(As, b.PK, a.FK).Select(new {...})
.Concat(Cs.Join(As, c.PK, a.FK).Select(new {...})
In F#, the closest I got to this was:
let result = seq {
yield! query { ... }
yield! query { ... }
}
but I've been told this will produce 2 SQL queries and the overall result will be on-memory. The question being: is there a way to make this "concatenation" as a query computation expression without using seq so that all happens in a single SQL query?
Here's some example code that combines two queries using SQL UNION or UNION ALL.
First, the setup. Note that I've added logging to dbContext so you can see what happens behind the scenes.
#r "System.Data.dll"
#r "System.Data.Linq.dll"
#r "FSharp.Data.TypeProviders.dll"
open System
open System.Linq
open Microsoft.FSharp.Data.TypeProviders
type sql = SqlDataConnection<connStr>
let createDbContext() =
let dbContext = sql.GetDataContext()
// add logging to console
dbContext.DataContext.Log <- System.Console.Out
dbContext
let db = createDbContext()
let products = db.Product
let q1 = query { for x in products do select x }
let q2 = query { for y in products do select y }
The Union extension method combines queries as one query using UNION
let qUnion = q1.Union(q2)
qUnion.ToList() |> Seq.toList
Here's the logged output:
SELECT [t2].[Id], [t2].[Name]
FROM (
SELECT [t0].[Id], [t0].[Name]
FROM [dbo].[Product] AS [t0]
UNION
SELECT [t1].[Id], [t1].[Name]
FROM [dbo].[Product] AS [t1]
) AS [t2]
The Concat extension method combines queries as one query using UNION ALL
let qConcat = q1.Concat(q2)
qConcat.ToList() |> Seq.toList
Here's the logged output:
SELECT [t2].[Id], [t2].[Name]
FROM (
SELECT [t0].[Id], [t0].[Name]
FROM [dbo].[Product] AS [t0]
UNION ALL
SELECT [t1].[Id], [t1].[Name]
FROM [dbo].[Product] AS [t1]
) AS [t2]
There's no special syntax for unions in query expressions, AFAIK.

How to return a query from an anonymous function and pass it to a forward pipe

I have a query that is then forward piped to an anonymous function. I would like this function to utilize the query and then forward pipe it to the next function. Not sure if this possible as I am having trouble accomplishing this.
so far I have this:
// find file for deletion
let findFileToDelete id =
// query
query{
for row in db.Uploads do
where (row.Id = id)
select row
exactlyOneOrDefault
}
|> (fun q -> File.Delete(q.FilePath))
But I would like to continue on with something like this:
// find file for deletion
let findFileToDelete id =
// query
query{
for row in db.Uploads do
where (row.Id = id)
select row
exactlyOneOrDefault
}
|> (fun q -> File.Delete(q.FilePath)) q // forward pipe the query
|> deleteRowFrom db.Uploads q
// update the database
saveToDb()
As Mark pointed out, pipelining is generally used for a series of transformations. Your code performs a series of actions. I recommend writing it like this:
// find file for deletion
let findFileToDelete id =
let row =
query {
for row in db.Uploads do
where (row.Id = id)
exactlyOneOrDefault
}
File.Delete row.FilePath
deleteRowFrom db.Uploads row
// update the database
saveToDb()
F# has significant whitespace, and the most normal way of separating multiple statements is by separating them with a newline, so you should be able to do something like this:
let findFileToDelete id =
// query
query{
for row in db.Uploads do
where (row.Id = id)
select row
exactlyOneOrDefault
}
|> (fun q ->
File.Delete(q.FilePath)
q) // forward pipe the query
|> deleteRowFrom db.Uploads
// update the database
saveToDb()
However, I generally wouldn't recommend doing something like this, as it introduces side-effects into the middle of a query. This is likely to make your code more difficult to reason about.
Instead, I would recommend assigning the query to a value with a let binding, and then pass it to Seq.iter to do all the operations with side effects, and reuse it in a query if you need to do that.

F# - cypher query with multiple return values

Given this query (from here)
let pAfollowers =
client.Cypher
.Match("n<-[:follows]-e")
.Where(fun n -> n.Twitter = "tA")
.Return<Person>("e")
.Results
.Select(fun x -> x.Name)
I would like to tweak it and have it return multiple values packaged together.
Not sure about how the type should look:
let pAfollowers =
client.Cypher
.Match("n<-[r:follows]-e")
.Where(fun n -> n.Twitter = "tA")
.Return<???>("n, r, e")
Secondly I was wondering if it is possible to have a return statement after a CreateUnique.
I am trying to tweak this query:
let knows target (details : Knows) source =
client.Cypher
.Match("(s:Person)", "(t:Person)")
.Where(fun s -> s.Twitter = source.Twitter)
.AndWhere(fun t -> t.Twitter = target.Twitter)
.CreateUnique("s-[:knows {knowsData}]->t")
.WithParam("knowsData", details)
.ExecuteWithoutResults()
to have it return s, t and the details.
OK, good news / bad news - though in practice the good is tempered with bad :(
Good first:
You can return after a CreateUnique, something like:
.CreateUnique("s-[:Knows {knowsData}]-t")
.WithParam("knowsData", details)
.Returns<???>( "s,t" )
.Results;
Bad news:
The bad news is that you probably can't do it in F#. Neo4jClient requires you to use either object initializers, or anonymous types to cast the data, so you could try something like:
type FollowingResults = { Follower : Person; Followed : Person;}
let createExpression quotationExpression = LeafExpressionConverter.QuotationToLambdaExpression quotationExpression
let pAfollowers =
client.Cypher
.Match("n<-[:follows]-e")
.Where(fun n -> n.Twitter = "tA")
.Return(createExpression <# Func<ICypherResultItem, ICypherResultItem, FollowingResults>(fun (e : Cypher.ICypherResultItem) (n : Cypher.ICypherResultItem) -> {Follower = e.As<Person>(); Followed = n.As<Person>()}) #>)
.Results
.Select(fun x -> x)
for follower in pAfollowers do
printfn "%s followed %s" follower.Follower.Name follower.Followed.Name
For which the F# compiler will have no problems at all. However, Neo4jClient will throw an Argument exception with the following message:
The expression must be constructed as either an object initializer (for example: n => new MyResultType { Foo = n.Bar }), an anonymous type initializer (for example: n => new { Foo = n.Bar }), a method call (for example: n => n.Count()), or a member accessor (for example: n => n.As().Bar). You cannot supply blocks of code (for example: n => { var a = n + 1; return a; }) or use constructors with arguments (for example: n => new Foo(n)).
The problem being, F# doesn't have object initializers, nor anonymous types, you can wrangle with the F# stuff for ages and not get anywhere, as the C# doesn't recognize the F# initialization.
I have somewhat good news to both. This code will compile just fine using tuples and can be used with a modified Neo4jClient that supports F# Tuples: https://github.com/n074v41l4bl34u/Neo4jClient Solution is based on: https://fsharppowerpack.codeplex.com/workitem/4572
let knows target (details : Knows) source =
client.Cypher
.Match("(s:Person)", "(t:Person)")
.Where(fun s -> s.Twitter = source.Twitter)
.AndWhere(fun t -> t.Twitter = target.Twitter)
.CreateUnique("s-[:knows {knowsData}]->t")
.WithParam("knowsData", details)
.Return(fun s t -> s.As<Person>(),t.As<Person>())
let pAfollowers =
client.Cypher
.Match("n<-[:follows]-e")
.Where(fun n -> n.Twitter = "tA")
.Return(fun (e : Cypher.ICypherResultItem) n -> e.As<Person>().Name,n.As<Person>().Name)
The type annotation on '(e : Cypher.ICypherResultItem)' can be omited when using more than one argument in fun.
However, when using a single argument, this gets rid off the ugly createExpression <# Func(...) #>) syntax.
For details on why look on the bottom of this page: https://gist.github.com/cskardon/8300420

Async database query

I'm just starting out with F# and .Net but after some Googling I didn't find examples of this. I apologize in advance if this is too simple.
I'm trying to query a database and do it asynchronously. For example, I have a function like so:
let queryExample name =
query {for f in foo do
where (f.name = name)
select f.name}
|> Seq.toList
Now, how would I make an async version of this? query doesn't return an Async<'a> type.
The answer will depend on what you're querying. Many data sources will expose something like a data context that enables running queries in different ways, but this isn't exposed directly on the IQueryable type (and is therefore not something that you can do directly with the result of a query { ... } expression.
As an example, if your foo is a LINQ-to-SQL Table<Foo>, then you can do something like this:
let queryExample name =
let q =
query { for f in foo do
where (f.name = name)
select f.name }
let cmd = foo.Context.GetCommand(q)
async {
let! reader = Async.AwaitTask (cmd.ExecuteReaderAsync())
return foo.Context.Translate<Foo>(reader)
}
This will return an Async<seq<Foo>>. If you'll be running lots of queries like this, then it's easy to extract the meat of this into a reusable mechanism.
I use FSharp.Data.Sql with success http://fsprojects.github.io/SQLProvider/
Seq.executeQueryAsync
Example below shows an easy syntax for doing this. Basically just create an async function with a query expression inside and return the result.
let data = [ 1; 5; 7; 11; 18; 21]
let getEvenInts = fun (arr : list<int> ) -> async {
let q = query {
for N in arr do
where (( N % 2 ) = 0)
select N
}
return q
}
let getOddInts = fun (arr : list<int> ) -> async {
let q = query {
for N in arr do
where (( N % 2 ) <> 0)
select N
}
return q
}
let evens = getEvenInts data |> Async.RunSynchronously
let odds = getOddInts data |> Async.RunSynchronously
printfn "Evens: %A Odds: %A" evens odds

Get the values of a variable of tuple option type?

I have the following F# code. The function getARow returns (string * string * string) option. In the main function, I need to extract column values and assign them to a, b, c respectively. How to implement it? (Newbie question)
And The function may return null if there is no row found? How to handle it if getARow returns null?
open System
open System.Data
open System.Data.Linq
open Microsoft.FSharp.Data.TypeProviders
open Microsoft.FSharp.Linq
open System.Net
open System.IO
open FSharp.Data
type dbSchema = SqlDataConnection<"Data Source=Svr;Initial Catalog=DB;Integrated Security=SSPI;">
//, LocalSchemaFile = "Schema.dbml", ForceUpdate = false >
let getARow =
let db = dbSchema.GetDataContext()
db.DataContext.Log <- System.Console.Out
let query = query {
for row in db.Table1 do
where (row.SiteID = 1)
select (Some(row.Col1, row.Col2, row.Col2)) // All Colx are strings
headOrDefault // May return null
}
query
[<EntryPoint>]
let main argv =
let aRow = getARow
printfn "%A" aRow
let a,b,c = match aRow with
| ........ // How to let a = Col1, b = Col2 and c = Col3?
| None -> failwith "Cannot find a row" // Null?
0
To extract values from an option type (no matter what it contains), you need to use the Some <...> pattern where <...> is a nested pattern that is can give name to the value (if you want a single variable containing the tuple) or decompose it further (if you want three separate variables):
let a,b,c =
match aRow with
| Some(a, b, c) -> a, b, c
| None -> failwith "Cannot find a row"
Alternatively, you can just use the built-in Option.get function:
let a,b,c = Option.get aRow
Your code relies on a trick - that headOrDefault returns null if there is no row - which will work, because null is the internal representation of None. But you could also use an extension that adds headOrNone to make the code a bit nicer.

Resources