Rails 3 - complex query with joins and counts, possible subqueries? - ruby-on-rails-3

Ok, so i have a bit of a complex query i am trying to come up with in my rails application. I have four tables: Clients, Projects, Invoices, Invoice_Line_Items. I am trying to get certain bits of data from all of those tables and display it in a "reports" type view in my application. This is what the structures look like for the four tables:
Clients
| id | name | archive |
----------------------------------------
| 1 | Client 1 | 0 |
| 2 | Client 2 | 0 |
Projects
| id | client_id | name | archive |
------------------------------------------------------
| 1 | 1 | Project 1 | 0 |
| 2 | 1 | Project 2 | 1 |
| 3 | 2 | Project 3 | 0 |
| 4 | 2 | Project 4 | 1 |
Invoices
| id | client_id | project_id | name | archive |
----------------------------------------------------------------------
| 1 | 1 | 1 | Invoice 1 | 0 |
| 2 | 1 | 1 | Invoice 2 | 0 |
| 3 | 1 | 2 | Invoice 3 | 1 |
| 4 | 1 | 2 | Invoice 4 | 1 |
| 5 | 2 | 3 | Invoice 5 | 0 |
| 6 | 2 | 3 | Invoice 6 | 0 |
| 7 | 2 | 4 | Invoice 7 | 1 |
| 8 | 2 | 4 | Invoice 8 | 1 |
Invoice_Line_Items
| id | invoice_id | name | amount_due |
---------------------------------------------------------
| 1 | 1 | Item 1 | 500 |
| 2 | 1 | Item 2 | 500 |
| 3 | 2 | Item 3 | 500 |
| 4 | 2 | Item 4 | 500 |
| 5 | 3 | Item 5 | 500 |
| 6 | 3 | Item 6 | 500 |
| 7 | 4 | Item 7 | 500 |
| 8 | 4 | Item 8 | 500 |
| 9 | 5 | Item 9 | 500 |
| 10 | 5 | Item 10 | 500 |
| 11 | 6 | Item 11 | 500 |
| 12 | 6 | Item 12 | 500 |
| 13 | 7 | Item 13 | 500 |
| 14 | 7 | Item 14 | 500 |
| 15 | 8 | Item 15 | 500 |
| 16 | 8 | Item 16 | 500 |
Ok, hope those diagrams make sense enough. What i am looking for as a result set is this (example data set taken from above example data):
| clients.name | current_projects | archived_projects | total_amount_due | total_amount_paid |
-----------------------------------------------------------------------------------------------------------
| Client 1 | 1 | 1 | 2000 | 2000 |
| Client 2 | 1 | 1 | 2000 | 2000 |
Ok, so here's what's going on there:
Getting all non-archived clients
Getting a count of all non-archived projects
Getting a count of all archived projects
Getting a total_amount_due from the invoice_line_items table that is a sum of all of the non-archived invoices
Getting a total_amount_paid from the invoice_line_items table that is a sum of all of the archived invoices
I am relatively new to Rails and this is a fairly complex query (at least in my head). Please let me know if there is a simpler solution that i am overlooking or if i am just over complicating it. If i need to do multiple queries in my controller that's fine, i was just wanting to see if i could get away with one sql call. I'm pretty sure i can do this pretty easily with some subqueries but i'm not sure how to write those in the controller in Rails.
Thanks for any help or direction you can provide and if this question is just outrageous or whatever just let me know and i'll delete it and go search the Googles more (have tried already to no avail).

Ok, well i ended up figuring out a solution myself. Not quite sure it's the best solution....feels heavy and messing but i just created quite a few objects in the controller to get the sql statements i needed to pull the data from the database. I basically have one object for each column (column, not each row). Let me know if anyone can figure out a better solution.

Related

Handling Many to Many

I'm stuck trying to model a many to many relationship. Here's a representative sample of my issue using an e-commerce model:
+------------+-------------+----------+------------+
| date | customer_id | order_id | address_id |
+------------+-------------+----------+------------+
| 12/1/2019 | 1 | 1 | 1 |
| 12/15/2019 | 2 | 1 | 1 |
| 12/15/2019 | 2 | 2 | 2 |
| 1/1/2020 | 2 | 3 | 1 |
| 1/1/2020 | 1 | 2 | 3 |
| 1/1/2020 | 1 | 3 | 2 |
| 1/2/2020 | 1 | 4 | 1 |
+------------+-------------+----------+------------+
A customer can place many orders.
A customer can ship to multiple addresses.
Addresses can have multiple customers.
How would I model a "household" junction/bridging table? In my data above, customer_id 1 and 2 could possibly be a family or business entity. What if I wanted to know on a given date, how many orders that household/entity placed, how many customers that household represented and how many locations did they ship to?
I think this is the start of how I build this model, but stuck on writing the bridging query.
orders addresses
+-------------+----------+ +-------------+------------+
| customer_id | order_id | | customer_id | address_id |
+-------------+----------+ +-------------+------------+
| 1 | 1 | | 1 | 1 |
| 1 | 2 | | 1 | 2 |
| 1 | 3 | | 1 | 3 |
| 1 | 4 | | 2 | 1 |
| 2 | 1 | | 2 | 2 |
| 2 | 2 | +-------------+------------+
| 2 | 3 |
+-------------+----------+
In a relational database you want to keep all the similar data in separate tables. This helps you with making joins later. I would recommend:

How to name child elements of a parent element by value? SQL server

Please, tell me an example how to mark all the child nodes to the parent id. Only need to mark those branches whose parent has the value "need" (see example image). Using a recursive query, it is not possible to rename all the children of a particular parent...
Initial data:
+-----+----------+----------+
| id | parentid | selector |
+-----+----------+----------+
| 1 | | |
| 2 | 1 | |
| 3 | 1 | need |
| 4 | 2 | |
| 5 | 2 | need |
| 6 | 3 | |
| 7 | 5 | |
| 8 | 5 | |
| 9 | 6 | |
+-----+----------+----------+
Need data:
+-----+----------+----------+----------------+
| id | parentid | selector | parentSelector |
+-----+----------+----------+----------------+
| 1 | null | | null |
| 2 | 1 | | null |
| 3 | 1 | need | 3 |
| 4 | 2 | | null |
| 5 | 2 | need | 5 |
| 6 | 3 | | 3 |
| 7 | 5 | | 5 |
| 8 | 5 | | 5 |
| 9 | 6 | | 3 |
+-----+----------+----------+----------------+
The task is to make the grouping by those elements whose parent has the value "need". I think, I should create a column with a mark, as in the example in the table above, or are there any other options?
I use SQL Server 2012
I dont't know if it work on Sql server 2012, but i found this microsoft, i think is what you want, to make the parentSelector with condition, I use CASE (Transact-SQL).
This is another example: stackoverflow question

How can I efficiently store large number sequences in a SQL db?

I want to store some number sequences in my database. So I:
+-----+---------+-----+
| idx | seq_id | x |
+-----+---------+-----+
| 1 | 1 | 1 |
| 2 | 1 | 1 |
| 3 | 1 | 2 |
| 4 | 1 | 3 |
| 5 | 1 | 5 |
| 6 | 1 | 7 |
| 1 | 2 | 1 |
| 2 | 2 | 2 |
| 3 | 2 | 4 |
| 4 | 2 | 8 |
| 5 | 2 | 16 |
| ... |
+-----+---------+-----+
but when I look at it, it feels like I'm storing more overhead with idx and seq_id than meaningful information.
In some sense I am, but I wouldn't find strange if the database engine optimized most of the repetition here. Is this the case for SQLite, MySQL, Postgre...?
And what can I make, perhaps in terms of table definition, to help the db optimize this storage pattern?

Is it possible to programmatically edit a SQLite CREATE VIEW statement?

I have a view that contains a number of nested views as shown below.
Main View:
CREATE VIEW `qryAttackRate` AS
Select qryFoodInCases.fldCaseID,qryFoodInCases.fldFood,
AteAndGotSick,TotalAte,AteAttackRate,
NotAteAndGotSick,TotalNotAte,NotAteAttackRate,
ROUND(AteAttackRate/CAST(NotAteAttackRate AS FLOAT),2) RelativeRisk
FROM qryFoodInCases
LEFT JOIN qryNotAteAttackRate QA
ON qryFoodInCases.fldFood=QA.fldFood
LEFT JOIN qryAteAttackRate QN
ON qryFoodInCases.fldFood=QN.fldFood
GROUP BY qryFoodInCases.fldFood
ORDER BY RelativeRisk Desc
These are 2 of the sub views. There are a few more:
CREATE VIEW `qryAteAttackRate`
AS SELECT qryFoodInCases.fldCaseID,qryFoodInCases.fldFood,
COALESCE(qryAteAndGotSick.AteAndGotSick,0) AteAndGotSick,
qryFoodInCases.fldFoodFrequency TotalAte,
100*COALESCE(qryAteAndGotSick.AteAndGotSick,0)/
qryFoodInCases.fldFoodFrequency AteAttackRate
FROM qryFoodInCases
LEFT JOIN qryAteAndGotSick
ON qryFoodInCases.fldFood=qryAteAndGotSick.fldFood
GROUP BY qryFoodInCases.fldFood
CREATE VIEW `qryFoodInCases`
AS SELECT tblCases.fldCaseID,fldfood,
COUNT(tblFoodHistory.fldFoodID) AS fldFoodFrequency
FROM tblFood
INNER JOIN tblFoodHistory
ON tblFoodHistory.fldFoodID=tblFood.fldFoodID)
INNER JOIN tblMealHistory
ON tblFoodHistory.fldMealID=tblMealHistory.fldMealHistoryID)
INNER JOIN tblInterviews
ON tblInterviews.fldInterviewID=tblMealHistory.fldInterviewID)
INNER JOIN tblCases
ON tblCases.fldCaseID=tblInterviews.fldCaseID
GROUP BY tblCases.fldCaseID,tblFood.fldFood
I want to query the main view for each fldCaseID. Problem is, the WHERE condition when querying the main view does not apply to the subviews.
Other than creating a long complex SQL statement, is there a way to programmatically insert a WHERE condition into the sub views?
To illustrate the problem see the output of select * from qryAttackRate where fldcaseid=1 before and after adding a second case without making any change to data related to the first case
Before
+-----------+------------+---------------+----------+---------------+------------------+-------------+------------------+--------------+
| fldCaseID | fldFood | AteAndGotSick | TotalAte | AteAttackRate | NotAteAndGotSick | TotalNotAte | NotAteAttackRate | RelativeRisk |
+-----------+------------+---------------+----------+---------------+------------------+-------------+------------------+--------------+
| 1 | Beans | 4 | 4 | 100 | 1 | 3 | 33 | 3.03 |
| 1 | Cabagge | 2 | 3 | 66 | 3 | 4 | 75 | 0.88 |
| 1 | fried fish | 2 | 3 | 66 | 3 | 4 | 75 | 0.88 |
| 1 | Banana | 1 | 2 | 50 | 4 | 5 | 80 | 0.62 |
| 1 | Pork | 2 | 4 | 50 | 3 | 3 | 100 | 0.5 |
| 1 | Chicken | 1 | 3 | 33 | 4 | 4 | 100 | 0.33 |
| 1 | Potatoes | 0 | 2 | 0 | 5 | 5 | 100 | 0 |
| 1 | Rice | 0 | 2 | 0 | 5 | 5 | 100 | 0 |
+-----------+------------+---------------+----------+---------------+------------------+-------------+------------------+--------------+
After
+-----------+------------+---------------+----------+---------------+------------------+-------------+------------------+--------------+
| fldCaseID | fldFood | AteAndGotSick | TotalAte | AteAttackRate | NotAteAndGotSick | TotalNotAte | NotAteAttackRate | RelativeRisk |
+-----------+------------+---------------+----------+---------------+------------------+-------------+------------------+--------------+
| 1 | Cabagge | 2 | 3 | 66 | 4 | 7 | 57 | 1.16 |
| 1 | fried fish | 2 | 3 | 66 | 4 | 7 | 57 | 1.16 |
| 1 | Pork | 2 | 4 | 50 | 4 | 6 | 66 | 0.76 |
| 1 | Potatoes | 0 | 2 | 0 | 6 | 8 | 75 | 0 |
+-----------+------------+---------------+----------+---------------+------------------+-------------+------------------+--------------+
A view always computes a fixed result set before any processing in the outer query is applied. (The database actually tries to optimize away unneeded processing in the view, but it will always behave as if the view were materialized in advance.)
It is not possible to change a view (with parameters, or with any other mechanism).
If your subqueries must change dynamically, you cannot use views.
You must write them as subqueries, or (in SQLite 3.8.3 or later) as common table expressions.

Selecting all rows in a master table and summing columns in multiple detail tables

I have a master table (Project List) along with several sub tables that are joined on one common field (RecNum). I need to get totals for all of the sub tables, by column and am not sure how to do it. This is a sample of the table design. There are more columns in each table (I need to pull * from "Project List") but I'm showing a sampling of the column names and values to get an idea of what to do.
Project List
| RecNum | Project Description |
| 6 | Sample description |
| 7 | Another sample |
WeekA
| RecNum | UserName | Day1Reg | Day1OT | Day2Reg | Day2OT | Day3Reg | Day3OT |
| 6 | JustMe | 1 | 2 | 3 | 4 | 5 | 6 |
| 6 | NotMe | 1 | 2 | 3 | 4 | 5 | 6 |
| 7 | JustMe | | | | | | |
| 7 | NotMe | | | | | | |
WeekB
| RecNum | UserName | Day1Reg | Day1OT | Day2Reg | Day2OT | Day3Reg | Day3OT |
| 6 | JustMe | 7 | 8 | 1 | 2 | 3 | 4 |
| 6 | NotMe | 7 | 8 | 1 | 2 | 3 | 4 |
| 7 | JustMe | | | | | | |
| 7 | NotMe | | | | | | |
So the first query should return the complete totals for both users, like this:
| RecNum | Project Description | sumReg | sumOT |
| 6 | Sample description | 40 | 52 |
| 7 | Another sample | 0 | 0 |
The second query should return the totals for just a specified user, (WHERE UserName = 'JustMe') like this:
| RecNum | Project Description | sumReg | sumOT |
| 6 | Sample description | 20 | 26 |
| 7 | Another sample | 0 | 0 |
Multiple parallel tables with the same structure is usually a sign of poor database design. The data should really be all in one table, with additional columns specifying the week.
You can, however, use union all to bring the data together. The following is an example of a query:
select pl.recNum, pl.ProjectDescription,
sum(Day1Reg + Day2Reg + Day3Reg) as reg,
sum(Day1OT + Day2OT + Day3OT) as ot
from ProjectList pl join
(select * from weekA union all
select * from weekB
) w
on pl.recNum = w.recNum
group by l.recNum, pl.ProjectDescription,;
In practice, you should use select * with union all. You should list the columns out explicitly. You can add appropraite where clauses or conditional aggregation to get the results you want in any particular case.