Calculate rank for multiple attributes - ruby-on-rails-3

I'm building a motorsport timing app. As car's race around the track, Users input the split times for each Car into the app. The app then ranks each car at each split and displays this in my view.
For example, Car A has a split_1 of 5.00 seconds, split_2 of 15.00 seconds, and split_3 of 25.00 seconds. Car B has a split_1 of 5.50 seconds, split_2 of 14.00 seconds, and split_3 of 23.00 seconds. The ranks displayed in my view for car A would be 1, 2, 2. Car B would be 2, 1, 1.
I wrote an inefficient, database-intensive method to calculate the rank for every single split. Basically the method gets all the split_1 times for every car on the Timesheet and places them into a sorted array, then compares the current Car's split_1 to find its position. It does this for every car, and every lap. It's a nightmare.
class Car < ActiveRecord::Base
has_many :laps
end
class Lap < ActiveRecord::Base
belongs_to :car
belongs_to :timesheet
end
class Timesheet < ActiveRecord::Base
has_many :laps
end
I know I'm missing an easier, more efficient way to calculate each rank. I'm using Postgres 9.2, and with my limited understanding and experience, I think window functions might offer a solution. Is there another way?

With a table like:
CREATE TABLE split_times (car TEXT, split INT, "time" INTERVAL);
INSERT INTO split_times VALUES ('A', 1, '00:00:05');
INSERT INTO split_times VALUES ('A', 2, '00:00:15');
INSERT INTO split_times VALUES ('A', 3, '00:00:25');
INSERT INTO split_times VALUES ('B', 1, '00:00:05.5');
INSERT INTO split_times VALUES ('B', 2, '00:00:14');
INSERT INTO split_times VALUES ('B', 3, '00:00:23');
CREATE INDEX split_times_split_and_time ON split_times (split, "time");
This will get you the split ranks:
SELECT
car, split,
RANK() OVER (PARTITION BY split ORDER BY time) AS rank
FROM
split_times;
You can use this to get the results per car:
WITH x AS (
SELECT
car, split,
RANK() OVER (PARTITION BY split ORDER BY time) AS rank
FROM
split_times ORDER BY split
)
SELECT
car, ARRAY_AGG(rank) AS ranks
FROM
x
GROUP BY car;
SQL Fiddle demo
Window functions documentation

Related

How would I calculate the quotient of 'churn' with the previous month's 'active'?

Trying to get the churn rate, obviously. Getting the quotient within each month would be easy but incorrect.
Frankly, I'm totally lost on this one. Would it make more sense to reorganize the output first?
I put your data in a table variable (which is SQL Server) to write the query. The actual SELECT statement I wrote should work in all RDBMSs - I think it is all ANSI standard SQL. You didn't mention what data you wanted to see, nor did you mention what should happen in MONTH 1 where there is no previous month, but hopefully you will be able to get your final query from seeing this.
To do it, JOIN the table to itself. I use two aliases, d1 and d2. For d1 I want to find CHURN and d2 I want to find ACTIVE. Also, the MONTHS of d2 should be one less than the MONTHS of d1. Finally, since I declared the SUM column as an INT, I multiply it by 1.0 to force it to an approximate data type, otherwise the division would come back as zero or a truncated INT (since it is integer division).
DECLARE #Data TABLE
(
[ID] INT,
[MONTHS] INT,
[THIS_MONTH_VALUE] VARCHAR(10),
[SUM] INT
);
INSERT INTO #Data
(
[ID],
[MONTHS],
[THIS_MONTH_VALUE],
[SUM]
)
VALUES
(1, 0, 'NEW', 4987),
(2, 1, 'ACTIVE', 3849),
(3, 1, 'CHURN', 1138),
(4, 1, 'NEW', 884),
(5, 2, 'ACTIVE', 3821),
(6, 2, 'CHURN', 912),
(7, 2, 'NEW', 818),
(9, 3, 'ACTIVE', 3954),
(10, 3, 'CHURN', 942);
-- the following statement should work in any RDBMS but you might have to change
-- the square brackets to whatever your RDBMS uses to escape
SELECT [d1].[ID],
[d1].[MONTHS],
[d1].[THIS_MONTH_VALUE],
[d1].[SUM],
[d2].[ID],
[d2].[MONTHS],
[d2].[THIS_MONTH_VALUE],
[d2].[SUM],
1.0 * [d1].[SUM] / [d2].[SUM] AS [CHURN_RATE]
FROM #Data AS [d1]
INNER JOIN #Data AS [d2]
ON [d1].[THIS_MONTH_VALUE] = 'CHURN'
AND [d2].[THIS_MONTH_VALUE] = 'ACTIVE'
AND [d2].[MONTHS] = [d1].[MONTHS] - 1;
The output is:
ID
MONTHS
THIS_MONTH_VALUE
SUM
ID
MONTHS
THIS_MONTH_VALUE
SUM
CHURN_RATE
6
2
CHURN
912
2
1
ACTIVE
3849
0.236944660950
10
3
CHURN
942
5
2
ACTIVE
3821
0.246532321381
Again, you might have to modify the query to get exactly what you want.

How to select the value of certain field from a table in a database, if the sum of the values from the given column does not exceed certain threshold?

Imagine there is an elevator which can hold a weight up to certain KG (kilogram). Now, consider a table in the database with the following columns: id, name, weight, turn - where the name represents the name of a person, weight represents the weight of that person (let's say in KG), and turn represents the position of the person waiting in the queue. How can one select the name of the last person who can enter the elevator considering the maximum weight the elevator can hold.
For example, considering the following values from the table named as INFO:
(1, John 100, 1), (2, Jade, 80, 3), (3, Kate, 90, 2), (4, Bebe, 70, 4). If the maximum weight the elevator can hold is 200KG, the last person who can enter the elevator is "Kate" (the first person who enters the elevator is John considering the value of turn being "1", then "Kate" considering the value of turn being "2") - Johns' weight(100) + Kate's weight(90) = 190 (if we consider the next person, the limit is exceeded).
You would use cumulative sums:
select t.*
from (select t.*, sum(weight) over (order by id) as running_weight
from t
) t
where running_weight <= 200
order by id desc
fetch first 1 row only;

Oracle SQL - How to "pivot" one row to many

In Oracle 12c, I have a view, which takes a little time to run. When I add the where clause, it will return exactly one row of interest. The row has columns/value like this...
I need this flipped so that I can see one row per EACH "set". I need the SQL to return something like
I know I can do a UNION ALL for each of the entry sets, but as the view takes a little while to run, plus there are about 30 different sets (I only showed 3 - Car, Boat, and truck)
Is there a better way of doing this? I have looked at PIVOT/UNPIVOT, but I didn't see how to make this work.
I think you are looking for UNPIVOT
WITH TEMP_DATA (ID1, CarPrice, CarTax, BoatPrice, BoatTax, TruckPrice, TruckTax)
AS (
select 'AAA', 1, 2, 3, 4, 5, 6 from dual )
select TYPE, PRICE, TAX
from temp_data
unpivot
(
(PRICE, TAX)
for TYPE IN
(
(CarPrice, CarTax) as 'CAR',
(BoatPrice, BoatTax) as 'BOAT',
(TruckPrice, TruckTax) as 'TRUCK'
)
)
;
OUTPUT:
TYPE PRICE TAX
----- ---------- ----------
CAR 1 2
BOAT 3 4
TRUCK 5 6

Postgres - Changing values within columns

I have a query that returns a wide dataset with one row per student and multiple columns per 'score':
Student ID score1 score2 score3...
12345 101 102 103
67890 102 103 104
The scores are not actual scores, but instead are score ids that need to be translated to actual scores.
I would like to return the actual scores instead of the score ids. I know that I can just write a bunch of CASE statements that will do the translation for each column, but there are about 20 columns that need to be translated. I'm hoping that there is a more efficient way of doing this.
Cheers,
Jonathon
You probably want to make a scores table and then join to that. That will take away the need to write an absurd case query.
CREATE TABLE code_scores (
ScoreID INT
, Value INT)
GO
INSERT INTO code_scores (scoreid, value)
VALUES
(101, 100)
, (102, 99)
GO
SELECT studentID, score1, value
FROM yourtable
INNER JOIN code_scores
on score1 = scoreID

Project multiple rows into a single row based on columns values in sql

I would like to know how you project multiple related rows into a single row, for example, a product that comes in multiple parts will have multiple SKUs but I want to project the multiple parts into a single row.
I'm sure this is possible but struggling to define the query for the desired result.
Given the example dataset
I would like to project my result to the following
What ends up in the product code or product name columns is irrelevant, essentially I just need a single row to represent these two rows.
How would I achieve this?
It depends on the format of data stored in ProductCode and ProductName.
According to this, you have to write appropriate expressions extracting all the useful data.
Then, of course, you have to decide what ID you will leave for new rows.
In my example I do simple transformation with substr(…) to extract necessary data,
and I use max(ID) to choose what ID will be for the row.
Test data:
insert table1(CustId, ProductCode, ProductName)
values
(10, 'Prod1Part1', 'Product1 Part1'),
(10, 'Prod1Part2', 'Product1 Part2'),
(10, 'Prod1Part3', 'Product1 Part3'),
(10, 'Prod2Part1', 'Product2 Part1'),
(10, 'Prod2Part2', 'Product2 Part2')
;
A query:
SELECT
(SELECT
MAX(id)
FROM
table1
WHERE
SUBSTR(ProductCode, 1, 5) = NewProductCode) id,
CustId,
NewProductCode,
NewProductName
FROM
(SELECT DISTINCT
CustId, SUBSTR(ProductCode, 1, 5) NewProductCode,
substr(ProductName, 1, instr(ProductName, ' ')) NewProductName
FROM
table1) x
The output:
8 10 Prod1 Product1
10 10 Prod2 Product2
Is it clear? Ask me to improve the answer, if it's not.