How do I retrieve releated models from subquery - sql

I have a table with models. Model can be parent to another model (1 to 1)
type Model struct {
ID uuid.UUID`
Name string
ParentModel Model
CaseID uuid.UUID
ProjectID uuid.UUID
ChildModels []Model
}
What I am trying to achive is:
select all models with the same caseID and select all models with caseId from parantModel and childrenModels.
Is it possible with subquery?
I have tried to select with preload (parent/children) with orm, but do not really get where to go from there

Sample data:
CREATE TABLE public.model (
id int4 NOT NULL,
caption varchar NULL,
parent_id int4 NULL,
CONSTRAINT model_pk PRIMARY KEY (id)
);
INSERT INTO model (id, caption, parent_id) VALUES(1, 'Model1', NULL);
INSERT INTO model (id, caption, parent_id) VALUES(2, 'Model2', NULL);
INSERT INTO model (id, caption, parent_id) VALUES(3, 'Model3', NULL);
INSERT INTO model (id, caption, parent_id) VALUES(4, 'Model4', NULL);
INSERT INTO model (id, caption, parent_id) VALUES(5, 'Model1_1', 1);
INSERT INTO model (id, caption, parent_id) VALUES(6, 'Model1_2', 1);
INSERT INTO model (id, caption, parent_id) VALUES(7, 'Model1_3', 1);
INSERT INTO model (id, caption, parent_id) VALUES(8, 'Model1_2_1', 6);
INSERT INTO model (id, caption, parent_id) VALUES(9, 'Model1_2_2', 6);
INSERT INTO model (id, caption, parent_id) VALUES(10, 'Model4_1', 4);
Recursive Query example:
with recursive tbl(id, caption, parent_id) as (
select * from model
where id = 1
union all
select model.* from model
inner join tbl on tbl.id = model.parent_id
)
select * from tbl
Result:
id caption parent_id
1 Model1 [NULL]
5 Model1_1 1
6 Model1_2 1
7 Model1_3 1
8 Model1_2_1 6
9 Model1_2_2 6

Related

Find all records in a table where the most recent record in a join table has a column which is not equal to one is this table?

Image the tables:
CREATE TABLE items (
id INT,
price INT
);
CREATE TABLE item_price_history (
id INT,
item_id INT,
price INT
);
With the following data:
INSERT INTO items (id, price) VALUES (1, 199);
INSERT INTO items (id, price) VALUES (2, 159);
INSERT INTO items (id, price) VALUES (3, 129);
INSERT INTO items (id, price) VALUES (4, 119);
INSERT INTO item_price_history (id, item_id, price) VALUES (1, 1, 249);
INSERT INTO item_price_history (id, item_id, price) VALUES (2, 1, 239);
INSERT INTO item_price_history (id, item_id, price) VALUES (3, 1, 229);
INSERT INTO item_price_history (id, item_id, price) VALUES (4, 1, 199);
INSERT INTO item_price_history (id, item_id, price) VALUES (5, 2, 299);
INSERT INTO item_price_history (id, item_id, price) VALUES (6, 2, 259);
INSERT INTO item_price_history (id, item_id, price) VALUES (7, 2, 159);
INSERT INTO item_price_history (id, item_id, price) VALUES (8, 2, 109);
INSERT INTO item_price_history (id, item_id, price) VALUES (9, 3, 129);
INSERT INTO item_price_history (id, item_id, price) VALUES (10, 4, 159);
INSERT INTO item_price_history (id, item_id, price) VALUES (11, 4, 119);
INSERT INTO item_price_history (id, item_id, price) VALUES (13, 4, 99);
Now I would like to find all the items for which the items.price is not equal to the most recent item_price_history.price (id DESC) in item_price_history. In this case that should produce item with id 3 and 4. As their prices does NOT match the most recent price in item_price_history.
I did the following which works:
SELECT items.id, items.price i_price, item_price_history.id, item_price_history.price as ih_price
FROM items
LEFT JOIN item_price_history ON item_price_history.item_id = items.id
AND item_price_history.id = (SELECT MAX(id) FROM item_price_history WHERE item_id = items.id)
WHERE items.price != item_price_history.price
LIMIT 100
However I have a table of about 2 million rows in items and 20 million rows in item_price_history which I need to scan through, so performance is important.
How would I write such a query in a more performant way e.g by using DISTINCT ON or something?
https://www.db-fiddle.com/f/wPkdHvvzS2tmZq2vKUwgNi/2
Here is a query to do it in bulk with DISTINCT ON:
SELECT items.id, items.price i_price, item_price_history.id, item_price_history.price as ih_price
from items
join (select distinct on (item_id) id, item_id, price from item_price_history order by item_id desc, id desc) item_price_history
on item_price_history.item_id = items.id
WHERE items.price != item_price_history.price;
It should be well supported by an index on item_price_history (item_id , id, price); which will let it do a presorted index-only scan.

Write SQL query for the given scenario

We have a table without date or incremental int column as below.
create table test(
id uniqueidentifier DEFAULT newsequentialid(),
userid int,
type int,
value varchar(20)
);
We wanted userid with value based on following logic, first data in case of primary else last data in case of other type.
Available type column value
-------------
0 - None
1 - Primary
2 - Other
3 - Registered
Logic to retrieve value should be as below :
if Primary available :
take "first" Primary's Value
else if Registered available :
take "last" Registered's Value
else if Other available :
take "last" Other's Value
else
take "last" None's Value
Created few scenarios to understand requirement better.
Scenario 1 :
/*
Output should be as below
userid, value
-------------
1, User1 value1.1
2, User2 value4.2
*/
insert into test (userid, type, value) values(1, 1, 'User1 value1.1')
insert into test (userid, type, value) values(1, 1, 'User1 value1.2')
insert into test (userid, type, value) values(1, 2, 'User1 value2.1')
insert into test (userid, type, value) values(1, 2, 'User1 value2.2')
insert into test (userid, type, value) values(1, 4, 'User1 value4.1')
insert into test (userid, type, value) values(1, 4, 'User1 value4.2')
insert into test (userid, type, value) values(2, 0, 'User2 value0.1')
insert into test (userid, type, value) values(2, 0, 'User2 value0.2')
insert into test (userid, type, value) values(2, 4, 'User2 value4.1')
insert into test (userid, type, value) values(2, 4, 'User2 value4.2')
Scenario 2 :
/*
Output should be as below
userid, value
-------------
3, User3 value1.1
4, User4 value4.2
*/
insert into test (userid, type, value) values(3, 0, 'User3 value0.1')
insert into test (userid, type, value) values(3, 1, 'User3 value1.1')
insert into test (userid, type, value) values(3, 2, 'User3 value2.1')
insert into test (userid, type, value) values(3, 2, 'User3 value2.2')
insert into test (userid, type, value) values(4, 4, 'User4 value4.1')
insert into test (userid, type, value) values(4, 4, 'User4 value4.2')
insert into test (userid, type, value) values(4, 2, 'User4 value2.1')
insert into test (userid, type, value) values(4, 0, 'User4 value0.1')
Scenario 3 :
/*
Output should be as below
userid, value
-------------
5, User5 value2.1
6, User6 value0.3
*/
insert into test (userid, type, value) values(5, 2, 'User1 value1.1')
insert into test (userid, type, value) values(5, 2, 'User1 value2.1')
insert into test (userid, type, value) values(5, 0, 'User1 value0.1')
insert into test (userid, type, value) values(6, 0, 'User6 value0.1')
insert into test (userid, type, value) values(6, 0, 'User6 value0.2')
insert into test (userid, type, value) values(6, 0, 'User6 value0.3')
Highly appreciate any help.
Is this what you want?
select t.*
from (select t.*,
row_number() over (partition by user_id
order by (case when type = 1 then 1 when type = 3 then 2 when type = 2 then 3 else 4 end),
(case when type = 1 then id end) asc,
id desc
) as seqnum
from test t
) t
where seqnum = 1;

SQL Query JOIN on the same table for a given data

I have the following table and data:
CREATE TABLE TEST_TABLE (
ID NUMBER(6) NOT NULL,
COMMON_SEQ NUMBER(22),
NAME VARCHAR(20),
CONSTRAINT PK_CONST PRIMARY KEY (ID)
);
INSERT INTO TEST_TABLE (ID, COMMON_SEQ, NAME) VALUES (1001, NULL, 'Michelle');
INSERT INTO TEST_TABLE (ID, COMMON_SEQ, NAME) VALUES (1002, NULL, 'Tiberius');
INSERT INTO TEST_TABLE (ID, COMMON_SEQ, NAME) VALUES (1003, NULL, 'Marigold');
INSERT INTO TEST_TABLE (ID, COMMON_SEQ, NAME) VALUES (1004, 999, 'Richmond');
INSERT INTO TEST_TABLE (ID, COMMON_SEQ, NAME) VALUES (1005, 999, 'Marianne');
INSERT INTO TEST_TABLE (ID, COMMON_SEQ, NAME) VALUES (1006, NULL, 'Valentin');
INSERT INTO TEST_TABLE (ID, COMMON_SEQ, NAME) VALUES (1007, 888, 'Juliette');
INSERT INTO TEST_TABLE (ID, COMMON_SEQ, NAME) VALUES (1008, NULL, 'Lawrence');
Some records in this table are related to each other by the common value of COMMON_SEQ (for example COMMON_SEQ of 999 relates Richmond and Marianne).
How can I select all names based on given ID as an input?
I tried joining table to itself (works ok when COMMON_SEQ is null). This example returns Michelle record:
SELECT T.ID, T.COMMON_SEQ,T.NAME
FROM TEST_TABLE T
LEFT JOIN TEST_TABLE T2 ON NOT T.COMMON_SEQ is NULL
AND T.COMMON_SEQ=T2.COMMON_SEQ AND T.ID<>T2.ID
WHERE T.ID=1001
But it doesn't bring back 2 records for ID 1004. This example returns only Richmond record (but I need to return also Marianne record):
SELECT T.ID, T.COMMON_SEQ,T.NAME
FROM TEST_TABLE T
LEFT JOIN TEST_TABLE T2 ON NOT T.COMMON_SEQ is NULL
AND T.COMMON_SEQ=T2.COMMON_SEQ AND T.ID<>T2.ID
WHERE T.ID=1004
How can I improve/rewrite the query to return Richmond and Marianne records when I supply only one ID value (either 1004 or 1005)?
You could use:
SELECT *
FROM TEST_TABLE t
WHERE COMMON_SEQ IN (SELECT COMMON_SEQ
FROM TEST_TABLE t1
WHERE t1.ID = 1004)
OR t.ID = 1004;
DBFiddle Demo
Passing the same parameter twice to handle NULL in COMMON_SEQ.
Try this
SELECT COALESCE (ty.id, tx.id) AS id,
COALESCE (ty.common_seq, tx.common_seq) AS common_seq,
COALESCE (ty.name, tx.name) AS name
FROM test_table tx LEFT OUTER JOIN test_table ty
ON (tx.common_seq = ty.common_seq)
WHERE tx.ID = 1004;
With this you can avoid using IN or EXISTS and this is likely to be more performant.

Joining two Hierarchical queries to form larger Hierarchy

I have researched this and know I'm not the first to ask but I can't seem to get my head around it. I have created a simple example that I think will help me crack it if someone can provide the missing link!
I have a table of areas that contains continents and countries in a hierarchy.
I also have a table of places that contains cities and landmarks in a hierarchy. This table contains an area id column to join to the areas table.
create table areas
(
id NUMBER not null,
name VARCHAR2(200) not null,
parent_id NUMBER
);
-- Top Level
Insert into areas (id, name)
Values (1, 'Europe');
Insert into areas (id, name)
Values (2, 'Americas');
Insert into areas (id, name)
Values (3, 'Asia ex Japan');
Insert into areas (id, name)
Values (4, 'Japan');
-- Jurisdictions
Insert into areas (id, name, parent_id)
Values (5, 'UK', 1);
Insert into areas (id, name, parent_id)
Values (7, 'France', 1);
Insert into areas (id, name, parent_id)
Values (6, 'Germany', 1);
Insert into areas (id, name, parent_id)
Values (8, 'Italy', 1);
Insert into areas (id, name, parent_id)
Values (9, 'US', 2);
Insert into areas (id, name, parent_id)
Values (10, 'Australia', 3);
Insert into areas (id, name, parent_id)
Values (11, 'New Zealand', 3);
create table places
(
id NUMBER not null,
name VARCHAR2(200) not null,
area_id NUMBER,
parent_id NUMBER
);
Insert into places (id, name, area_id, parent_id)
Values (1, 'London', 5, NULL);
Insert into places (id, name, area_id, parent_id)
Values (2, 'Bath', 5, NULL);
Insert into places (id, name, area_id, parent_id)
Values (3, 'Liverpool', 5, NULL);
Insert into places (id, name, area_id, parent_id)
Values (4, 'Paris', 7, NULL);
Insert into places (id, name, area_id, parent_id)
Values (5, 'New York', 9, NULL);
Insert into places (id, name, area_id, parent_id)
Values (6, 'Chicago', 9, NULL);
Insert into places (id, name, area_id, parent_id)
Values (7, 'Kings Cross', 5, 1);
Insert into places (id, name, area_id, parent_id)
Values (8, 'Tower of London', 5, 1);
I can query these tables independently like this:
SELECT a.*, level FROM areas a
start with parent_id is null
connect by prior id = parent_id
SELECT p.*, level FROM places p
start with parent_id is null
connect by prior id = parent_id
Is someone able to show me the last step to join these into one query with four levels? I've been working with Oracle for years but somehow this never came up!
If there was no connect by prior in the places table, just a list of cities with an area id, would this be easier?
Thank you
Is it what you need?
with src as (
select 'A' type, a.id, a.name, a.parent_id, null area_id from areas a
union all
select 'P', -p.id id, p.name, -p.parent_id parent_id, area_id from places p)
select
src.*, level
from
src
start with
type = 'A' and parent_id is null
connect by
parent_id = prior id or
parent_id is null and area_id = prior id

Rails ActiveRecord friendly code from a Complex Join, Sum, and Group query

PROBLEM
Hello,
I am having no luck trying to break down this SQL statement into ActiveRecord/Rails friendly code and I'd like to learn how I can avoid a find_by_sql statement in this situation.
Scenario
I have users that create audits when they perform an action. Each audit is of a specific audit_activity. Each audit_activity is worth a certain number of points, based on score_weight. I need to find the total scores of each user, based on their total accumulated audit_activity score_weights. Eventually I'll need to rank them which means adding a sort to this as well.
My Code
Here is my sql and simplified versions of the tables in question. Any thoughts?
SQL with full column names (for clarity)
SELECT users.id, u.email, SUM(audit_activity.score_weight)
FROM users
JOIN audits ON users.id = audits.user_id
JOIN audit_activities ON audit_activities.id = audits.audit_activity_id
GROUP BY users.id;
Models: User, Audit, AuditActivity
User fields: id, email
class User < ActiveRecord::Base
include Clearance::User
has_many :audits
end
Audit fields: id, user_id, audit_activity_id
class Audit < ActiveRecord::Base
belongs_to :user
belongs_to :audit_activity
end
AuditActivity fields: id, score_weight
class AuditActivity < ActiveRecord::Base
has_many :audits
end
Example Data
Here is a set of SQL statements so you can play with similar data I'm working with and see what comes up when the concerned query is run. You should just be able to copy/paste the whole thing into a database query browser.
CREATE TABLE users(
id INTEGER NOT NULL,
email TEXT (25),
PRIMARY KEY (id)
);
CREATE TABLE audits(
id INTEGER NOT NULL,
user_id INTEGER,
audit_activity_id INTEGER,
PRIMARY KEY (id)
);
CREATE TABLE audit_activities(
id INTEGER NOT NULL,
score_weight INTEGER,
PRIMARY KEY (id)
);
INSERT INTO users(id, email)
VALUES(1, "1user#a.com");
INSERT INTO users(id, email)
VALUES(2, "2user#b.com");
INSERT INTO users(id, email)
VALUES(3, "3user#c.com");
INSERT INTO audits(id, user_id, audit_activity_id)
VALUES(1, 1, 1);
INSERT INTO audits(id, user_id, audit_activity_id)
VALUES(2, 1, 2);
INSERT INTO audits(id, user_id, audit_activity_id)
VALUES(3, 1, 1);
INSERT INTO audits(id, user_id, audit_activity_id)
VALUES(4, 1, 3);
INSERT INTO audits(id, user_id, audit_activity_id)
VALUES(5, 1, 1);
INSERT INTO audits(id, user_id, audit_activity_id)
VALUES(6, 1, 4);
INSERT INTO audits(id, user_id, audit_activity_id)
VALUES(7, 2, 4);
INSERT INTO audits(id, user_id, audit_activity_id)
VALUES(8, 2, 4);
INSERT INTO audits(id, user_id, audit_activity_id)
VALUES(9, 2, 4);
INSERT INTO audits(id, user_id, audit_activity_id)
VALUES(10, 3, 3);
INSERT INTO audits(id, user_id, audit_activity_id)
VALUES(11, 3, 2);
INSERT INTO audits(id, user_id, audit_activity_id)
VALUES(12, 3, 2);
INSERT INTO audits(id, user_id, audit_activity_id)
VALUES(13, 3, 2);
INSERT INTO audits(id, user_id, audit_activity_id)
VALUES(14, 3, 3);
INSERT INTO audits(id, user_id, audit_activity_id)
VALUES(15, 3, 1);
INSERT INTO audits(id, user_id, audit_activity_id)
VALUES(16, 3, 1);
INSERT INTO audit_activities(id, score_weight)
VALUES(1, 1);
INSERT INTO audit_activities(id, score_weight)
VALUES(2, 2);
INSERT INTO audit_activities(id, score_weight)
VALUES(3, 7);
INSERT INTO audit_activities(id, score_weight)
VALUES(4, 11);
The Query
Again, here is the query.
SELECT u.id, u.email, SUM(aa.score_weight)
FROM users u
JOIN audits a ON u.id = a.user_id
JOIN audit_activities aa ON aa.id = a.audit_activity_id
GROUP BY u.id;
User.sum( :score_weight, :include => {:audits => :audit_activity}, :group => 'users.id' )
It's easy enough to get your users, and iterate through the audits for each of them, summing up the values as you go. So it would be something like this:
users = User.find(:all)
users.each do |user|
puts "user: #{user.email}"
score = 0
user.audits.each do |audit|
puts " audit: #{audit.audit_activity.id} score: #{audit.audit_activity.score_weight}"
score += audit.audit_activity.score_weight
end
puts "total score for this user: #{score"
end
That will generate many separate queries, however, but that's not always a bad thing.
If the data volumes are going to be large, and as you say, you will want to sort by user score, then I think the answer will be to have a field with the current score on the user record, which gets updated every time an audit activity record is written. This can be done automatically with an association callback (i.e. an after_add method on the audit association on the User record). See http://guides.rubyonrails.org/association_basics.html#association-callbacks.