Text array of integers for joining tables in Postgres - postgresql

I have two tables
Entity
House
In house table, there is a column entity_id of type text.
And entity_id will store multiple house ids.
So it will look like
entity_id (text)
------------------
[1,2]
[3,6]
Now I have to join this entity table with house table.
How will I achieve this.
I know this may not be a good design. Though now this my responsibility to do this.

This should work:
CREATE TABLE entity(
id integer,
entity_id integer[],
details text
);
CREATE TABLE houses(
id integer,
house_name text
);
INSERT INTO entity(id, entity_id, details)
VALUES
(1, '{1,3}', 'Left side houses'),
(2, '{2,4}', 'Right side houses');
INSERT INTO houses(id, house_name)
VALUES
(1, 'Left 1'),
(2, 'Right 1'),
(3, 'Left 2'),
(4, 'Right 2');
----------------------------------------------
SELECT h.id as house_id, h.house_name, e.details
FROM houses h
LEFT JOIN entity e
ON h.id = ANY(e.entity_id);
| house_id | house_name | details |
| -------- | ---------- | ----------------- |
| 1 | Left 1 | Left side houses |
| 2 | Right 1 | Right side houses |
| 3 | Left 2 | Left side houses |
| 4 | Right 2 | Right side houses |

As Laurenz says in comment: it seems to be an error of modelisation here.
So I will not directly answer to the question but I will give a most correct structure :
CREATE TABLE entity (id int primary key);
CREATE TABLE house (id int primary key);
CREATE TABLE entity_house (id bigserial, identity int references entity(id), idhouse int references house(id));
INSERT INTO entity VALUES (1), (2);
INSERT INTO house VALUES (3), (56);
INSERT INTO entity_house (identity, idhouse) VALUES (1,56), (2,56);
SELECT e.*, h.*
FROM entity_house eh
INNER JOIN entity e ON eh.identity = e.id
INNER JOIN house h ON eh.idhouse = h.id;

Related

Postgres: Query for list of ids in a mapping table and create If they don't exist

Assume we have the following table whose purpose is to autogenerate a numeric id for distinct (name, location) tuples:
CREATE TABLE mapping
(
id bigserial PRIMARY KEY,
name text NOT NULL,
location text NOT NULL,
);
CREATE UNIQUE INDEX idx_name_loc on mapping(name location)
What is the most efficient way to query for a set of (name, location) tuples and autocreate any mappings that don't already exist, with all mappings (including the ones we created) being returned to the user.
My naive implementation would be something like:
SELECT id, name, location
FROM mappings
WHERE (name, location) IN ((name_1, location_1)...(name_n, location_n))
do something with the results in a programming language of may choice to work out which results are missing.
INSERT
INTO mappings (name, location)
VALUES (missing_name_1, missing_loc_1), ... (missing_name_2, missing_loc_2)
ON CONFLICT DO NOTHING
This gets the job done but I get the feeling there's probably something that can a) be done in pure sql and b) is more efficient.
You can use DISTINCT to get all possible values for the two columns, and CROSS JOIN to get their Carthesian product.
LEFT JOIN with the original table to get the actual records (if any):
CREATE TABLE mapping
( id bigserial PRIMARY KEY
, name text NOT NULL
, location text NOT NULL
, UNIQUE (name, location)
);
INSERT INTO mapping(name, location) VALUES ('Alice', 'kitchen'), ('Bob', 'bedroom' );
SELECT * FROM mapping;
SELECT n.name, l.location, m.id
FROM (SELECT DISTINCT name from mapping) n
CROSS JOIN (SELECT DISTINCT location from mapping) l
LEFT JOIN mapping m ON m.name = n.name AND m.location = l.location
;
Results:
DROP SCHEMA
CREATE SCHEMA
SET
CREATE TABLE
INSERT 0 2
id | name | location
----+-------+----------
1 | Alice | kitchen
2 | Bob | bedroom
(2 rows)
name | location | id
-------+----------+----
Alice | kitchen | 1
Alice | bedroom |
Bob | kitchen |
Bob | bedroom | 2
(4 rows)
And if you want to physically INSERT the missing combinations:
INSERT INTO mapping(name, location)
SELECT n.name, l.location
FROM (SELECT DISTINCT name from mapping) n
CROSS JOIN (SELECT DISTINCT location from mapping) l
WHERE NOT EXISTS(
SELECT *
FROM mapping m
WHERE m.name = n.name AND m.location = l.location
)
;
SELECT * FROM mapping;
INSERT 0 2
id | name | location
----+-------+----------
1 | Alice | kitchen
2 | Bob | bedroom
3 | Alice | bedroom
4 | Bob | kitchen
(4 rows)

Join Same Column from Same Table Twice

I am new to the SQL world. I would like to replace the Games.home_team_id and Games.away_team_id with the Corresponding entry in the Teams.name column.
First I start by initializing a small table of data:
CREATE TABLE Games (id,away_team_id INT,away_team_score INT,home_team_id INT, home_team_score INT);
CREATE TABLE
INSERT INTO Games (id,away_team_id,away_team_score,home_team_id,home_team_score)
VALUES
(1,1,1,2,4),
(2,1,3,3,2),
(3,1,1,4,1),
(4,2,0,3,2),
(5,2,3,4,1),
(6,3,5,4,2)
;
INSERT 0 6
Then I create a template of a reference table
CREATE TABLE Teams (id INT, name VARCHAR(63);
CREATE TABLE
INSERT INTO Teams (id, name)
VALUES
(1, 'Oogabooga FC'),
(2, 'FC Milawnchair'),
(3, 'Ron\'s Footy United'),
(4, 'Pylon City FC')
;
INSERT 0 4
I would like to have the table displayed as such:
| id | away_team_name | away_team_score | home_team_name | home_team_score |
-----+----------------+-----------------+----------------+------------------
| 1 | Oogabooga FC | 1 | FC Milawnchair | 4 |
...
I managed to get a join query to show the first value from Teams.name in the away_team_name field using this JOIN:
SELECT
Games.id,
Teams.name AS away_team_name,
Games.away_team_score,
Teams.name AS home_team_name,
Games.home_team_score
FROM Games
JOIN Teams ON Teams.id = Games.away_team_id
;
| id | away_team_name | away_team_score | home_team_name | home_team_score |
-----+----------------+-----------------+----------------+------------------
| 1 | Oogabooga FC | 1 | Oogabooga FC | 4 |
...
But now I am stuck when I call it twice as a JOIN it shows the error:
SELECT
Games.id,
Teams.name AS away_team_name,
Games.away_team_score,
Teams.name AS home_team_name,
Games.home_team_score
FROM Games
JOIN Teams ON Teams.id = Games.away_team_id
JOIN Teams ON Teams.id = Games.home_team_id
;
ERROR: table name "teams" specified more than once
How do you reference the same reference the same column of the same table twice for a join?
You need to specify an alias for at least one of the instances of the table; preferably both.
SELECT
Games.id,
Away.name AS away_team_name,
Games.away_team_score,
Home.name AS home_team_name,
Games.home_team_score
FROM Games
JOIN Teams AS Away ON Away.id = Games.away_team_id
JOIN Teams AS Home ON Home.id = Games.home_team_id
Explanation: As you are joining to the same table twice, the DBMS (in your case, PostgreSQL) is unable to identify which of the tables you're referencing to when using its fields; the way to solve this is to assign an alias to the joined tables the same way you assign aliases for your columns. This way you can specify which of the joined instances are you referencing to in your SELECT, JOIN and WHERE statements.

How to query PostgreSQL nested JSONB in deeper level?

I'm new to JSON/JSONB datatypes and I'm having some problems selecting the JSONB elements from deeper levels.
Here is an example table:
CREATE TABLE person (id integer, details jsonb);
INSERT INTO person (id, details) VALUES
("id": 1, {"favorites":{"colors":"blue", "colors":"red"}}),
("id": 2),
("id": 3, {"favorites":{"colors":"blue", "colors":"red", "colors":"green"}});
I would like to select all p.details ->'favorites' ->>'colors', for example:
------------------------
| id | Favorite colors |
------------------------
| 1 | blue |
------------------------
| 1 | red |
------------------------
| 3 | blue |
------------------------
| 3 | red |
------------------------
| 3 | green |
------------------------
The following (or similar) gives empty column for Favorite colors (tried also with jsonb_array_elements):
SELECT p.id, p.details ->'favorites' ->>'colors' AS "Favorite colors"
FROM "person" p;
SELECT p.id, json_array_elements((p.details ->'favorites' ->>'colors')::json) AS "Favorite colors"
FROM "person" p;
The following works with a simple query, but it crashes the browser when retrieving more data (the end users use the queries in a web browser).
SELECT p.id, ((json_array_elements((json_array_elements(p.details::json)::json->>'favorites')::json))::json->>'colors') AS "Favorite colors"
FROM "person" p;
Seems that the browser is consuming lot of memory. I've found some posts saying that Multiple calls to json_array_elements slow down the query: https://www.postgresql.org/message-id/52EEEC37.9040305#2ndquadrant.com
Any advice on this?
There is some syntax errors in sql queries you provided.
If I correctly guessed db schema than they should looks like this:
DROP TABLE person;
CREATE TABLE person (id integer, details jsonb);
INSERT INTO person (id, details) VALUES
(1, '{"favorites":[{"colors":"blue"}, {"colors":"red"}]}'::jsonb),
(2, '{}'::jsonb),
(3, '{"favorites":[{"colors":"blue"}, {"colors":"red"}, {"colors":"green"}]}'::jsonb);
If this is correct than you can get the results via:
select id, jsonb_array_elements(details->'favorites')->'colors' from person

How to view linked data in OrientDB

I have two tables, described as below.
Table 1: countries
c_id, int
c_name, varchar(20) (PK)
Sample records in this table are.
c_id | c_name
1 | USA
2 | UK
3 | PAK
Table 2: immigrants
i_id, int
i_name, varchar(20)
i_country, int (FK)
Sample records in this table are.
i_id | i_name | i_country
1 | John | 1
2 | Graham | 2
3 | Ali | 3
Question 1:
I want to create two classes (tables) in OrientDB but I need to know what should be the data type of the FK field and what to insert in it. I mean what should I write in query to insert the id of the PK table. Does it need to be #rid? How?
QUESTION 2:
What is the OrientDB SQL for producing the following output.
i_id | i_name | i_country | c_id | c_name
1 | John | 1 | 1 | USA
2 | Graham | 2 | UK
3 | Ali 3 | PAK
with OrientDB you can avoid the creation of FK fields and join operations by using direct Edge links between records. For example:
create class Country extends V;
create class Immigrant extends V;
create class comesFrom extends E;
create property Country.c_id integer;
create property Country.c_name String;
create property Immigrant.i_id integer;
create property Immigrant.i_name String;
insert into Country(c_id, c_name) values (1, USA);
insert into Country(c_id, c_name) values (2, UK);
insert into Country(c_id, c_name) values (3, PAK);
insert into Immigrant(i_id, i_name) values (1, John);
insert into Immigrant(i_id, i_name) values (2, Graham);
insert into Immigrant(i_id, i_name) values (3, Ali);
Now you can connect directly the record you want (I used an Edge called 'comesFrom' and subqueries to link the id fields but you could also directly use the #RID field)
create edge comesFrom from (select from Immigrant where i_id = 1) to (select from Country where c_id = 1);
create edge comesFrom from (select from Immigrant where i_id = 2) to (select from Country where c_id = 2);
create edge comesFrom from (select from Immigrant where i_id = 3) to (select from Country where c_id = 3);
Finally you can query the fields you want without join operations:
select i_id, i_name, out('comesFrom').c_id as c_id, out('comesFrom').c_name as c_name
from Immigrant unwind c_id, c_name
----+------+----+------+----+------
# |#CLASS|i_id|i_name|c_id|c_name
----+------+----+------+----+------
0 |null |1 |John |1 |USA
1 |null |2 |Graham|2 |UK
2 |null |3 |Ali |3 |PAK
----+------+----+------+----+------
In Orient Studio you should obtain a graph like this:
OrientDB does not support join.
You should declare the i_country field in class Immigrant as Link to Country class, or you can use Graph Model if you want bidirectional relationship.
If you want to use Direct link
you can insert immigrants like this
insert into Immigrant set id=1, name ='John', country = (select from country where id = 1 )
then
select id,name,country.id,country.name form Immigrant
you could speed up the insertion and link operations with a javascript function like this:
so the edge is automatically created when you decide to create a new person. You can also decide where to place the person:
Could it be helpful for you ?
I think that you have created a Document DB because I reproduced the issue by creating a Document DB and not a Graph DB and I get, like you, the same exception. This is because if you want to work with Vertices and Edges you must use the Graph DB type.

T-SQL - Complicated recursion with sum

I apologize for the long problem description, but I was unable to break it down more than this.
Before reading on, keep in mind that my end goal is T-SQL (maybe some recursive CTE?). However, a shove in the right direction would be much appreciated (I've tried a million things and have been scratching my head for hours).
Consider the following problem: I have a table of categories which is self-referencing through ParentCategoryID->CategoryID:
-----------------------
| Category |
-----------------------
| *CategoryID |
| Name |
| ParentCategoryID |
-----------------------
This type of table allows me to build a tree structure, say:
-----------------
| parentCategory|
-----------------
/ | \
child(1) child child
/ \
child(2) child(3)
where "child" means "child category" (ignore the numbers, I'll explain them later). Obviously I can have as many children as I like at any level.
Every day, a program I've written stores values to a table "ValueRegistration", which is connected to "Category" like so:
------------------------ ---------------------- ----------------------
| ValueRegistration | | Item | | Category |
------------------------ ---------------------- ----------------------
| *RegID | | *ItemID | | *CategoryID |
| Date |>-------| CategoryID |>---------| Name |
| ItemID | | ItemTypeID | | ParentCategoryID |
| Value | ---------------------- ----------------------
------------------------ Y
|
|
---------------------
| ItemType |
---------------------
| *ItemTypeID |
| ItemType |
---------------------
As you can see, a ValueRegistration concerns a specific Item, which in turn belongs to a certain category. The category may or may not have a parent (and grandparents and great-grandparents and so on). For instance, it may be the child all the way to the bottom left (number 2) in the tree I illustrated above. Also, an Item is of a certain ItemType.
My goal:
I register values to the ValueRegistration table daily (in other words, Date and ItemID combined is also a primary key). I want to be able to retrieve a resultset on the following form:
[ValueRegistration.Date, ItemType.ItemTypeID, Category.CategoryID, Value]
which seems simple enough (it's obviously just a bunch of joins). However, I also want results for rows that actually don't exist in the ValueRegistration table, namely results in which the values of sibling nodes for a given date and itemID are summed, and a new row is produced where ValueRegistration.Date and ItemType.ItemTypeID are the same as in the child nodes but where CategoryID is that of the parent of the child nodes. Keep in mind that an Item will NOT exist for this type of row in the resultset.
Consider for instance a scenario where I have ValueRegistrations for child 2 and 3 on a bunch of dates and a bunch of ItemIDs. Obviously, each registration belongs to a certain ItemType and Category. It should be clear to the reader that
ValueRegistration.Date, ItemType.ItemTypeID, Category.CategoryID
is a sufficient key to identify a specific ValueRegistration (in other words, it's possible to solve my problem without having to create temporary Item rows), and so I can inner join all tables and, for instance, the following result:
ValueReg.Date, ItemType.ItemTypeID, Category.CategoryID, ValueReg.Value
08-mar-2013, 1, 5, 200
08-mar-2013, 1, 6, 250
Assume now that I have four category rows that look like this:
1, category1, NULL
2, category2, 1
5, category5, 2
6, category6, 2
I.e. category 1 is the parent of category 2, and category 2 is the parent of categories 5 and 6. Category 1 has no parent. I now wish to append the following rows to my resultset:
08-mar-2013, 1, 2, (200+250)
08-mar-2013, 1, 1, (200+250+sum(values in all other childnodes of node 1)
Remember:
the solution needs to be recursive, so that it is performed upwards in the tree (until NULL is reached)
an Item row will NOT exist for tree nodes which are calculated, so CategoryID and ItemTypeID must be used
yes, I know I could simply create "virtual" Item rows and add ValueRegistrations when I originally INSERT INTO my database, but that's solution is prone to errors, particularly if other programmers code up against my database but either forget or are unaware that results must be passed up to parent node. A solution that calculates this on request instead is much safer and, frankly, much more elegant.
I've tried to set something up along the lines of this, but I seem to get stuck with having to group by Date and ItemTypeID, and that's not allowed in a CTE. The programmer in me just wants to make a recursive function, but I'm really struggling to do that in SQL.
Anyone have an idea where to begin, what things I should try, or even (fingers crossed) a solution?
Thanks!
Alexander
EDIT:
SQL FIDDLE
CREATE TABLE ItemType(
ItemTypeID INT PRIMARY KEY,
ItemType VARCHAR(50)
);
CREATE TABLE Category(
CategoryID INT PRIMARY KEY,
Name VARCHAR(50),
ParentCategoryID INT,
FOREIGN KEY(ParentCategoryID) REFERENCES Category(CategoryID)
);
CREATE TABLE Item(
ItemID INT PRIMARY KEY,
CategoryID INT NOT NULL,
ItemTypeID INT NOT NULL,
FOREIGN KEY(CategoryID) REFERENCES Category(CategoryID),
FOREIGN KEY(ItemTypeID) REFERENCES ItemType(ItemTypeID)
);
CREATE TABLE ValueRegistration(
RegID INT PRIMARY KEY,
Date DATE NOT NULL,
Value INT NOT NULL,
ItemID INT NOT NULL,
FOREIGN KEY(ItemID) REFERENCES Item(ItemID)
);
INSERT INTO ItemType VALUES(1, 'ItemType1');
INSERT INTO ItemType VALUES(2, 'ItemType2');
INSERT INTO Category VALUES(1, 'Category1', NULL); -- Top parent (1)
INSERT INTO Category VALUES(2, 'Category2', 1); -- A child of 1
INSERT INTO Category VALUES(3, 'Category3', 1); -- A child of 1
INSERT INTO Category VALUES(4, 'Category4', 2); -- A child of 2
INSERT INTO Category VALUES(5, 'Category5', 2); -- A child of 2
INSERT INTO Category VALUES(6, 'Category6', NULL); -- Another top parent
INSERT INTO Item VALUES(1, 4, 1); -- Category 4, ItemType 1
INSERT INTO Item VALUES(2, 5, 1); -- Category 5, ItemType 1
INSERT INTO Item VALUES(3, 3, 1); -- Category 3, ItemType 1
INSERT INTO Item VALUES(4, 1, 2); -- Category 1, ItemType 2
INSERT INTO ValueRegistration VALUES(1, '2013-03-08', 100, 1);
INSERT INTO ValueRegistration VALUES(2, '2013-03-08', 200, 2);
INSERT INTO ValueRegistration VALUES(3, '2013-03-08', 300, 3);
INSERT INTO ValueRegistration VALUES(4, '2013-03-08', 400, 4);
INSERT INTO ValueRegistration VALUES(5, '2013-03-09', 120, 1);
INSERT INTO ValueRegistration VALUES(6, '2013-03-09', 220, 2);
INSERT INTO ValueRegistration VALUES(7, '2013-03-09', 320, 3);
INSERT INTO ValueRegistration VALUES(8, '2013-03-09', 420, 4);
-- -------------------- RESULTSET I WANT ----------------------
-- vr.Date | ItemType | CategoryTypeID | Value
-- ------------------------------------------------------------
-- 2013-03-08 | 'ItemType1' | 'Category4' | 100 Directly available
-- 2013-03-08 | 'ItemType1' | 'Category5' | 200 Directly available
-- 2013-03-08 | 'ItemType1' | 'Category3' | 300 Directly available
-- 2013-03-08 | 'ItemType1' | 'Category2' | 100+200 Calculated tree node
-- 2013-03-08 | 'ItemType1' | 'Category1' | 100+200+300 Calculated tree node
-- 2013-03-08 | 'ItemType2' | 'Category1' | 400 Directly available
-- 2013-03-09 | 'ItemType1' | 'Category4' | 120 Directly available
-- 2013-03-09 | 'ItemType1' | 'Category5' | 220 Directly available
-- 2013-03-09 | 'ItemType1' | 'Category3' | 320 Directly available
-- 2013-03-09 | 'ItemType1' | 'Category2' | 120+220 Calculated tree node
-- 2013-03-09 | 'ItemType1' | 'Category1' | 120+220+320 Calculated tree node
-- 2013-03-09 | 'ItemType2' | 'Category1' | 420 Directly available
If you replace all joins to the table Category with joins to this dynamic relation, you will get the hierarchy you are lookinfg for:
with Category as (
select * from ( values
(1,'Fred',null),
(2,'Joan',1),
(3,'Greg',2),
(4,'Jack',2),
(5,'Jill',4),
(6,'Bill',3),
(7,'Sam',6)
) Category(CategoryID,Name,ParentCategoryID)
)
, Hierarchy as (
select 0 as [Level],* from Category
-- where Parent is null
union all
select super.[Level]+1, sub.CategoryID, super.Name, super.ParentCategoryID
from Category as sub
join Hierarchy as super on super.CategoryID = sub.ParentCategoryID and sub.ParentCategoryID is not null
)
select * from Hierarchy
-- where CategoryID = 6
-- order by [Level], CategoryID
For example, uncommenting the two lines at the bottom will yield this result set:
Level CategoryID Name ParentCategoryID
----------- ----------- ---- ----------------
0 6 Bill 3
1 6 Greg 2
2 6 Joan 1
3 6 Fred NULL