PySpark SQL Joining Tables - join
I have to provide the names of the top 10 users who provided the most tips from the Yelp dataset using the tip and user tables. When I run tip.printSchema() I get:
root
|-- business_id: string (nullable = true)
|-- compliment_count: long (nullable = true)
|-- date: string (nullable = true)
|-- text: string (nullable = true)
|-- user_id: string (nullable = true)
And the user.printSchema() returns:
root
|-- average_stars: double (nullable = true)
|-- compliment_cool: long (nullable = true)
|-- compliment_cute: long (nullable = true)
|-- compliment_funny: long (nullable = true)
|-- compliment_hot: long (nullable = true)
|-- compliment_list: long (nullable = true)
|-- compliment_more: long (nullable = true)
|-- compliment_note: long (nullable = true)
|-- compliment_photos: long (nullable = true)
|-- compliment_plain: long (nullable = true)
|-- compliment_profile: long (nullable = true)
|-- compliment_writer: long (nullable = true)
|-- cool: long (nullable = true)
|-- elite: string (nullable = true)
|-- fans: long (nullable = true)
|-- friends: string (nullable = true)
|-- funny: long (nullable = true)
|-- name: string (nullable = true)
|-- review_count: long (nullable = true)
|-- useful: long (nullable = true)
|-- user_id: string (nullable = true)
|-- yelping_since: string (nullable = true)
So I need to count which user_id's had the most tips from the tip table and join that with the name's in the user table. I have tried this query to get it started:
query = """
SELECT tip.user_id, count(*), user.name
FROM tip
GROUP BY user_id
LIMIT 10
LEFT JOIN user
ON tip.user_id = user.name
"""
spark.sql(query).show()
because I want to get just the top ten user_id's from tip and match them with their names from user. This returns an error however:
Py4JJavaError: An error occurred while calling o24.sql.
: org.apache.spark.sql.catalyst.parser.ParseException:
mismatched input 'LEFT' expecting <EOF>(line 6, pos 4)
== SQL ==
SELECT user_id, count(*)
FROM tip
GROUP BY user_id
LIMIT 10
LEFT JOIN user
----^^^
ON tip.user_id = user.name
Here are some sample rows from the tip table:
[Row(business_id='VaKXUpmWTTWDKbpJ3aQdMw', compliment_count=0, date='2014-03-27 03:51:24', text='Great for watching games, ufc, and whatever else tickles yer fancy', user_id='UPw5DWs_b-e2JRBS-t37Ag'),
Row(business_id='OPiPeoJiv92rENwbq76orA', compliment_count=0, date='2013-05-25 06:00:56', text='Happy Hour 2-4 daily with 1/2 price drinks and slushes AND after 8 half price shakes. They actually have a peanut butter and bacon shake.', user_id='Ocha4kZBHb4JK0lOWvE0sg'),
Row(business_id='5KheTjYPu1HcQzQFtm4_vw', compliment_count=0, date='2011-12-26 01:46:17', text='Good chips and salsa. Loud at times. Good service. Bathrooms AWFUL. So that tanks my view on this place.', user_id='jRyO2V1pA4CdVVqCIOPc1Q')]
And some samples from the user table:
[Row(average_stars=4.03, compliment_cool=1, compliment_cute=0, compliment_funny=1, compliment_hot=2, compliment_list=0, compliment_more=0, compliment_note=1, compliment_photos=0, compliment_plain=1, compliment_profile=0, compliment_writer=2, cool=25, elite='2015,2016,2017', fans=5, friends='c78V-rj8NQcQjOI8KP3UEA, alRMgPcngYSCJ5naFRBz5g, ajcnq75Z5xxkvUSmmJ1bCg, BSMAmp2-wMzCkhTfq9ToNg, jka10dk9ygX76hJG0gfPZQ, dut0e4xvme7QSlesOycHQA, l4l5lBnK356zBua7B-UJ6Q, 0HicMOOs-M_gl2eO-zES4Q, _uI57wL2fLyftrcSFpfSGQ, T4_Qd0YWbC3co6WSMw4vxg, iBRoLWPtWmsI1kdbE9ORSA, xjrUcid6Ymq0DoTJELkYyw, GqadWVzJ6At-vgLzK_SKgA, DvB13VJBmSnbFXBVBsKmDA, vRP9nQkYTeNioDjtxZlVhg, gT0A1iN3eeQ8EMAjJhwQtw, 6yCWjFPtp_AD4x93WAwmnw, 1dKzpNnib-JlViKv8_Gt5g, 3Bv4_JxHXq-gVLOxYMQX0Q, ikQyfu1iViYh8T0us7wiFQ, f1GGltNaB7K5DR1jf3dOmg, tgeFUChlh7v8bZFVl2-hjQ, -9-9oyXlqsMG2he5xIWdLQ, Adj9fBPVJad8vSs-mIP7gw, Ce49RY8CKXVsTifxRYFTsw, M1_7TLi8CbdA89nFLlH4iw, wFsNv-hqbW_F5-IRqfBN6g, 0Q1L7zXHocaUZ2gsG2XJeg, cBFgmOCBdhYa0xoFEAzp_g, VrD_AgiFvzqtlR15vir3SQ, cpE-7HK514Sr5vpSen9CEQ, F1UYelhPFB-zIKlt0ygIZg, CQAL1hvsLMCzuJf9AglsXw, 1KnY1wr15WfEWIRLB9IS6g, QWFQ-kXBiLbid-lm5Jr3dQ, nymT8liFugCrM16lTy0ZfQ, qj69bdd885heDvUPCyHd2Q, DySCZZcgbdrlHgEovk5y9w, lZMJIDuvhT9Dy4KyquLXyA, b_9Gn7wS93AoPZPR0dIJqQ, N07g1IaLh0_6sUjtiSRe4w, YdfPX_7DxSnKvvdCJ57iOw, 8GYryZPD22W7WgQ8kvMkEQ, cpQmAgOWatghp14h1pn1dQ, EnchhymLYMqftCRjqvVWmw, -JdfKhFktE7Zs9BMDFcPeQ, uWhC9eof98zPkvsalgaqJw, eyTlNDDaiPatfe6mheIZ0g, VfHq0o73aKsODvfAhwAQtg, kvD5tICngLAaQDujSFWupA, dXacwEhqi9-3_XT6JeH0Og, NfU0zDaTMEQ4-X9dbQWd9A, cTHWBdjSKbctSUIvWsgFxw, 3IEtCbSDF5t7RkZ20T6s9A, HJJXTrp6UybYyPdQ9DA0JA, JaXogQFVjzGRAeBvzamBHg, NUonfKkjS1iVqnNITtgXZg, D5vaJAYp0sOrGfsj9qvsMA, H27Ecbwwu4FGAlLgICourw, S8HrLmMiE4u8FWYWkNEoTw, Io36Y3xWQcIX9rYvPcYfXQ, J5mcqh8KxYpqjaLBNlwcig, -nTB3_08g06fD0GT8AtDBQ, wMpFA46lihK8oFns_5p65A, RZGFJHeomGJCWp3xcL3ejA, ZoQSzzXoSP1RxOD4Amv9Bg, qzM0EB0SkuuGIFv0adjQAQ, HuM6vvuveken-fPZ7d4olA, H3oukHpGpn9n_mJwSDSQyQ, PkmsJsQ8FIZe8eh8c_u96g, wSByVbwME4MzgkJaFyfvNg, YEVqknoDmrHAoUbHX0nPnA, li3vsK1XAPmeJYAUTYflHQ, MKc8yXi0glbPYt0Qb4PECw, fQPH6W9fksi27gkuUPnFaA, amrCMrDsoRetYFg2kwwdFA, 84dVQ6n6r2ezNaTuc7RkKA, yW9QjWY0olv5-uRKv3t_Kw, 5XJDj7c3eoidfQ3jW18Zgw, txSc6a6pIDctvwyBeu7Aqg, HFbbDCyyqP9xPkUlcxeIdg, hTUv5oh2do6Z3OppPuuiJA, gSqonG9J4fNM-fl_fE71AA, pd9mgTFpBTg5F9x-MsczNg, j3VE22V2GcHiH8UZxfFLfw, NYXlMW-T-3V4Jqr4r-i0Wg, btxgAZedxX8IWhMifA7Xkg, -Hp5mPLiRJNFnyeX5Ygzag, P6-DwVg6-t2JuQwIUEk0iQ, OI2TvxYvZrAodBG_RF53Xw, bHxf_VPKmZur1Bier-6A2A, Et_Sb39cVm81_Xe9HDM8ZQ, 5HwGl2UyYbaRq8aD6YC-fA, ZK228WMcCKLo5thcjD7rdw, iTf8wojwfm0NOi7dOiz3Nw, btYRxQYNJjpecflNHtFH0A, Kgo42FzpW_dXFgDKoewbtg, MNk_1Q_dqOY3xxHZKeO8VQ, AlwD504T9k0m5lkg3k5g6Q', funny=17, name='Rashmi', review_count=95, useful=84, user_id='l6BmjZMeQD3rDxWUbiAiow', yelping_since='2013-10-08 23:11:33'),
Row(average_stars=3.71, compliment_cool=0, compliment_cute=0, compliment_funny=0, compliment_hot=0, compliment_list=0, compliment_more=0, compliment_note=1, compliment_photos=0, compliment_plain=0, compliment_profile=0, compliment_writer=0, cool=10, elite='', fans=0, friends='4N-HU_T32hLENLntsNKNBg, pSY2vwWLgWfGVAAiKQzMng, lwhksSpgIyeYZor_HlN93w, v281gE-nk3jozr_5hWFAug, V7XFwm0baX37HRIduHmrXw, haSh72Q0MsQZUpWPeVgp0Q, bLbSNkLggFnqwNNzzq-Ijw, EKnCY7Tfxts4dexPrz63OQ, u_wqt9RshdZsoj8ikLqoEQ, J8FKQM1yvbwoKcuWRNh1yw, S9tC7Bp2sOLF_nwH-ksiIg, vNGs6_DP7ZbtPwX7finVIQ, 1IQ_d1RuMj8iIpcF2CDohA, WJqVJqhh7vNX51xLHEMEoQ, 5OllWsrKJsYo3XQK6siRKA', funny=8, name='David', review_count=16, useful=28, user_id='bc8C_eETBWL0olvFSJJd0w', yelping_since='2013-10-04 00:16:10')]
The SQL logic first finds the top 10 users with the most tips from the tip table and joins it with the user table. Left or right side of a SQL join can be another SQL expression.
Example
The examples creates dataframes with minimal information needed to illustrate query working.
tip = spark.createDataFrame([("UPw5DWs_b-e2JRBS-t37Ag",), ("UPw5DWs_b-e2JRBS-t37Ag",), ("another_user_id",)], ("user_id",))
user = spark.createDataFrame([("UPw5DWs_b-e2JRBS-t37Ag", "u1", ), ("another_user_id", "u2",),], ("user_id", "name"))
tip.registerTempTable("tip")
user.registerTempTable("user")
sql = """
SELECT u.*
FROM user u
JOIN (SELECT user_id,
Count(user_id) AS tip_count
FROM tip
GROUP BY user_id
ORDER BY tip_count DESC
LIMIT 10) t
ON t.user_id = u.user_id
"""
spark.sql(sql).show(200, False)
Output
+----------------------+----+
|user_id |name|
+----------------------+----+
|UPw5DWs_b-e2JRBS-t37Ag|u1 |
|another_user_id |u2 |
+----------------------+----+
Related
Trying to perform soft delete of entity with relations causes exception
Recently, I implemented soft delete for most of my entities in the app (at least the ones I needed). The implementation looks like this: Goal.groovy class Goal { String definition; Account account; boolean tmpl = false; String tmplName; Goal template Timestamp dateCreated Timestamp lastUpdated Timestamp deletedAt static belongsTo = [ account: Account, template: Goal ] static hasMany = [perceptions: Perception, sessions: RankingSession] static mapping = { autoTimestamp true table 'goal' definition type: 'text' tmplName column: '`tmpl_name`' perceptions sort:'title', order:'asc' dateCreated column: 'date_created' lastUpdated column: 'last_updated' deletedAt column: 'deleted_at' } ... def beforeDelete() { if (deletedAt == null) { Goal.executeUpdate('update Goal set deletedAt = ? where id = ?', [new Timestamp(System.currentTimeMillis()), id]) } return false } ... Perception.groovy class Perception { String title String definition Goal goal Timestamp dateCreated Timestamp lastUpdated Timestamp deletedAt static hasMany = [left: Rank, right: Rank] static mappedBy = [left: "left", right: "right"] static belongsTo = [goal: Goal] static namedQueries = { notDeleted { isNull 'deletedAt' } } static mapping = { autoTimestamp true table 'perception' definition type: 'text' dateCreated column: 'date_created' lastUpdated column: 'last_updated' deletedAt column: 'deleted_at' } static constraints = { title blank: false, size: 1..255 definition nullable: true, blank: true, size: 1..5000 goal nullable: false lastUpdated nullable: true deletedAt nullable: true } /** * before delete callback to prevent physical deletion * * #return */ def beforeDelete() { if (deletedAt == null) { Perception.executeUpdate('update Perception set deletedAt = ? where id = ?', [new Timestamp(System.currentTimeMillis()), id]) } return false } } Rank.groovy class Rank { Perception left Perception right Integer leftRank Integer rightRank RankingSession session static belongsTo = [session: RankingSession] static mapping = { table 'rank' } static constraints = { leftRank range: 0..1, nullable: true rightRank range: 0..1, nullable: true left nullable: false right nullable: false session nullable: false } } My problem happens on deletion (logical deletion). I perform the deletion through service class the following way: GoalService.groovy #Transactional class GoalService { /** * Deletes goal * * #param goal * #return */ def deleteGoal(Goal goal) { if (goal.tmpl == true) { throw new ValidationException("Provided object is a template!") } def perceptions = Perception.notDeleted.findAllByGoal(goal) for (perception in perceptions) { perception.delete() } goal.delete() } } I have a use-case, which work under one condition and trows an exception under another. #1 Existing Goal with number of assigned perceptions to it. Deletion works as expected: The Goal and Perceptions are marked as deleted. #2 Goal with Perceptions + Number of Rank objects linked to Perceptions. When I am trying to delete such a goal, I am getting an exception: Error 2015-03-23 14:52:10,294 [http-nio-8080-exec-9] ERROR spi.SqlExceptionHelper - Column 'left_id' cannot be null | Error 2015-03-23 14:52:10,357 [http-nio-8080-exec-9] ERROR errors.GrailsExceptionResolver - MySQLIntegrityConstraintViolationException occurred when processing request: [POST] /triz/rrm/goal/1/delete - parameters: SYNCHRONIZER_TOKEN: 57fda8f2-8025-45e0-ac60-592234f54ef1 SYNCHRONIZER_URI: /triz/rrm/goals Column 'left_id' cannot be null. Stacktrace follows: Message: Column 'left_id' cannot be null Line | Method ->> 411 | handleNewInstance in com.mysql.jdbc.Util - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - | 386 | getInstance in '' | 1041 | createSQLException in com.mysql.jdbc.SQLError | 4237 | checkErrorPacket in com.mysql.jdbc.MysqlIO | 4169 | checkErrorPacket . in '' | 2617 | sendCommand in '' | 2778 | sqlQueryDirect . . in '' | 2834 | execSQL in com.mysql.jdbc.ConnectionImpl | 2156 | executeInternal . in com.mysql.jdbc.PreparedStatement | 2441 | executeUpdate in '' | 2366 | executeUpdate . . in '' | 2350 | executeUpdate in '' | 129 | doCall . . . . . . in triz.rrm.RrmGoalController$_delete_closure5 | 127 | delete in triz.rrm.RrmGoalController I've already tried everything, including: physically dropped all constraints used "cascade: 'save-update'" on relationships Nothing helps, the only thing I can understand is the fact that it is related to cascading, but why is GORM trying to cascade 'delete' if in reality I am updating the objects?
You are getting the exception because Rank has a reference to Perception, but you did not specify belongsTo on either side. You have this in Rank: Perception left That is why you are getting Column 'left_id' cannot be null. Stacktrace follows.... So to solve the problem either delete rank objects having r/ship with each perception you are deleting or specify belongsTo = [left: Perception] with in Rank.
How to change parent in GORM?
I do not have too much expierence in Grails, so maybe I do not understand hasMany and belongsTo relations in GORM. Say, I have 2 classes Parent.groovy and Child.groovy class Parent { String name List childen = new ArrayList() static hasMany = [children: Child] } class Child { String name static belongsTo = [parent: Parent] } Person person1 = new Person(name: "Person1") Child child1 = new Child(name: "child1") Child child2 = new Child(name: "child2") person1.addToChildren(child1).save(flush: true) person1.addToChildren(child2).save(flush: true) Person person2 = new Person(name: "Person2").save(flush: true) Now I want to change a parent of a child child1.parent = parent2 // no effect child1.save(flush: true) In controller it is possible Child child1 = Child.get(1) bindData(child1, [parent: [id: 2]]) child1.save(flush: true) But now there is null in movie1.children, in DB I can see that parent_id has changed to 2 Note: In Active Record (Rails) it is easy child1.parent_id = 2 Maybe I do not need to use such relation if I want to change parent? Maybe there is another way to do it?
After some investigation I understood why there is a null in collection. At the very beginning there are such values in Child table id | name | parent_id | parent_idx 1 | child1 | 1 | 0 2 | child2 | 1 | 1 After bindData(child1, [parent: [id: 2]]) We have id | name | parent_id | parent_idx 1 | child1 | 2 | 0 2 | child2 | 1 | 1 So now parent2 has child1 with parent_idx 0 - it is ok. But parent1 has child2 with parent_idx = 1 (without 0). So we changed parent_id value without changing parent_idx. I wish there was a possibility to change index in collection too. So the conclusion is: I we want to change parent we should not use List collection, so we will not have idx column and no problems
Grails: writing HQL query when domains are not directly matched
I have a domain class named Logging which stores an id of another domain class: Organization The structure of both domains is provided: class Logging { Date dateCreated long user_id long organization_id String memberCode static constraints = { user_id(nullable: false) organization_id(nullable: false) memberCode(nullable: true) } } class Organization { Type type String name String memberCode User manager String collateralAutoEmails boolean isBlocked = true static constraints = { name(blank: false, unique: true) manager(nullable: true) memberCode(nullable: true) collateralAutoEmails(nullable: true) } static mapping = { manager(lazy: false) } } User enters several parameters: dateCreated, the memberCode and the name of the organization. I need to select all elements from the Logging domain matching these criterias. The tricky part for me is writing the query for the name of the organisation parameter. According to the search rules I should check whether organization.name field contains data entered by user as a substring(case insensetive) and select the corresponding element from the Logging domain. The two domains are not mapped directly and I can't join those tables.I have tried different approaches but still haven't found the solution.
Here you go Logging.executeQuery("Select l from Logging l, Organization o where l.organization_id = o.id and o.dateCreated = :dateCreated and o.memberCode = :memberCode and o.name = :name", [dateCreated: dateCreated, memberCode: memberCode, name: name])
Try something like this: Organization.executeQuery("select o from Organization o, Logging l where o.name like = :orgName AND o.id=l.organization_id", [orgName : orgName ]) I didn't tried it, if it works then more search options can be added on the query, and also % can be added on the parameter, in order to enhance the search.
grails: converting SQL into domain classes
I am developing a GRAILS application (I'm new to GRAILS and inherited the project from a previous developer). I'm slowly getting a small grasp for how GRAILS operates and the use of DOMAIN classes, hibernate etc. The MySQL db is hosted on Amazon and we're using ElasticCache. Do any of you more knowledgeable folks know how I can go about converting the following SQL statement into domain classes and query criteria. if(params?.searchterm) { def searchTerms = params.searchterm.trim().split( ',' ) def resultLimit = params.resultlimit?: 1000 def addDomain = '' if (params?.domainname){ addDomain = " and url like '%${params.domainname}%' " } def theSearchTermsSQL = "" /* * create c.name rlike condition for each search term * */ searchTerms.each{ aSearchTerm -> if( theSearchTermsSQL != '' ){ theSearchTermsSQL += ' or ' } theSearchTermsSQL += "cname rlike '[[:<:]]" + aSearchTerm.trim() + "[[:>:]]'" } /* * build query * */ def getUrlsQuery = "select u.url as url, c.name as cname, t.weight as tweight from (category c, target t, url_meta_data u ) where (" + theSearchTermsSQL + ") and t.category_id = c.id and t.url_meta_data_id = u.id and u.ugc_flag != 1 " + addDomain + " order by tweight desc limit " + resultLimit.toLong() /* * run query * */ Sql sqlInstance = new Sql( dataSource ) def resultsList = sqlInstance.rows( getUrlsQuery ) } The tables are as follows (dummy data): [Category] id | name ----------- 1 | small car 2 | bike 3 | truck 4 | train 5 | plane 6 | large car 7 | caravan [Target] id | cid | weight | url_meta_data_id ---------------------------------------- 1 | 1 | 56 | 1 2 | 1 | 76 | 2 3 | 3 | 34 | 3 4 | 2 | 98 | 4 5 | 1 | 11 | 5 6 | 3 | 31 | 7 7 | 5 | 12 | 8 8 | 4 | 82 | 6 [url_meta_data] id | url | ugc_flag --------------------------------------------- 1 | http://www.example.com/foo/1 | 0 2 | http://www.example.com/foo/2 | 0 3 | http://www.example.com/foo/3 | 1 4 | http://www.example.com/foo/4 | 0 5 | http://www.example.com/foo/5 | 1 6 | http://www.example.com/foo/6 | 1 7 | http://www.example.com/foo/7 | 1 8 | http://www.example.com/foo/8 | 0 domain classes class Category { static hasMany = [targets: Target] static mapping = { cache true cache usage: 'read-only' targetConditions cache : true } String name String source } class Target { static belongsTo = [urlMetaData: UrlMetaData, category: Category] static mapping = { cache true cache usage: 'read-only' } int weight } class UrlMetaData { String url String ugcFlag static hasMany = [targets: Target ] static mapping = { cache true cache usage: 'read-only' } static transients = ['domainName'] String getDomainName() { return HostnameHelper.getBaseDomain(url) } } Basically, a url from url_meta_data can be associated to many categories. So in essence what I'm trying to achieve should be a relatively basic operation...to return all the urls for the search-term 'car', their weight(i.e importance) and where the ugc_flag is not 1(i.e the url is not user-generated content). There are 100K + of records in the db and these are imported from a third-party provider. Note that all the URLs do belong to my client - not doing anything dodgy here. Note the rlike I've used in the query - I was originally using ilike %searchterm% but that would find categories where searchterm is part of a larger word, for example 'caravan') - unfortunately though the rlike is not going to return anything if the user requests 'cars'. I edited the code - as Igor pointed out the strange inclusion originally of 'domainName'. This is an optional parameter passed that allows the user to filter for urls of only a certain domain (e.g. 'example.com')
I'd create an empty list of given domain objects, loop over the resultsList, construct a domain object from each row and add it to a list of those objects. Then return that list from controller to view. Is that what you're looking for?
1) If it's a Grails application developed from a scratch (rather than based on a legacy database structure) then you probably should already have domain classes Category, Target, UrlMetaData (otherwise you'll have to create them manually or with db-reverse-engineer plugin) 2) I assume Target has a field Category category and Category has a field UrlMetaData urlMetaData 3) The way to go is probably http://grails.org/doc/2.1.0/ref/Domain%20Classes/createCriteria.html and I'll try to outline the basics for your particular case 4) Not sure what theDomain means - might be a code smell, as well as accepting rlike arguments from the client side 5) The following code hasn't been tested at all - in particular I'm not sure how disjunction inside of a nested criteria works or not. But this might be suitable a starting point; logging sql queries should help with making it work ( How to log SQL statements in Grails ) def c = Target.createCriteria() //create criteria on Target def resultsList = c.list(max: resultLimit.toLong()) { //list all matched entities up to resultLimit results category { //nested criteria for category //the following 'if' statement and its body is plain Groovy code rather than part of DSL that translates to Hibernate Criteria if (searchTerms) { //do the following only if searchTerms list is not empty or { // one of several conditions for (st in searchTerms) { // not a part of DSL - plain Groovy loop rlike('name', st.trim())) //add a disjunction element } } } urlMetaData { //nested criteria for metadata ne('ugcFlag', 1) //ugcFlag not equal 1 } } order('weight', 'desc') //order by weight } Possibly the or restriction works better when written explicitly if (searchTerms) { def r = Restrictions.disjunction() for (st in searchTerms) { r.add(new LikeExpression('name', st.trim())) } instance.add(r) //'instance' is an injected property } Cheers, Igor Sinev
Can I Do This In Grails 1: Create Data Entry Form Dynamicly
I'm considering using Grails for my current project. I have a couple of requirements that I'm hoping I can do in Grails. First, I have the following database table: TagType --------- tag_type_id tag_type Sample Data: TagType -------------------- 1,title 2,author Based on that data, I need to generate a data entry form like this which will save its data to another table. Tile _ _ _ _ _ _ _ _ _ _ _ Author _ _ _ _ _ _ _ _ _ _ _ save cancel Can I do that in Grails? Can you point me in the right direction? Thanks! More Details I'm building a digital library system that supports OIA-PMH which is a standard for sharing metadata about documents. The standard states that every element is optional and repeatable. To support this requirement I have the following database design. I need to generate the user GUI (data entry form) based primarily on the contents of the TagType Table (see above). The data from the form then get's saved to the Tags (if the tag is new) and Item_Tags tables. Items --------- item_id last_update Tags -------- tag_id tag_type_id tag TagType --------- tag_type_id tag_type Item_tags --------- item_id tag_id Sample Data: Items ------------------ 1,2009-06-15 Sample Data: TagType -------------------- 1,title 2,author Sample Data: Tags ------------------ 1,1,The Definitive Guide to Grails 2,2,Graeme Rocher 3,2, Jeff Brown Sample Data: Item_tags ----------------------- 1,1 1,2 1,3
I am not completely sure what you are asking here in regards to "save its data to another table", but here are some thoughts. For the table you have, the domain class you'd need is the following: class Tag { String type } ID field will get generated for you automatically when you create the scaffolding. Please add more information to your question if this is insufficient.
I'm really liking grails. When I first started playing with it a couple of weeks ago, I didn't realize that it's a full fledged language. In fact it's more than that. It's a complete web stack with a web server and a database included. Anyway, the short answer to my question is yes. You might even say yes, of course! Here's the code for the taglib I created: import org.maflt.flashlit.pojo.Item import org.maflt.flashlit.pojo.ItemTag import org.maflt.flashlit.pojo.Metacollection import org.maflt.flashlit.pojo.SetTagtype import org.maflt.flashlit.pojo.Tag import org.maflt.flashlit.pojo.Tagtype /** * #return Input form fields for all fields in the given Collection's Metadataset. Does not return ItemTags where TagType is not in the Metadataset. * * In Hibernate, the * * "from ItemTag b, Tag a where b.tag= a" * * query is a cross-join. The result of this query is a list of Object arrays where the first item is an ItemTag instance and the second is a Tag instance. * * You have to use e.g. * * (ItemTag) theTags2[0][0] * * to access the first ItemTag instance. * (http://stackoverflow.com/questions/1093918/findall-not-returning-correct-object-type) **/ class AutoFormTagLib { def autoForm = {attrs, body -> //def masterList = Class.forName(params.attrs.masterClass,false,Thread.currentThread().contextClassLoader).get(params.attrs.masterId) def theItem = Item.get(attrs.itemId) def theCollection = Metacollection.get(attrs.collectionId) def masterList = theCollection.metadataSet.setTagtypes def theParams = null def theTags = null def itemTag = null def tag = null masterList.each { theParams = [attrs.itemId.toLong(),it.tagtype.id] theTags = ItemTag.findAll("from ItemTag d, Item c, Tag b, Tagtype a where d.item = c and d.tag = b and b.tagtype = a and c.id=? and a.id=?",theParams) for (int i=0;i<it.maxEntries;i++) { out << "<tr>\n" out << " <td>${it.tagtype.tagtype}</td>\n" out << " <td>\n" if (theTags[i]) { itemTag = (ItemTag) theTags[i][0] //item = (Item) theTags[i][1] tag = (Tag) theTags[i][2] //itemTag = (Tagtype) theTags[i][3] out << " <input name='${it.tagtype.tagtype}_${i}_${itemTag.id}' value='${tag.tag}' />\n"; } else out << " <input name='${it.tagtype.tagtype}_${i}' />\n"; out << " </td>\n" out << "</tr>\n" } } } }