Self join query with references in other tables - join

I have tried a series of joins and queries to get the result I am looking for with no success.
I have three tables with the following columns:
Attributes
ID, KEY, VALUE
Book
BID
PAGE
PID, BID
The ID in Attributes refers back to either the Book or page table as such
ID - KEY - VALUE
a1 - A - Book1
a2 - B - Page1
The BOOK table simple holds a record of the book and its associated pages
BID - PID
a1 - p1
a1 - p2
Page id is similar but swapped
PID - BID
p1 - a1
p2 - a1
I need to query the attributes table to get the book name and then count how many pages are associated with it
select b.VALUE as "book", COUNT(p.VALUE) as "PAGE COUNT
from attributes b, attributes p
inner join book k on k.PID = p.ID
where b.key = 'A' and p.KEY = 'B' groub by b.value;
I am getting a result, but its incorrect. Here is a table example and the result I am looking for.
[1]: https://i.stack.imgur.com/8Ndmn.png

select a.value as book, count(p.pid) as page_count
from book b, page p, attributes a
where b.bid = p.bid
and b.bid = a.id
and a.key = 'A'
group by a.value

SELECT COUNT(pages.pid) FROM pages JOIN books ON books.bid = pages.pid JOIN attributes ON attributes.id = books.bid WHERE books.bid = book_id

Related

ActiveRecord, Nested includes with counter

I have 3 tables (a, b, c) and "a" has_many "b" and "b" has_many "c". I have this line c.includes(b: :a), but I need to count how many "a" are for each "c" so the results be like 2 columns. Also there are some data in C that doesn't have b, so when I show them, it throw me an error.
I can do it with a query from pgAdmin and it show me how I need it but I can't make it on ActiveRecords.
The query that works fine is:
select count(c.id), a.name from c
left Join b on b.id = c.b_id
left join a on a.id = b.a_id
where c.created_at between '2018-11-08 00:00:00' and '2018-11-08 23:59:59' group by a.name
A.
joins(:b).
joins(b: :c).
group("a.name").
where("c.created_at between ? and ?", DateTime.new(2018,
11, 08), DateTime.new(2018, 11, 08).end_of_day).
count
returns a hash where the keys are "a.name" and the values are the count of C for each A.
If you only want to count unique C for each A, you can specify count("distinct c.id").
For more complex queries you can always query with raw SQL:
ActiveRecord::Base.connection.execute(<<~SQL).values
select count(c.id), a.name from c
left Join b on b.id = c.b_id
left join a on a.id = b.a_id
where c.created_at between '2018-11-08 00:00:00' and '2018-
11-08 23:59:59' group by a.name
SQL
returns a jagged array of the results.
As for your orphaned C records, when you query for them you can inner join to B. That will only return C that have B.

Can I find a bookshelf that has book A and book (B, C, or D) in one activerecord query?

Imagine these associations:
class Bookshelf
has_many :book_associations, dependent: :destroy
has_many :books, through: :book_associations
end
class Book
has_many :book_associations, dependent: :destroy
has_many :bookshelves, through: :book_associations
end
class BookAssociation
belongs_to :book
belongs_to :bookshelf
end
I need to find all bookshelves that contain a book with ID A and a book with ID B, C, or D
I can do this in a multi-step process using ruby like:
bookshelf_ids1 = Book.find(A).bookshelves.pluck(:id)
bookshelf_ids2 = Book.where(id: [B, C, D]).map(&:bookshelves).flatten.uniq.pluck(:id)
Bookshelf.where(id: bookshelf_ids1 & bookshelf_ids2)
But there must be a way to do this in one line, either through ActiveRecord or a raw SQL query.
What this question boils down to is that you're looking for an intersection of Bookshelf objects in set A (contains book with ID a) and Bookshelf objects in set B (contains book with ID in array b).
I don't recall seeing any easy way to express this intersection using a single ActiveRecord query. And as you probably have suspected, a multi query approach wouldn't scale well. Why run three queries when you can run one?
So here's my solution:
Finding the Bookshelve IDs
SELECT DISTINCT b1.bookshelf_id
FROM book_associations b1
INNER JOIN book_associations b2 ON b1.bookshelf_id = b2.bookshelf_id
WHERE b1.book_id = 1 AND b2.book_id IN (2,3,4);
This is a bit complicated so let me break it down. We are joining the book_associations table on itself, on its own bookshelf_id. This has the effect of making two tables available for filtering. We then filter query table b1 for the first criteria (ID = 1), and filter query table b2 for the other criteria (ID in (2,3,4)). With the INNER JOIN we are then ensuring that we are only getting the intersection of tables b1 and b2. We retrieve only the bookshelf_id because we're looking only to retrieve the bookshelves. Finally, the DISTINCT query is the SQL equivalent of .uniq and ensures the returned values are unique.
Retrieving the Bookshelves
From here, we then need to instantiate the Bookshelf objects. While we could do this:
bookshelf_ids = ActiveRecord::Base.connection.query(["SELECT DISTINCT b1.bookshelf_id
FROM book_associations b1
INNER JOIN book_associations b2 ON b1.bookshelf_id = b2.bookshelf_id
WHERE b1.book_id = ? AND b2.book_id IN (?);", 1, [2,3,4]])
bookshelves = Bookshelf.find(bookshelf_ids)
It's still two steps. Here's a single-step solution:
Bookshelf.find_by_sql(["SELECT * FROM bookshelves bs
WHERE bs.id IN (SELECT DISTINCT b1.bookshelf_id
FROM book_associations b1
INNER JOIN book_associations b2 ON b1.bookshelf_id = b2.bookshelf_id
WHERE b1.book_id = ? AND b2.book_id IN (?)
)", first_id,second_ids])
The Bookshelf.select_by_sql command instantiates records from the query results. The bookshelf_ids are retrieved in the subquery and used as a condition for the query on the bookshelves table.
Caveats
I haven't tested this code and can't confirm it will work, but the broad strokes should be correct.
The SQL should be valid for PostgresQL, but may require some tweaks depending on your specific DB implementation.
Edit
I've corrected the code above, I had joined the book_associations table on the wrong column (id) instead of the correct column bookshelf_id, and the subquery was returning the wrong column (again id when it should've been bookshelf_id).
I've created a proof of concept with test included.

Entity Framework: Select with sum from joined table

How do i do something like this in entity framework with out an overhead.
select
p.id,
total = sum(b.beløb)
from
projekt p
inner join budget b on ,,,
Column total doesn't exists on table. I added it to data model, so i can handle the value when present in resultset.
Don't add unnecessary fields to your data model. Use DTO's or ViewModels instead.
Example of how to perform those queries in LINQ:
var result = (from a in dbContext.Table
where a.ID == id
select new
{
id = a.id,
total = a.Budget.Sum(q => q.beløb)
}).FirstOrDefault();

Joining multiple table order

I think I have read somewhere that join in associative and commutative so order of table in join query is irrelevant, is this true?!!
If so then I have a situation here where I have 4 tables like this:
S(s_id, sname, status, city) supplier
J(j_id, jname, city) job/project
P(p_id, pname, color, weight, city) part
SPJ(s_id, p_id, j_id, qnty) supplier - parts - jobs
So my question is are the following joins the same?
S join J join SPJ
S join SPJ join J

DQL Select Join

I'm trying to make a DQL query (doctrine from symfony 2.2) with no success on these entities:
Lesson
Which has several LessonContent
Which are each linked to a User
I want to retrieve all the lessons of a user (should be pretty basic...).
SELECT l, lc FROM MyBundle:LessonContent lc
JOIN lc.lesson l JOIN lc.modifiedBy u
WHERE lc.creation=1 AND u.id = :userId
But this returns the LessonContent entities. If I select from Lesson, I can't JOIN the lessons (which is probably what I should be doing).
Can anyone help me?
The main table you query from is what doctrine gives you back as the main objects, so the following should work (assuming l.content points to the LessonContent association):
SELECT l, lc FROM MyBundle:Lesson l
JOIN l.content lc
JOIN lc.modifiedBy u
WHERE lc.creation=1 AND u.id = :userId
It turned out I needed to have a l.content*s* attribute in order to be able to select from lessons and then JOIN on the rest.
Entity:
/**
* #var ArrayCollection $contentHistory
* #ORM\OneToMany(targetEntity="AAA\CoreBundle\Entity\LessonContent", mappedBy="lesson", cascade={"persist", "remove"})
* #ORM\OrderBy({"lastModified" = "DESC"})
*/
private $contentHistory;
Query:
SELECT l FROM AAACoreBundle:Lesson l JOIN l.contentHistory lc JOIN lc.modifiedBy u WHERE lc.creation=1 AND u.id = :userId GROUP BY l
And with that it works like a charm!

Resources