In a Rails app with Postgres I have a users, jobs and followers join table. I want to select jobs that are not followed by a specific user. But also jobs with no rows in the join table.
Tables:
users:
id: bigint (pk)
jobs:
id: bigint (pk)
followings:
id: bigint (pk)
job_id: bigint (fk)
user_id: bigint (fk)
Data:
sandbox_development=# SELECT id FROM jobs;
id
----
1
2
3
(3 rows)
sandbox_development=# SELECT id FROM users;
id
----
1
2
sandbox_development=#
SELECT id, user_id, job_id FROM followings;
id | user_id | job_id
----+---------+--------
1 | 1 | 1
2 | 2 | 2
(2 rows)
Expected result
# jobs
id
----
2
3
(2 rows)
Can I create a join query that is the equivalent of this?
sandbox_development=#
SELECT j.id FROM jobs j
WHERE NOT EXISTS(
SELECT 1 FROM followings f
WHERE f.user_id = 1 AND f.job_id = j.id
);
id
----
2
3
(2 rows)
Which does the job but is a PITA to create with ActiveRecord.
So far I have:
Job.joins(:followings).where(followings: { user_id: 1 })
SELECT "jobs".* FROM "jobs"
INNER JOIN "followings"
ON "followings"."job_id" = "jobs"."id"
WHERE "followings"."user_id" != 1
But since its an inner join it does not include jobs with no followers (job id 3). I have also tried various attempts at outer joins that either give all the rows or no rows.
In Rails 5, You can use #left_outer_joins with where not to achieve the result. Left joins doesn't return null rows. So, We need to add nil conditions to fetch the rows.
Rails 5 Query:
Job.left_outer_joins(:followings).where.not(followings: {user_id: 1}).or(Job.left_outer_joins(:followings).where(followings: {user_id: nil}))
Alternate Query:
Job.left_outer_joins(:followings).where("followings.user_id != 1 OR followings.user_id is NULL")
Postgres Query:
SELECT "jobs".* FROM "jobs" LEFT OUTER JOIN "followings" ON "followings"."job_id" = "jobs"."id" WHERE "followings"."user_id" != 1 OR followings.user_id is NULL;
I'm not sure I understand, but this has the output you want and use outer join:
SELECT j.*
FROM jobs j LEFT JOIN followings f ON f.job_id = j.id
LEFT JOIN users u ON u.id = f.user_id AND u.id = 1
WHERE u.id IS NULL;
Related
here is what i would like to accomplish: I have two (or more) tables in join, and I would like to get a boolean output if some conditions are met, e.g.
Table1:
customer_id
customer_name
status
1
Google
waiting
2
Facebook
working
3
Salesforce
waiting
Table2:
customer_id
agent
outcome
1
John
failure
1
Mike
success
2
John
success
I would like to get for all customer ids true if status in Table1 is "waiting" and there is no Table2 record for that customer
Desired output:
customer_id
waiting_and_no_record_in_table_2
1
false
2
false
3
true
Any idea on how to reach this goal?
Thanks in advance
As there can be multiple rows per customer_id in table2, to achieve the desired result requires some simplification be applied on table2. For this I simply chose select distinct which is sufficient for the example, but alternatives do exist.
select
table1.customer_id
, case when table1.status = 'waiting' and t2.customer_id IS NULL then true else false end as waiting_and_no_record_in_table_2
from table1
left join (select distinct customer_id from table2) as t2 on table1.customer_id = t2.customer_id
order by
table1.customer_id
and alternative might be to join only successful rows from t2 although this may still produce more than one row per customer_id
select
table1.customer_id
, case when table1.status = 'waiting' and t2.customer_id IS NULL then true else false end as waiting_and_no_record_in_table_2
from table1
left join table2 as t2 on table1.customer_id = t2.customer_id
and t2.outcome = 'success'
order by
table1.customer_id
or these 2 might be combined to also ensure only one row per customer_id
select
table1.customer_id
, case when table1.status = 'waiting' and t2.customer_id IS NULL then true else false end as waiting_and_no_record_in_table_2
from table1
left join (select distinct customer_id from table2 where outcome = 'success') as t2 on table1.customer_id = t2.customer_id
order by
table1.customer_id
I'm going to use the group by clause...max().
I'm using Informix version 10.
This is example table: table_a
col_a col_b col_c
1 20181010 3
1 20181030 4
I want to retrieve data with a recent date.
I want result :
col_a col_b col_c
1 20181030 4
When I use this query
#query 1: select col_a, max(col_b), col_c from table_a group by col_a
#result : The column (col_c) must be in the GROUP BY list.
#query 2: select col_a, max(col_b), col_c from table_a group by col_a, col_c
#result :
col_a col_b col_c
1 20181010 3
1 20181030 4
I think can I use MS SQL of row_num(partition by col_b)? but
Informix version 10 can't use row_num...
So, I use a join query
select a.col_a,a.col_b,a.col_c from table_a a
inner join (select col_a, max(col_b) as col_b from table_a group by col_a) b
on a.col_a = b.col_a and a.col_b = b.col_b
I got the results I wanted.
Is there a way to use join?
I have two tables ITEMS and ITEM_AUDITS. An item can have 0 or more audit records associated to it. Resource and status are required for fields and lookups.
I am trying to create a query that counts the number of occurrences of the item_id in both tables. Effectively producing data that looks like the example below
Title Item_ID Count
ABC 1 2
ABC 3 4
Bible 5 1
I have been able to create a union query that produces the data without the counts:
# Declare Arel objects
i = Item.arel_table
ia = ItemAudit.arel_table
s = Status.arel_table
r = Resource.arel_table
######################
# Build the item query
######################
# Build the joins
item = Item.joins(
i.join(r, Arel::Nodes::OuterJoin).on(i[:resource_id].eq(r[:id]))
.join(s, Arel::Nodes::OuterJoin).on(i[:status_id].eq(s[:id]))
.join_sql
).uniq
# Build the select columns
#item = item.select('resources.title, items.id as id, count(*) as loan_count')
item = item.select('resources.title, items.id as item_id')
# Adds the where criteria
item = item.where(
s[:title].matches("On Loan").or(s[:title].matches("Permanent Loan"))
)
# Add the group by clause
item = item.group("items.id")
##########################
# Build item history query
##########################
# Build the joins
item_audit = Item.joins(
i.join(ia).on(i[:id].eq(ia[:item_id]))
.join(r, Arel::Nodes::OuterJoin).on(i[:resource_id].eq(r[:id]))
.join(s, Arel::Nodes::OuterJoin).on(i[:status_id].eq(s[:id]))
.join_sql
)
# Build the select columns
item_audit = item_audit.select('resources.title, item_audits.item_id as item_id')
#######################
# Union the two queries
#######################
report = item.union(:all, item_audit).select('title, item_id')
I can't progress past this to get the counts which should be something like
report = item.union(:all, item_audit).select('title, item_id, count(item_id)')
EDIT
The SQL I am trying to end up with is as below (a simple group and count on the union results).
select qry.title, qry.item_id, count(qry.item_id) from(
SELECT DISTINCT
resources.title, items.id as item_id
FROM
`items`
LEFT OUTER JOIN
`resources` ON `items`.`resource_id` = `resources`.`id`
LEFT OUTER JOIN
`statuses` ON `items`.`status_id` = `statuses`.`id`
WHERE
((`statuses`.`title` LIKE 'On Loan'
OR `statuses`.`title` LIKE 'Permanent Loan'))
GROUP BY items.id
UNION ALL SELECT
resources.title, item_audits.item_id as item_id
FROM
`items`
INNER JOIN
`item_audits` ON `items`.`id` = `item_audits`.`item_id`
LEFT OUTER JOIN
`resources` ON `items`.`resource_id` = `resources`.`id`
LEFT OUTER JOIN
`statuses` ON `items`.`status_id` = `statuses`.`id`) as qry
group by qry.item_id
Anyone give me any pointers.
Many thanks,
I have 2 models - Question and Tag - which have a HABTM between them, and they share a join table questions_tags.
Feast your eyes on this badboy:
1.9.3p392 :011 > Question.count
(852.1ms) SELECT COUNT(*) FROM "questions"
=> 417
1.9.3p392 :012 > Tag.count
(197.8ms) SELECT COUNT(*) FROM "tags"
=> 601
1.9.3p392 :013 > Question.connection.execute("select count(*) from questions_tags").first["count"].to_i
(648978.7ms) select count(*) from questions_tags
=> 39919778
I am assuming that the questions_tags join table contains a bunch of duplicate records - otherwise, I have no idea why it would be so large.
How do I clean up that join table so that it only has uniq content? Or how do I even check to see if there are duplicate records in there?
Edit 1
I am using PostgreSQL, this is the schema for the join_table questions_tags
create_table "questions_tags", :id => false, :force => true do |t|
t.integer "question_id"
t.integer "tag_id"
end
add_index "questions_tags", ["question_id"], :name => "index_questions_tags_on_question_id"
add_index "questions_tags", ["tag_id"], :name => "index_questions_tags_on_tag_id"
I'm adding this as a new answer since it's a lot different from my last. This one doesn't assume that you have an id column on the join table. This creates a new table, selects unique rows into it, then drops the old table and renames the new one. This will be much faster than anything involving a subselect.
foo=# select * from questions_tags;
question_id | tag_id
-------------+--------
1 | 2
2 | 1
2 | 2
1 | 1
1 | 1
(5 rows)
foo=# select distinct question_id, tag_id into questions_tags_tmp from questions_tags;
SELECT 4
foo=# select * from questions_tags_tmp;
question_id | tag_id
-------------+--------
2 | 2
1 | 2
2 | 1
1 | 1
(4 rows)
foo=# drop table questions_tags;
DROP TABLE
foo=# alter table questions_tags_tmp rename to questions_tags;
ALTER TABLE
foo=# select * from questions_tags;
question_id | tag_id
-------------+--------
2 | 2
1 | 2
2 | 1
1 | 1
(4 rows)
Delete tag associations with bad tag reference
DELETE FROM questions_tags
WHERE NOT EXISTS ( SELECT 1
FROM tags
WHERE tags.id = questions_tags.tag_id);
Delete tag associations with bad question reference
DELETE FROM questions_tags
WHERE NOT EXISTS ( SELECT 1
FROM questions
WHERE questions.id = questions_tags.question_id);
Delete duplicate tag associations
DELETE FROM questions_tags
USING ( SELECT qt3.user_id, qt3.question_id, MIN(qt3.id) id
FROM questions_tags qt3
GROUP BY qt3.user_id, qt3.question_id
) qt2
WHERE questions_tags.user_id=qt2.user_id AND
questions_tags.question_id=qt2.question_id AND
questions_tags.id != qt2.id
Note:
Please test the SQL's in your development environment before trying them on your production environment.
When I use User.count(:all, :group => "name"), I get multiple rows, but it's not what I want. What I want is the count of the rows. How can I get it?
Currently (18.03.2014 - Rails 4.0.3) this is correct syntax:
Model.group("field_name").count
It returns hash with counts as values
e.g.
SurveyReport.find(30).reports.group("status").count
#=> {
"pdf_generated" => 56
}
User.count will give you the total number of users and translates to the following SQL: SELECT count(*) AS count_all FROM "users"
User.count(:all, :group => 'name') will give you the list of unique names, along with their counts, and translates to this SQL: SELECT count(*) AS count_all, name AS name FROM "users" GROUP BY name
I suspect you want option 1 above, but I'm not clear on what exactly you want/need.
Probably you want to count the distinct name of the user?
User.count(:name, :distinct => true)
would return 3 if you have user with name John, John, Jane, Joey (for example) in the database.
________
| name |
|--------|
| John |
| John |
| Jane |
| Joey |
|________|
Try using User.find(:all, :group => "name").count
Good luck!
I found an odd way that seems to work. To count the rows returned from the grouping counts.
User Table Example
________
| name |
|--------|
| Bob |
| Bob |
| Joe |
| Susan |
|________|
Counts in the Groups
User.group(:name).count
# SELECT COUNT(*) AS count_all
# FROM "users"
# GROUP BY "users"."name"
=> {
"Bob" => 2,
"Joe" => 1,
"Susan" => 1
}
Row Count from the Counts in the Groups
User.group(:name).count.count
=> 5
Something Hacky
Here's something interesting I ran into, but it's quite hacky as it will add the count to every row, and doesn't play too well in active record land. I don't remember if I was able to get this into an Arel / ActiveRecord query.
SELECT COUNT(*) OVER() AS count, COUNT(*) AS count_all
FROM "users"
GROUP BY "users"."name"
[
{ count: 3, count_all: 2, name: "Bob" },
{ count: 3, count_all: 1, name: "Joe" },
{ count: 3, count_all: 1, name: "Susan" }
]