rails - boolean operators in find - ruby-on-rails

params[:codes] = "9,10"
#result = Candidate.find :all,
:joins =>
params[:codes].split(',').collect {|c| ["INNER JOIN candidates_codes on candidates_codes.candidate_id = candidates.id, INNER JOIN codes on codes.code_id = candidates_codes.code_id AND codes.value = ?", c]}
Error
Association named 'INNER JOIN candidates_codes on candidates_codes.candidate_id = candidates.id, INNER JOIN codes on codes.code_id = candidates_codes.code_id AND codes.value = ?' was not found; perhaps you misspelled it?
Update
CREATE TABLE `candidates` (
 `id` int(11) NOT NULL auto_increment,
`first_name` varchar(255) collate utf8_unicode_ci default NULL,
`last_name` varchar(255) collate utf8_unicode_ci default NULL,
`mobile_number` varchar(255) collate utf8_unicode_ci default NULL,
`address` text collate utf8_unicode_ci,
`country` varchar(255) collate utf8_unicode_ci default NULL,
`created_at` datetime default NULL,
`updated_at` datetime default NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci AUTO_INCREMENT=5 ;
CREATE TABLE `candidates_codes` (
`candidate_id` int(11) default NULL,
`code_id` int(11) default NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
CREATE TABLE `codes` (
`id` int(11) NOT NULL auto_increment,
`section` varchar(255) collate utf8_unicode_ci default NULL,
`value` varchar(255) collate utf8_unicode_ci default NULL,
`created_at` datetime default NULL,
`updated_at` datetime default NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci AUTO_INCREMENT=11 ;
Hi,
I am trying to create a find which can ether be "OR" or "AND"
for example(pseudocode)
array a = (1,2)
array b = (1)
find(1 AND 2) = array a
find(1 OR 2) = array a, array b
My code currently looks like this -
#result = Code.all :joins => :candidates,
:conditions => ["codes.id IN (?)", params['searches'][:Company]],
:select => "candidates.*"
code is a table full of codes that describe a candidate,
a habtm relationship exists between code and candidate
The only way of using AND I can see in the guides is between two columns..
Many Thanks
Alex

Since the association is done with a join table, doing an AND requires an INNER JOIN, once for each term in the AND. What you're trying to do is find a given candidate that has a mapping for all of the codes.
This could get messy, since you not only have to join for each term, but also again to the codes table if you're matching on a field there, such as value.
Assuming the number of terms isn't too high, and you pass in params[:codes] = "1,5,9", and that you're trying to match on codes.value:
Candidate.find :all,
:joins =>
params[:codes].split(',').collect {|c| "INNER JOIN candidates_codes#{c} on candidates_codes#{c}.candidate_id = candidates.id INNER JOIN codes#{c} on codes#{c}.id = candidates_codes#{c}.code_id AND codes#{c}.value = c"}
...or something like that. Warning that I haven't tested that code, but give it a whirl if that's what you're looking for.
Note I've removed the substitution from the last rev (where the ? is replaced by a variable) because joins don't support this. You should first sanitize the params (i.e. make sure they are integers, or whatever), or use the protected sanitize_sql method in the model.

Related

Doctrine hydrates relations wrong, but result set after applying SQL directly is right

Basically, I'm trying to select all Users with their Permissions filtered by CurrentCompany, so if we switched CurrentCompany we should have only Users with their Permissions only for that CurrentCompany.
For testing purposes, I have added two Companies, two Users (f.i. A and B), and assigned user A to both Companies, and user B just only to one Company. Also, user A has different permissions set for each company, and user B has permissions just only for one company he added in.
While querying directly MySQL with generated SQL shows the right resultset, but the resulting Collection for User::permissions() has all permissions for all companies User assigned to, but there should be just only permissions related to the CurrentCompany.
Hope I described it well to understand.
So, in two words, the issue is that hydrated User->permissions() relation have all permissions assigned for the User for all companies he assigned to, but querying SQL directly shows the right results.
QueryBuilder + resulting DQL/SQL:
$this->createQueryBuilder('u')
->join('u.companies', 'c', Join::WITH, 'c.company = :company')
->leftJoin('u.permissions', 'p', Join::WITH, 'p.company = :company')
// ->andWhere('c.company = :company')
// ->andWhere('p.company = :company')
->setParameter('company', $this->tenant->company()->id()->toBinary())
->getQuery()
->getResult()
);
DQL:
SELECT u FROM App\Identity\Domain\User\User u INNER JOIN u.companies c WITH c.company = :company LEFT JOIN u.permissions p WITH p.company = :company
SQL:
SELECT u0_.id AS id_0, u0_.email AS email_1, u0_.username AS username_2, u0_.password AS password_3, u0_.created AS created_4, u0_.deleted AS deleted_5, u0_.active_company_id AS active_company_id_6 FROM user u0_ INNER JOIN user_company u1_ ON u0_.id = u1_.user_id AND (u1_.company_id = 0x3DF17103A3E14FD09D1CEF98D8318230) LEFT JOIN user_permission u2_ ON u0_.id = u2_.user_id AND (u2_.company_id = 0x3DF17103A3E14FD09D1CEF98D8318230) WHERE ((u0_.deleted IS NULL OR u0_.deleted > CURRENT_TIMESTAMP));
DB structure:
CREATE TABLE `user` (
`id` binary(16) NOT NULL,
`email` varchar(180) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL,
`password` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL,
`created` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
`deleted` datetime DEFAULT NULL,
`username` varchar(255) CHARACTER SET utf8 COLLATE utf8_unicode_ci DEFAULT NULL,
`active_company_id` binary(16) NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `UNIQ_8D93D649E7927C74` (`email`),
KEY `FK_USER_ACTIVE_COMPANY` (`active_company_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
CREATE TABLE `user_company` (
`user_id` binary(16) NOT NULL,
`company_id` binary(16) NOT NULL,
`email` varchar(180) COLLATE utf8mb4_unicode_ci NOT NULL,
`id` binary(16) NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `UNIQ_USER_COMPANY` (`user_id`,`company_id`),
KEY `FK_17B21745979B1AD6` (`company_id`),
CONSTRAINT `FK_17B21745979B1AD6` FOREIGN KEY (`company_id`) REFERENCES `Company` (`id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
CREATE TABLE `user_permission` (
`user_id` binary(16) NOT NULL,
`permission_id` binary(16) NOT NULL,
`id` binary(16) NOT NULL,
`company_id` binary(16) NOT NULL,
PRIMARY KEY (`id`),
KEY `FK_USER_PERMISSION` (`user_id`),
KEY `FK_USER_PERMISSION_PERMISSION` (`permission_id`),
KEY `FK_USER_PERMISSION_COMPANY` (`company_id`),
CONSTRAINT `FK_USER_PERMISSION_COMPANY` FOREIGN KEY (`company_id`) REFERENCES `company` (`id`) ON DELETE CASCADE,
CONSTRAINT `FK_USER_PERMISSION_PERMISSION` FOREIGN KEY (`permission_id`) REFERENCES `permission` (`id`) ON DELETE CASCADE,
CONSTRAINT `FK_USER_PERMISSION_USER` FOREIGN KEY (`user_id`) REFERENCES `user` (`id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
Doctrine mapping:
App\Identity\Domain\User\User:
type: entity
table: user
...
oneToMany:
permissions:
targetEntity: App\Identity\Domain\User\UserPermission
orphanRemoval: true
mappedBy: user
joinTable:
name: user_permission
cascade: [ "all" ]
companies:
targetEntity: App\Identity\Domain\UserCompany\UserCompany
mappedBy: user
joinTable:
name: user_company
cascade: [ "all" ]
App\Identity\Domain\User\UserPermission:
type: entity
table: user_permission
...
manyToOne:
user:
targetEntity: App\Identity\Domain\User\User
inversedBy: permissions
joinTable:
name: user_permission
App\Identity\Domain\UserCompany\UserCompany:
type: entity
table: user_company
...
manyToOne:
company:
targetEntity: App\Identity\Domain\Company\Company
inversedBy: userCompanies
user:
targetEntity: App\Identity\Domain\User\User
inversedBy: companies
Okay, the solution is to use Query::HINT_REFRESH or $em->clear(), because I found there was another query for the User entity in middleware, and Doctrine uses cached results from IdentityMap, if the entity is already present there.

Get result from multiple result-JOIN or Union?

I am a newbie on the database side.
I have three tables. as per the below screen.
I want to create a view that shows records from job_document and engineer_certficate tables
job document holds, manually uploaded document info and enginnner_certficate holds document data filled out by the android application.
On UI, I want to show available documents from both tables. I tried with "join" but did not get the expected result.
SELECT * FROM document_type d join job_document jd ON jd.document_type_id = d.id JOIN `engineer_certficate` ec on ec.document_type_id = d.id ;
I have also tries Union, but still no luck
Expected Result
Show all columns of job_document and engineer_certficate
CREATE TABLE `document_type` ( `id` int(5) NOT NULL, `name` varchar(55) NOT NULL, `description` varchar(255) NOT NULL) ENGINE=InnoDB DEFAULT CHARSET=latin1; INSERT INTO `document_type` (`id`, `name`, `description`) VALUES (1, 'A', ''),(2, 'B', ''),(3, 'C', ''),(4, 'D', ''); CREATE TABLE `engineer_certficate` ( `id` int(10) NOT NULL, `job_id` int(10) NOT NULL, `document_type_id` mediumint(4) NOT NULL, `certficate_info` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL CHECK (json_valid(`certficate_info`))) ENGINE=InnoDB DEFAULT CHARSET=latin1; INSERT INTO `engineer_certficate` (`id`, `job_id`, `document_type_id`, `certficate_info`) VALUES(1, 1, 2, '{\"test\":\"abc\"}'),(2, 2, 2, '{\"test\":\"abc\"}'); CREATE TABLE `job_document` ( `id` int(10) NOT NULL, `job_id` int(10) NOT NULL, `document_type_id` mediumint(4) NOT NULL, `document_label` varchar(55) NOT NULL, `document_path` varchar(500) NOT NULL) ENGINE=InnoDB DEFAULT CHARSET=latin1; INSERT INTO `job_document` (`id`, `job_id`, `document_type_id`, `document_label`, `document_path`) VALUES(1, 1, 1, 'DocA', '2022/02/doca.pdf'),(2, 1, 3, 'DocC', '2022/02/docc.pdf');
ALTER TABLE `document_type` ADD PRIMARY KEY (`id`); ALTER TABLE `engineer_certficate` ADD PRIMARY KEY (`id`); ALTER TABLE `job_document` ADD PRIMARY KEY (`id`);

make a join select using django orm

create table topic (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(128) NOT NULL,
`description` longtext NOT NULL
)
create table `subscribe` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`userid` varchar(128) NOT NULL,
`topicid` int(11) NOT NULL,
Foreign Key (topic_id) REFERENCES topic(id)
)
Now, given userid = "Amy01", I want to get all the topics that "Amy01" has subscribed.
when using SQL, it's:
select t.id, t.name, t.description
from topic t join subscribe s on t.id = s.topicid
where s.userid = "Amy01"
How can I get the same select using django orm ?
I already have a resolution, but I don't think it's pretty good:
searched_sub = FilterSubscribe.objects.filter(userid = "Amy01").select_related()
searched = []
for sub in searched_sub:
searched.append(sub.topicid)
then, searched is all the topics that Amy01 has subscribed.
Is there any better statements to achieve this?
I have found one solution:
my_topic = Topic.objects.filter(Subscribe_set__userid = "Amy01").distinct()
"Subscribe_set" is used for 'callback' the subscribe table.
what's more, when define the Subscribe entity in models.py, if you code like this:
topic = models.ForeignKey('Topic', related_name = '_subscribe'),
you can also achieve the select as:
my_topic = Topic.objects.filter(_subscribe__userid = "Amy01").distinct()
referenced to: https://docs.djangoproject.com/en/1.6/topics/db/queries/#backwards-related-objects

Rails: efficient way to collect data of one model from another with many-to-many relationship

I have two models, Bookmark and Tag. The tags are implemented by acts-as-taggable-on gem, but I will explain the main point here.
Bookmark model contains an url field. Given a Bookmark instance #bookmark, #bookmart.tags returns its tags. Different bookmarks can share a same tag (that is the many part from Tag).
Tag has name, and taggings_count. The taggings_count field stores how many bookmarks to which the tag are tagged. Behind the scene, there is an taggings table, but that doesn't matter.
Now is the question, I want to retrieve all those tags that are tagged by bookmarks with specific url value, and the result should be sorted by the number of bookmarks to which a certain tag is tagged (that number is not the same as the taggings_count field, which represents tagging count for all bookmark, but want bookmark for a specific url here). How can it be done so that the generated sql is efficient?
I know I can write directly in sql for efficiency, but I am also wondering whether Rails can do the same without hurting too much performance, so that I don't have to inject sql code in my Rails application
Following is the table definitions, in the taggings table, taggable_id acts as a foreign key to Bookmark, and tag_id a foreign key to Tag:
CREATE TABLE `bookmarks` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`title` varchar(255) DEFAULT NULL,
`url` varchar(255) DEFAULT NULL,
`description` text,
`private` tinyint(1) DEFAULT NULL,
`read` tinyint(1) DEFAULT NULL,
`list_id` int(11) DEFAULT NULL,
`user_id` int(11) DEFAULT NULL,
`created_at` datetime DEFAULT NULL,
`updated_at` datetime DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci
CREATE TABLE `taggings` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`tag_id` int(11) DEFAULT NULL,
`taggable_id` int(11) DEFAULT NULL,
`taggable_type` varchar(255) DEFAULT NULL,
`tagger_id` int(11) DEFAULT NULL,
`tagger_type` varchar(255) DEFAULT NULL,
`context` varchar(128) DEFAULT NULL,
`created_at` datetime DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `taggings_idx` (`tag_id`,`taggable_id`,`taggable_type`,`context`,`tagger_id`,`tagger_type`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci
CREATE TABLE `tags` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(255) DEFAULT NULL,
`taggings_count` int(11) DEFAULT '0',
PRIMARY KEY (`id`),
UNIQUE KEY `index_tags_on_name` (`name`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci
This is a solution:
bookmark_ids = Bookmark.where(url: "http://foobar.com").pluck(:id)
taggings = Tagging.where(taggable_id: bookmark_ids).where(taggable_type: "Bookmark")
tag_ids = taggings.pluck(:tag_id).uniq
tags = Tag.where(id: tag_ids).order(taggings_count: :desc)
It theory it could be written using the joins() method from ActiveRecord, but I don't know how the gem you're using defines the associations.
This might or might not work:
Tag.order(taggings_count: :desc)
.joins(taggings: :bookmark)
.where(bookmark: { url: "http://foobar.com" })
You could also write raw SQL, but it feels dirty in rails.
It turns out to be simple, acts-as-taggable-on can actually operates on an association. The docs have that sample, I just didn't noticed:
Bookmark.where(url: url).tag_counts_on(:tags).sort_by(&:taggings_count).reverse.map(&:name)
The only problem is that, the result is stilled sorted by popularity of those tags, no matter in what kinds of context they are popular. That doesn't really matter, though. The point is this rails statement only generates a single sql:
SELECT tags.*, taggings.tags_count AS count FROM "tags" JOIN (SELECT taggings.tag_id, COUNT(taggings.tag_id) AS tags_count FROM "taggings" INNER JOIN bookmarks ON bookmarks.id = taggings.taggable_id WHERE (taggings.taggable_type = 'Bookmark' AND taggings.context = 'tags') AND (taggings.taggable_id IN(SELECT bookmarks.id FROM "bookmarks" WHERE "bookmarks"."url" = 'http://kindleren.com/forum.php?gid=49')) GROUP BY taggings.tag_id HAVING COUNT(taggings.tag_id) > 0) AS taggings ON taggings.tag_id = tags.id

Rails 3 ActiveRecord inserting autoincrement field issue

I'm using:
Rails 3.0.9
Activerecord-sqlserver-adapter 3.0.15
TinyTds
MSSQL 2005
I have following table:
CREATE TABLE [dbo].[eclaims](
[id_app] [int] IDENTITY(1,1) NOT NULL,
[id_user] [int] NOT NULL,
[property] [varchar](4) NOT NULL,
[app_number] [varchar](15) NULL,
[patent_number] [varchar](15) NULL,
[native_number] [varchar](20) NULL,
[title] [nvarchar](max) NULL,
[applicants] [nvarchar](max) NULL,
[receive_date] [datetime] NULL,
[change_date] [datetime] NOT NULL CONSTRAINT [DF_eclaims_change_date] DEFAULT (getdate()),
CONSTRAINT [PK_eclaims] PRIMARY KEY CLUSTERED
(
[id_app] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
GO
SET ANSI_PADDING OFF
GO
ALTER TABLE [dbo].[eclaims] WITH CHECK ADD CONSTRAINT [FK_eclaims_users] FOREIGN KEY([id_user])
REFERENCES [dbo].[users] ([id])
GO
ALTER TABLE [dbo].[eclaims] CHECK CONSTRAINT [FK_eclaims_users]
The model is:
# coding: utf-8
class Eclaim < ActiveRecord::Base
end
As you can see there is auto-increment field named id_app.
When I try execute a query insert into eclaims (id_user, [property], title) values (1, 2, 'Alex') in MSSQL console everything goes perfect.
But when I try Eclaim.create(:id_user => 1, :property => 'inv', :change_date => Time.now )
I am getting such error TinyTds::Error: DEFAULT or NULL are not allowed as explicit identity values.: INSERT INTO [eclaims] ([id_app], [id_user], [property], [app_number], [patent_number], [native_number], [title], [applicants], [receive_date], [change_date]) VALUES (NULL, 1, N'inv', NULL, NULL, NULL, NULL, NULL, NULL, '2012-05-08 06:39:14.882')
Why ActiverRecord doesn't insert the auto-increment field id_app automatically?
Thanks in advance.
Rails is all conventions and it assumes 'id' column to be the primary key. However, since you chose to set 'id_app' to be your primary key of the table, you should tell that to Rails as well.
Use set_primary_key(value = nil, &block) to set 'id_app' as the primary key and it should work.

Resources