Can this query be any more DRY? - sql

I've just checked out the ON DUPLICATE KEY UPDATE for MySQL.
Here is an example query.
$query = 'INSERT INTO `activities`
(`id`,
`hole_id`,
`name_id`,
`start_depth`,
`end_depth`,
`start_time`,
`end_time`
) VALUES (
:id,
:hole_id,
:name_id,
:start_depth,
:end_depth,
:start_time,
:end_time
) ON DUPLICATE KEY UPDATE
`id` = :id,
`hole_id` = :hole_id,
`name_id` = :name_id,
`start_depth` = :start_depth,
`end_depth` = :end_depth,
`start_time` = :start_time,
`end_time` = :end_time
';
There is a lot of repetition there obviously.
Is there a way to say "insert, or if exists use the existing information to update".
I've looked at REPLACE, and it says it inserts and deletes if neccessary. The docs say to insert or update to use the method I've used above.
So can I eliminate doubling up of all that update info?

You can use the VALUES() function to refer to the value of a column rather than repeating the value in the ON DUPLICATE KEY portion. See: http://dev.mysql.com/doc/refman/5.1/en/miscellaneous-functions.html#function_values.
For example:
$query = 'INSERT INTO `activities`
(`id`,
`hole_id`,
`name_id`,
`start_depth`,
`end_depth`,
`start_time`,
`end_time`
) VALUES (
:id,
:hole_id,
:name_id,
:start_depth,
:end_depth,
:start_time,
:end_time
) ON DUPLICATE KEY UPDATE
`id` = VALUES(id),
`hole_id` = VALUES(hole_id),
`name_id` = VALUES(name_id),
`start_depth` = VALUES(start_depth),
`end_depth` = VALUES(end_depth),
`start_time` = VALUES(start_time),
`end_time` = VALUES(end_time)
';

Related

Active record query for multiple conditions on child association

I need help with a correlated subquery.
An item has many facets. The attributes of a facet are identifier and content. The facet identifier and content are used to provide options to filter what items are displayed - for example, the identifier could be 'color' and the content 'red' or identifier is 'size' and content is '10`
I want a query that will return the item where
(label contains a search term)
OR
( has a facet with an identifier equal to one of 2 given values AND the content includes the search term)
AND
(it ALSO has ANOTHER facet whose identifier is equal to one of a number of some different values AND content equal to one of a number of yet more different values).
The rails query I've tried
rails query
items.joins(:facets)
.where('items.label LIKE ? OR facets.identifier LIKE ?', "%#{search_term}%", "%#{search_term}%")
.where('facets.identifier' => filter).where('facets.content' => value).uniq
which translates into the following sql query
SELECT DISTINCT (`items`.`id`) FROM `items` INNER JOIN `facets` ON
`facets`.`item_id` = `items`.`id` AND `facets`.`identifier` IN ('summary', 'description') AND
(items.label LIKE '%outing%' OR facets.content LIKE '%outing%') AND
`facets`.`identifier` = 'color' AND `facets`.`content` IN
('red')
This returns an empty array because I think there is a conflict between
`facets`.`identifier` IN ('summary', 'description')
and
`facets`.`identifier` = 'color'
How do I write a query that will return an item on the basis of more than one of its facets. And is it possible to write this as an active record as opposed to raw sql?
I've identified an approach that works though it's not necessarily optimum:
class ItemProvider
attr_reader :keyword, :filters
FACET_CONTENT_TO_SEARCH = %w[summary description]
def initialize(keyword, filters)
#keyword = keyword
#filters = filters
end
def retrieve
#items = Item.all
filter_by_keyword
filter_items
end
private
def filter_by_keyword
return #items if keyword.blank?
#items = #items
.joins(:facets)
.where('facets.identifier' => FACET_CONTENT_TO_SEARCH)
.where('items.label LIKE ? OR facets.content LIKE ?', "%#{keyword}%", "%#{keyword}%").uniq
end
def filter_items
return #items if filters.blank?
filters_to_hash.each do |filter, value|
#items = Item
.unscoped
.select("items.*")
.joins("INNER JOIN facets ON facets.facetable_id = items.id AND facets.facetable_type = 'Item'")
.where('facets.identifier' => filter)
.where('facets.content' => value)
end
end
def filters_to_hash
filters.reduce({}) do |acc, filter|
if acc[filter[:identifier]]
acc[filter[:identifier]] << filter[:value]
else
acc[filter[:identifier]] = [filter[:value]]
end
acc
end
end
end

ActiveRecord Create ignores value

I am doing this
CasPgtiou.create({:pgt_iou => "a", :pgt_id => "b"})
which results in
INSERT INTO `cas_pgtious` (`created_at`, `pgt_id`, `pgt_iou`, `updated_at`) VALUES ('2015-06-25 02:22:55', NULL, NULL, '2015-06-25 02:22:55')
Mysql2::Error: Column 'pgt_id' cannot be null: INSERT INTO `cas_pgtious` (`created_at`, `pgt_id`, `pgt_iou`, `updated_at`) VALUES ('2015-06-25 02:22:55', NULL, NULL, '2015-06-25 02:22:55')
ActiveRecord::StatementInvalid: Mysql2::Error: Column 'pgt_id' cannot be null: INSERT INTO `cas_pgtious` (`created_at`, `pgt_id`, `pgt_iou`, `updated_at`) VALUES ('2015-06-25 02:22:55', NULL, NULL, '2015-06-25 02:22:55')
Even though the value is there the create function is not taking it.
Also the model I have is like this
class CasPgtiou < ActiveRecord::Base
attr_accessible :pgt_iou, :pgt_id
end
Rails version is 3.1.3. I'm not sure why a thing as simple as this would fail.
PS I have tried create this way also. But same error
CasPgtiou.create(:pgt_iou => "a", :pgt_id => "b")
UPDATE
This thing strangely works
pgtiou = CasPgtiou.new
pgtiou[:pgt_iou] = pgt_iou
pgtiou[:pgt_id] = pgt
pgtiou.save!

How to get multiple table values to gridview?

I have two tables and i need to filter and put those two table data to gridview.i use joined two table like this
$student=new Student;
$marks=new AssimentMarks;
$criteria_st=new CDbCriteria;
$criteria=new CDbCriteria;
$criteria->select = 't.st_id,t.st_name,stu.ass_id,stu.marks_cr1,stu.marks_cr2,stu.marks_cr3,stu.marks_cr4,stu.marks_cr5';
$criteria->join = 'INNER JOIN assiment_marks stu ON stu.st_id=t.st_id';
$criteria->condition = 'stu.ass_id=:ass_id';
$criteria->params = array(':ass_id'=>Yii::app()->session['modelcrite']['ass_id']);
$criteria->addInCondition('t.st_id', $studentid);
return new CActiveDataProvider($student, array('criteria'=>$criteria,));
but in gridviwe only show the student database values.it is show as .how can i pass two models to CActiveDataProvider ?
this is how data shows
http://i.stack.imgur.com/Kogjz.jpg
1) Foreign key in schema
Your assiment table should reference your student table with a foreign key, like this (assuming SQLite):
CREATE TABLE student (
id INTEGER PRIMARY KEY,
name TEXT
);
CREATE TABLE assiment_record(
id INTEGER PRIMARY KEY,
student_id INTEGER,
cr1 INTEGER,
cr2 INTEGER,
cr3 INTEGER,
cr4 INTEGER,
cr5 INTEGER,
FOREIGN KEY( student_id ) REFERENCES student(id) -- FK goes in the CHILD table.
);
2) Generate models with Gii:
student -> models/Student.php
assiment_record -> models/AssimentRecord.php
Gii is smart and guesses your relations:
// AssimentRecord.php looks good! No changes made!
public function relations()
return array(
'student' => array(self::BELONGS_TO, 'Student', 'student_id'),
);
The assiment record BELONGS TO the student.
3) Tweak gii's output (Student.php)
But Gii's is not perfect, and does not know what your intentions are. The current student relation (HAS_MANY) returns an array, but I don't want to deal with the array (because I'm lazy!), so I change it into a HAS_ONE-relation instead:
// Student.php
public function relations()
{
return array(
// Change this: 'assimentRecords' => array(self::HAS_MANY, 'AssimentRecord', 'student_id'),
/* into this: */ 'assimentRecord' => array(self::HAS_ONE, 'AssimentRecord', 'student_id'),
);
}
The student HAS ONE assiment record.
4) Display the grid:
// views/site/index.php (Or wherever.)
$dataProvider = new CActiveDataProvider( 'Student' );
$this->widget('zii.widgets.grid.CGridView', array(
'dataProvider'=>$dataProvider,
'columns' => array(
'id',
'name',
'assimentRecord.cr1',
'assimentRecord.cr2',
'assimentRecord.cr3',
'assimentRecord.cr4',
'assimentRecord.cr5',
)
));

Rails Arel equivalent of this complex sql query

Here is the original logic
(scrape_datas = ScrapeData.find(
:all, :conditions =>
"artist_status = 'NOT_FOUND'
AND blacklisted = 1
AND extracted = 0
and not EXISTS(
SELECT * FROM artist_name_suggestions where original = artist_name
)
I've been able to split up the first part better
scrape_datas = ScrapeData.where(
:artist_status => 'NOT_FOUND',
:blacklisted => 1,
:extracted => 0
)
Although having issues getting the "and not EXISTS" query into the mix
and not EXISTS(
SELECT * FROM artist_name_suggestions where original = artist_name
)
Thanks!
Firstly you can extract simple scopes:
scope :not_found, where(:artist_status => 'NOT_FOUND')
scope :blacklisted, where(:blacklisted => 1)
scope :extracted, where(:extracted => 0)
Then add a query method (assume artist_name is a column of scrape_datas):
def self.no_suggestions
scrape_datas = ScrapeData.arel_table
suggestions = ArtistNameSuggestion.arel_table
where(ArtistNameSuggestion.where(
suggestions[:original].eq(scrape_datas[:artist_name])
).exists.not)
end
Now you can do something like this:
ScrapeData.not_found.blacklisted.extracted.no_suggestions

pulling one field of a record into an array in a Rails 3 app

Rails 3 noob here. Currently the code in my controller below is getting the whole record in my database. I am trying to populate the array with one integer, not the whole record. The integer is contained in a table "answers" and the field name is "score". How can i modify this line in my controller to get just the one field?
#questions.each do |s|
#ans[s.position] = Answer.where("question_id = ? AND user_id = ?", s.q_id, current_user.id )
end
UPDATE FOR CLARIFICATION: The :score can be any integer from 0 to 5. I would like to populate #ans[s.position] with the integer.
Thanks.
You're very close
#questions.each do |s|
#ans[s.position] = Answer.where("question_id = ? and user_id = ?",s.q_id,current_user.id).select(:score).first.try(:score)
end
You need to select "score" from Answer, then you need to retrieve it from the object.
Since where could potentially return many Answers, use first to pull off the first Answer, and then score to project out the score field.
answers = Answer.where("question_id = ? AND user_id = ?", s.q_id, current_user.id )
score = answers.first.score
All together it would be:
#questions.each do |s|
#ans[s.position] = Answer.where("question_id = ? AND user_id = ?", s.q_id, current_user.id ).first.score
end
As an optimization to only retrieve score from the database, instead of answers.*, you could use select(:score).
#questions.each do |s|
#ans[s.position] = Answer.where("question_id = ? AND user_id = ?", s.q_id, current_user.id ).select(:score).first.score
end
See Active Record Query Interface for more about using select.
Just include them.
#questions = Question.includes(:answers).select(:position)
Or use this
#questions = Question.select(:position)
#questions.each{ |s| #ans[s.position] = s.answers.where(:user_id => current_user.id) }