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"
}
}
}
}

Resources