Clojure join multiple table - clojure

I am trying to solve the query in clojure. It has to return only one row. But it is returning multiple row. The function is picking the correct row from Customer table, but along with that it is fetching all the 3 row from queue table and all 4 row from service table. So totally I am getting 12 (1X3X4) row instead of only one. Any suggestion would be helpful. Thanks.
SELECT a.first_name, b.queue_name, c.service_name
from customer a
JOIN queue b
on a.queue_id = b.queue_id
JOIN service c
on a.service_id = c.service_id
where a.empl_id = 'BA123';
(defn query-using-map
"Generates a query using a honey sql map and returns the results"
[query-map]
(jdbc/with-db-transaction [conn {:datasource config/datasource}]
(let [sql-query (sql/format query-map)
query-result (jdbc/query conn sql-query)]
query-result)))
(defn get-servicename-queuename [emplid]
(jdbc/with-db-transaction [conn {:datasource config/datasource}]
(let [result (query-using-map
{:select [:a.first_name :b.queue_name :c.service_name]
:from [[:customer :a] [:queue :b] [:service :c]]
:join [":a.queue_id=:b.queue_id and :a.service_id=:c.service_id"]
; OR :join [[:= :a.queue_id :b.queue_id] [:= :a.service_id :c.service_id]]
:where [:= :a.empl_id emplid]})](println result))))
;(get-servicename-queuename "BA123")

Related

Is (def m (update-in m ks f & args)) a good practice?

I'm new to the world of clojure and I have a doubt.
I have a nested map such as
(def accounts (hash-map :XYZ (hash-map :balance (hash-map 171000 0 :171018 500 :171025 200)
:statement (hash-map :171018 [{:desc "purchase" :amount 200}
{:desc "deposit" :amount 700}]
:171025 [{:desc "purchase" :amount 300}]))
And I want to update the statements, so I wrote a simple function:
(defn add-statement
[account date desc amount]
(def accounts (update-in accounts [account :statement date] conj {:desc desc :amount amount}))
But I have a feeling that I'm doing that the wrong way...
You would need to change accounts to be mutable if you want to update them. The usual way to do this is by making accounts an atom. Then your function might look like this:
(defn add-statement! [account date desc amount]
(swap! accounts update-in [account :statement date]
(fn [line-items]
(conj (or line-items []) {:desc desc :amount amount}))))
This will add a statement line-item at a new or an existing date. The update-in is the same as yours, except that a line-item for a new date will be put into a vector rather than a list. (conj keeps the type, but it has to know the type: (conj nil :a) gives (:a)).
To turn accounts into an atom:
(def accounts (atom (hash-map ...)))
I notice your balances are not correct anyway. But if you are updating them be sure to do so in the same swap! function.
To answer your question, "Is (def m (update-in m ...)) a good practice?". Definitely not inside a defn. If you are thinking to put a def inside a defn use a let instead. Outside of a defn it would be fine to have a def that updates another def that has a different name, so (def m2 (update-in m1 ...)).

How to iterate over a result set and extract one particular value in clojure?

Below is my attempt to iterate over a result set and get its values
(sql/with-connection db
(sql/with-query-results rs ["select * from user where UserID=?" 10000]
(doseq [rec rs
s rec]
(println (val s))
)))
But how do you extract one particular value from it; i need only the user name field.
Can anyone please demonstarte how to do this?
The result set is a sequence of maps, so if you wanted to obtain one field (e.g. one called name) then:
(sql/with-connection db
(sql/with-query-results rs ["select * from user where UserID=?" 10000]
(doseq [rec rs]
(let [name (:name rec)]
(println "User name:" name)
(println "Full record (including name):" rec)))))
But as mentioned in the comments, if you only want name, then select name from would be the more efficient option. The code above is useful when you need the full row for something else.
The with-connection / with-query-results syntax is deprecated as of clojure.java.jdbc 3.0. Filtering results can be done much easier with the new query syntax and additional :row-fn and :result-set-fn parameters.
(query db ["select * from user"]
:row-fn :name
:result-set-fn #(doall (take 1000 (drop 10000 %))))
Be sure to make the result-set-fn realize all values, it shouldn't return a lazy sequence (hence the doall in this example).

Merge two lists of maps, combining the maps together on a specific key

I'm running two select statements against Cassandra, so instead of having a join I need to join them in code. Being relatively new to Clojure, I'm having a hard time doing this without resorting to really ugly nested loops. Furthermore, if table-b is missing a matching entry from table-a, it should add default table-b values.
The two selects each result in a list of maps (each "row" is one map). The id key is a UUID, not string.
Here's how the selects look if I def something with the same structure.
(def table-a (list {:id "105421db-eca4-4500-9a2c-08f1e09a35ca" :col-b "b-one"}
{:id "768af3f3-3981-4e3f-a93d-9758cd53a056" :col-b "b-two"}))
(def table-b (list {:id "105421db-eca4-4500-9a2c-08f1e09a35ca" :col-c "c-one"}))
I want the end result to be this:
({:id "105421db-eca4-4500-9a2c-08f1e09a35ca" :col-b "b-one" :col-c "c-one"}
{:id "768af3f3-3981-4e3f-a93d-9758cd53a056" :col-b "b-two" :col-c "default-value"})
Thanks for any help.
This can be done by splitting it into groups with the same key, merging all the like-keyed maps and then filling in the default values:
user> (->> (concat table-a table-b) ;; stat with all the data
(sort-by :id) ;; split it into groups
(partition-by :id) ;; by id
(map (partial apply merge)) ;; merge each group into a single map.
(map #(assoc % ;; fill in the missing default values.
:col-c (or (:col-c %) "default value")
:col-b (or (:col-b %) "default value"))))
({:col-c "c-one",
:col-b "b-one",
:id "105421db-eca4-4500-9a2c-08f1e09a35ca"}
{:col-c "default value",
:col-b "b-two",
:id "768af3f3-3981-4e3f-a93d-9758cd53a056"})
Using the thread-last macro ->> makes this a lot easier for me to read, though that is just my opinion. There is also likely a more elegant way to supply the default keys.

clojure: dynamically compose query with korma

I am trying to create a very simple API with korma
Users can query a database like so:
localhost:8080/my_postgres_db/users.json?where[age]=50&limit=1
Currently I am getting an error when trying to apply a where clause to an existing, composable, query.
clojure.lang.ArityException: Wrong number of args (2) passed to: core$where
The code in question:
(defn- comp-query [q [func arg]]
(let [sql-fn (ns-resolve 'korma.core (-> func name symbol))]
(sql-fn q arg)))
(defn compose-query [table col]
(reduce comp-query (select* table) col))
Usage:
(def clauses {:where {:column1 10} :fields "a,b" :limit 10 :offset 500})
(-> (compose-query table clauses) select)
Everything behaves as expected, except for where clauses. I can combine limit, offset and fields in any way I choose and I get the expected results. Only when I have a :where key in my map do I run into the error.
Am I attempting something I shouldn't? Is this bad clojure? Any help would be appreciated.
Note: I have read this SO question
Edit: from lein repl I can manually compose a query in the same fashion and it works
(where (select* "my_table") {:a 5})
Edit:
If I modify my compose-query function to this:
(defn compose-query [table col]
; remove where clause to process seperately
(let [base (reduce comp-query (select* table) (dissoc col :where))]
(if-let [where-clause (:where col)]
(-> base (where where-clause))
base)))
Everything works as expected.
The problem here is that korma.core/where is not a function and needs to be handled specially. Where can't be implemented as a function and still correctly handle things like (where query (or (= :hits 1) (> :hits 5)))
You can use where* function as you are using select*.
Just make your clause map like:
(def clauses {:where* {:column1 10} :fields "a,b" :limit 10 :offset 500})
Just a hunch; expanding some of the threading macros makes it a little hard to see if they are correct:
core> (macroexpand-1 '(-> (compose-query table clauses) select))
(select (compose-query table clauses))
core> (macroexpand-1 '(-> func name symbol))
(clojure.core/-> (clojure.core/-> func name) symbol)
core> (macroexpand-1 '(clojure.core/-> func name))
(name func)
Passing func to name looks suspicious.

Problems with "Revisiting the past" section of Datomic Tutorial

I'm having problems with the datomic tutorial at the "Revisiting the past" section http://datomic.com/company/resources/tutorial.html
For the two queries below:
query = "[:find ?c :where [?c :community/name]]";
db_asOf_schema = conn.db().asOf(schema_tx_date);
System.out.println(Peer.q(query, db_asOf_schema).size()); // 0
db_since_data = conn.db().since(data_tx_date);
System.out.println(Peer.q(query, db_since_data).size()); // 0
I have tried these commands in clojure, but cannot get them working as described in the tutorial:
(since (db conn) (java.util.Date.) )
;; It should return 0 but returns the whole database instead
(def ts (q '[:find ?when :where [?tx :db/txInstant ?when]] (db conn)))
(count (since (db conn) (ffirst (reverse (sort ts))))))
;; returns 13, but should return 0
(count (as-of (db conn) (ffirst (sort ts)))))
;; returns 13, but should return 0
I'm not too sure is this is the right behaviour, is there anything I'm doing wrong?
If you are working through the Seattle tutorial in Clojure, probably the most important single thing to know is that working Clojure code is included in the Datomic distribution. The filename is samples/seattle/getting-started.clj, and you can simply follow along at the REPL.
Two observations on the Clojure code in your question:
The since function is documented to return a database value, not a number, so the behavior you are seeing is as expected. In order to see what is in the database, you need to issue a query.
Databases do not have any documented semantics for the Clojure count function, so you should not call count on them. Again, if you want to see what is in the database, you need to issue a query, e.g.
;; Find all transaction times, sort them in reverse order
(def tx-instants (reverse (sort (q '[:find ?when :where [_ :db/txInstant ?when]]
(db conn)))))
;; pull out two most recent transactions, most recent loaded
;; seed data, second most recent loaded schema
(def data-tx-date (ffirst tx-instants))
(def schema-tx-date (first (second tx-instants)))
;; make query to find all communities
(def communities-query '[:find ?c :where [?c :community/name]])
;; find all communities as of schema transaction
(let [db-asof-schema (-> conn db (d/as-of schema-tx-date))]
(println (count (seq (q communities-query db-asof-schema)))))
;; find all communities as of seed data transaction
(let [db-asof-data (-> conn db (d/as-of data-tx-date))]
(println (count (seq (q communities-query db-asof-data)))))
;; find all communities since seed data transaction
(let [db-since-data (-> conn db (d/since data-tx-date))]
(println (count (seq (q communities-query db-since-data)))))
Hope this helps. There is also a Datomic google group if you have more questions.