F# Query Expression using join & groupBy in same query - linq

I decided to learn how to use query expressions in F# and found the official Microsoft documentation. The caveats don't seem spelled out so I'm sure I've hit a problem with a simple fix but don't know why I'm getting the error it shows.
My thought was to write a query that did both a join and grouping in the same go. For example, using their sample 'MyDatabase' I thought I would try to find the number of classes that each student is enrolled in. After writing the following query, the compiler flags no warning, but when I go to run it it gives an error.
My query expression:
query {
for student in db.Student do
join course in db.CourseSelection
on (student.StudentID = course.StudentID)
groupBy student into group
select (group.Key, group.Count())
}
|> Seq.iter (fun (student, classCount) -> printfn "Student %s has %i classes" student.Name classCount)
The error:
System.InvalidOperationException: Could not format node 'New' for execution as SQL.
at System.Data.Linq.SqlClient.SqlFormatter.Visitor.VisitNew(SqlNew sox)
at System.Data.Linq.SqlClient.SqlVisitor.Visit(SqlNode node)
> at System.Data.Linq.SqlClient.SqlFormatter.Visitor.VisitAlias(SqlAlias alias)
at System.Data.Linq.SqlClient.SqlVisitor.Visit(SqlNode node)
at System.Data.Linq.SqlClient.SqlFormatter.Visitor.VisitSelect(SqlSelect ss)
at System.Data.Linq.SqlClient.SqlVisitor.Visit(SqlNode node)
at System.Data.Linq.SqlClient.SqlFormatter.Visitor.VisitScalarSubSelect(SqlSubSelect ss)
at System.Data.Linq.SqlClient.SqlVisitor.Visit(SqlNode node)
at System.Data.Linq.SqlClient.SqlFormatter.Visitor.VisitRow(SqlRow row)
at System.Data.Linq.SqlClient.SqlFormatter.Visitor.VisitSelect(SqlSelect ss)
at System.Data.Linq.SqlClient.SqlVisitor.Visit(SqlNode node)
at System.Data.Linq.SqlClient.SqlFormatter.Visitor.Format(SqlNode node, Boolean isDebug)
at System.Data.Linq.SqlClient.SqlFormatter.Format(SqlNode node)
at System.Data.Linq.SqlClient.SqlProvider.BuildQuery(ResultShape resultShape, Type resultType, SqlNode node, ReadOnlyCollection`1 parentParameters, SqlNodeAnnotations annotations)
at System.Data.Linq.SqlClient.SqlProvider.BuildQuery(Expression query, SqlNodeAnnotations annotations)
at System.Data.Linq.SqlClient.SqlProvider.System.Data.Linq.Provider.IProvider.Execute(Expression query)
at System.Data.Linq.DataQuery`1.System.Collections.Generic.IEnumerable<T>.GetEnumerator()
at System.Linq.Enumerable.WhereSelectEnumerableIterator`2.MoveNext()
at Microsoft.FSharp.Collections.SeqModule.Iterate[T](FSharpFunc`2 action, IEnumerable`1 source)
at <StartupCode$FSI_0008>.$FSI_0008.main#() in C:\Users\JDKS\Library\query expressions.fsx:line 129
Stopped due to error
I even thought I could get out of this issue by using a subquery:
query {
for student in db.Student do
join course in db.CourseSelection
on (student.StudentID = course.StudentID)
let count = query {
for s in student.Name do
select course.CourseID
count
}
select (student.Name, count)
}
|> Seq.iter (fun (student, classCount) -> printfn "Student %s has %i classes" student classCount)
but that gave an even larger error:
> System.NotSupportedException: Sequence operators not supported for type 'System.String'.
at System.Data.Linq.SqlClient.SqlBinder.Visitor.ConvertToFetchedSequence(SqlNode node)
at System.Data.Linq.SqlClient.SqlBinder.Visitor.VisitAlias(SqlAlias a)
at System.Data.Linq.SqlClient.SqlVisitor.Visit(SqlNode node)
at System.Data.Linq.SqlClient.SqlVisitor.VisitSource(SqlSource source)
at System.Data.Linq.SqlClient.SqlBinder.Visitor.VisitSelect(SqlSelect select)
at System.Data.Linq.SqlClient.SqlVisitor.Visit(SqlNode node)
at System.Data.Linq.SqlClient.SqlBinder.Visitor.VisitAlias(SqlAlias a)
at System.Data.Linq.SqlClient.SqlVisitor.Visit(SqlNode node)
at System.Data.Linq.SqlClient.SqlVisitor.VisitSource(SqlSource source)
at System.Data.Linq.SqlClient.SqlBinder.Visitor.VisitSelect(SqlSelect select)
at System.Data.Linq.SqlClient.SqlVisitor.Visit(SqlNode node)
at System.Data.Linq.SqlClient.SqlBinder.Visitor.VisitAlias(SqlAlias a)
at System.Data.Linq.SqlClient.SqlVisitor.Visit(SqlNode node)
at System.Data.Linq.SqlClient.SqlVisitor.VisitSource(SqlSource source)
at System.Data.Linq.SqlClient.SqlBinder.Visitor.VisitSelect(SqlSelect select)
at System.Data.Linq.SqlClient.SqlVisitor.Visit(SqlNode node)
at System.Data.Linq.SqlClient.SqlVisitor.VisitSequence(SqlSelect sel)
at System.Data.Linq.SqlClient.SqlVisitor.VisitScalarSubSelect(SqlSubSelect ss)
at System.Data.Linq.SqlClient.SqlBinder.Visitor.VisitSubSelect(SqlSubSelect ss)
at System.Data.Linq.SqlClient.SqlVisitor.Visit(SqlNode node)
at System.Data.Linq.SqlClient.SqlBinder.Visitor.VisitExpression(SqlExpression expr)
at System.Data.Linq.SqlClient.SqlBinder.Visitor.FetchExpression(SqlExpression expr)
at System.Data.Linq.SqlClient.SqlBinder.Visitor.VisitNew(SqlNew sox)
at System.Data.Linq.SqlClient.SqlVisitor.Visit(SqlNode node)
at System.Data.Linq.SqlClient.SqlBinder.Visitor.VisitExpression(SqlExpression expr)
at System.Data.Linq.SqlClient.SqlBinder.Visitor.VisitSelect(SqlSelect select)
at System.Data.Linq.SqlClient.SqlVisitor.Visit(SqlNode node)
at System.Data.Linq.SqlClient.SqlBinder.Visitor.VisitAlias(SqlAlias a)
at System.Data.Linq.SqlClient.SqlVisitor.Visit(SqlNode node)
at System.Data.Linq.SqlClient.SqlVisitor.VisitSource(SqlSource source)
at System.Data.Linq.SqlClient.SqlBinder.Visitor.VisitSelect(SqlSelect select)
at System.Data.Linq.SqlClient.SqlVisitor.Visit(SqlNode node)
at System.Data.Linq.SqlClient.SqlBinder.Visitor.VisitIncludeScope(SqlIncludeScope scope)
at System.Data.Linq.SqlClient.SqlVisitor.Visit(SqlNode node)
at System.Data.Linq.SqlClient.SqlBinder.Bind(SqlNode node)
at System.Data.Linq.SqlClient.SqlProvider.BuildQuery(ResultShape resultShape, Type resultType, SqlNode node, ReadOnlyCollection`1 parentParameters, SqlNodeAnnotations annotations)
at System.Data.Linq.SqlClient.SqlProvider.BuildQuery(Expression query, SqlNodeAnnotations annotations)
at System.Data.Linq.SqlClient.SqlProvider.System.Data.Linq.Provider.IProvider.Execute(Expression query)
at System.Data.Linq.DataQuery`1.System.Collections.Generic.IEnumerable<T>.GetEnumerator()
at System.Linq.Enumerable.WhereSelectEnumerableIterator`2.MoveNext()
at Microsoft.FSharp.Collections.SeqModule.Iterate[T](FSharpFunc`2 action, IEnumerable`1 source)
at <StartupCode$FSI_0015>.$FSI_0015.main#() in C:\Users\JDKS\Library\query expressions.fsx:line 144
Stopped due to error
My guess is that I don't truly understand what's going on under the hood enough to work out the error or lead myself to a solution. Is it possible to query for what I want here? Is there a workaround?

When I tried running it locally I got this exception
System.Exception: Grouping over multiple tables is not supported yet
I am not sure why you would be getting the less pretty version of that error. But if you pull the GroupBy out of the query, things seem to work fine.
query {
for student in db.Student do
join course in db.CourseSelection
on (student.StudentID = course.StudentID)
select (student.Name, course.Id)
}
|> Seq.countBy snd
|> Seq.iter (fun (student, classCount) -> printfn "Student %s has %i classes" student classCount)

I believe that the reason why your first query is not working is because of the groupBy student into group.
I think what you actually want to write is groupBy student.StudentID into group.

I found an answer from a similar question:
query {
for student in db.Student do
join course in db.CourseSelection
on (student.StudentID = course.StudentID)
let count = query {
for c in db.CourseSelection do
where (c.StudentID = student.StudentID)
select c
count
}
select (student.Name, count)
distinct
}
|> Seq.iter (fun (student, classCount) -> printfn "Student %s has %i classes" student classCount)
The underlying paradigm of these query expressions still eludes me.
EDIT:
I found that you can join implicitly without needing to use the join operator
query {
for student in db.Student do
let count = query {
for course in student.CourseSelection do
count
}
select (student.Name, count)
}
|> Seq.iter (fun (name, count) -> printfn "%s has %i courses" name count)

Related

check resultset for emptiness in clojure jdbc

i'm using clojure with jdbc, compojure, cheshire, postgresql, c3p0, tryin make REST. When i'm using this code as handler
(defn get-document [id]
(sql/query (db-connection)
["select * from document where id = cast(? as integer)" id]
{:row-fn
(fn [first]
(if (empty? first )
(response "empty")
(response first)
))}))
If reslutset is not empty i have response as i need, but if its empty i got empty brackets [].
Also this is my project dependencies
:dependencies [[org.clojure/clojure "1.8.0"]
[compojure "1.5.1"]
[ring/ring-json "0.4.0"]
[c3p0/c3p0 "0.9.1.2"]
[ring/ring-defaults "0.2.1"]
[org.clojure/java.jdbc "0.7.3"]
[org.postgresql/postgresql "42.1.4"]
[cheshire "5.8.0"]]
The :row-fn function is executed for each row in the result set. When your result set is empty, row-fn is not called.
You may need to use the result of sql/query to handle the response of the query. When there is no result an empty vector is returned (this is []). You can check if the result is empty by calling empty?. Something like this:
(defn get-document [id]
(let [query ["select * from document where id = cast(? as integer)" id]
rows (sql/query (db-connection) query)]
(if (empty? rows)
(response "empty")
(response (first rows)))))

how to get the column name list from a table in clojure

I am using clojure.java.jdbc, and want to get the column name list from a table, but how should I do it in clojure? I found there is a function of result-set-read-column, and I guess I could use it, but I don't understand the function fully, could any one give an example?
result-set-read-column is for transforming values, not what you want in this case.
This is the easiest way I can think of right now:
(defn column-names [db table]
(let [rs (clojure.java.jdbc/query db (str "select * from " table " limit 1"))]
(-> rs first keys)))
The :as-arrays? option of query returns the resultset as arrays, with the first array being the column names, the next arrays having the field information. This also works without results
(j/query db-spec
["select TOP 1 * from person"]
:as-arrays? true)
[[:surname :first-name :age :gender]
["Doe" "John" 32 "male"]]
(j/query db-spec
["select TOP 0 * from person"]
:as-arrays? true)
[[:surname :first-name :age :gender]]
If you want to process the resultset yourself, you can use db-with-resultset which takes a db-spec, a query vector, and a function to process the resultset. Be sure to realise everything in that function with a doall or mapv to prevent lazy processing and the resultset being closed before processing has finished.
(defn get-field-info
[rs]
(let [m (.getMetaData rs)]
(into {} (mapv (fn [c] [(.getColumnName m c)
(.getColumnTypeName m c)])
(range 1 (inc (.getColumnCount m)))))))
(j/db-query-with-resultset db-spec
["select TOP 0 * from person"]
get-field-info)
{"surname" "nvarchar","first-name" "nvarchar","age" "int","gender" "nvarchar"}
Be aware these options only return the metadata of the query resultset, not of the table itself !
(j/query db-spec
["select TOP 0 surname, first-name from person"]
:as-arrays? true)
[[:surname :first-name]]
If you want to query the metadata of the database structure itself without using queries, you can use with-database-metadata and metadata-query from clojure.java.jdbc. The following will give all column names from the "person" table:
(j/with-db-metadata [m db-spec]
(->> (.getColumns m "mycatalog" nil "person" nil)
(j/metadata-query)
(map :column_name)))
["surname" "first-name" "age" "gender"]
The with-db-metadata binds the DataBaseMetaData of a db-spec to a var, so it only needs to be evaluated once, and the metadata-query processes the returns of a Java DatabaseMetaData method call into a clojure data structure.

How to map an IGrouping<Key, ValueA> to a IGrouping<Key, ValueB>?

Given a function f:ValueA -> ValueB, how could I map an IGrouping of type IGrouping<Key, ValueA> to IGrouping<Key, ValueB>?
Problem instance:
Say you have this type:
TaggedItem = { Tag:Tag ; Item:Item }
and this query:
query {
for i in taggedItems
groupBy i.Tag into g
select g
}
This would give you a seq of type: IGrouping<Tag, TaggedItem>, but I really want a seq of type: IGrouping<Tag, Item>.
The mapping function is: fun taggedItem -> taggedItem.Item
Solution
The solution is to avoid the mapping of groupings and instead do the transformation while doing the group, using groupValBy, as pointed by the selected answer. The selected answer also shows how to do the mapping from one type of grouping to the other, if you insist.
query {
for i in taggedItems
groupValBy i.Tag i.Item into g
select g
}
How about this?
let mapGrouping f (xs : IGrouping<_,_>) =
let projection = xs |> Seq.map (fun x -> xs.Key, f x)
(projection.GroupBy (fst, snd)).First()
From your code example, I think you want this:
query {
for i in taggedItems do
groupValBy i.Item i.Tag into g
select g
}
At https://msdn.microsoft.com/en-us/library/hh225374.aspx, we learn that groupValBy "selects a value for each element selected so far and groups the elements by the given key."

Translating Linq to a FSharp expression

The DocumentDB has a walk-though and sample project in C# that I am working through in FSharp. One of the 1st tasks is to locate an existing datbase. The C# code is this
var database = client.CreateDatabaseQuery().Where(db => db.Id == "FamilyRegistry").ToArray().FirstOrDefault();
I am attempting to do the same line in FSharp but I am not getting the .Where even though I am referencing the same libraries. Instead I am getting this:
Am I thinking about the problem wrong? Thanks in advance.
Linq isn't specific to C#. Even in C# you need to add a reference to System.Linq.dll and a using System.Linq; statement. The C# project templates already include these statements.
In F# you need to do the same, ensure your project has a reference to System.Linq and add an open System.Linq statement
There are at least two more idiomatic ways:
You can use the Seq module's functions with the pipeline operator achieve the same result as method chaining, eg:
let random = new System.Random()
Seq.initInfinite (fun _ -> random.Next())
|> Seq.filter (fun x -> x % 2 = 0)
|> Seq.take 5
|> Seq.iter (fun elem -> printf "%d " elem)
printfn ""
seq<'T> is a synonym of IEnumerable<T> so if you apply the methods to an IQueryable it will force the query's execution.
You can use Query Expressions, equivalent to LINQ's SQL-like syntax:
let countOfStudents =
query {
for student in db.Student do
select student
count
}
query returns a proper IQueryable<T>
Your specific query could be something like this:
let database =
query {
for db in client.CreateDatabaseQuery()
where db.Id == "FamilyRegistry"
select db
headOrDefault
}

The value or constructor query is not defined

I have a query
query {
for user in db.Users do
join (for selection in db.CourseSelection ->
user.UserID = selection.UserID)
select (user, selection)
}
|> Seq.iter (fun (user, selection) -> printfn "%d %s %d" user.UserID user.Name selection.GroupID)
Why it says "The value or constructor 'query' is not defined" ?
I have implemented the Linq library
open System
open System.Linq
or is there a better way to do it in builtin way?
Like built-in query expressions?
Here is the documentation and tutorial on F# query expressions:
http://msdn.microsoft.com/en-us/library/vstudio/hh225374(v=vs.110).aspx
It states that "Projects should add references to System.Data, System.Data.Linq, and FSharp.Data.TypeProviders assemblies."
Also in a sample it refer to these namespaces:
open Microsoft.FSharp.Data.TypeProviders
open System.Data.Linq.SqlClient
open System.Linq
open Microsoft.FSharp.Linq

Resources