Need to create a View for the following - sql

Here are three tables
Table users {
id uuid [pk, default: `gen_random_uuid()`]
auth_id uuid [ref: - auths.id]
org_id uuid [ref: - orgs.id]
access_group text [default: 'DEFAULT']
created_at int [default: `now()::int`]
updated_at int
age int
status text
}
Table op_user_access_groups {
op_id uuid [pk, ref: > ops.id]
access_group text [pk, default: 'DEFAULT']
}
Table op_users {
op_id uuid [pk, ref: > ops.id]
user_id uuid [pk, ref: > users.id]
access boolean [default: false]
}
The table users has user info and he/she belongs to certain organization (org_id)
The table op_user_access_groups has the information regarding an operator having access to what all access_groups. The op_id belongs to the org_id
The table op_users has information about users (user_id) that can be accessed by op_id irrespective of which group a user belongs to.
I want to create a view such that if I do
select * from <that view> where op_id = ?
I should get the users the operator has access to.
Any help is appreciated :)

You should use a JOIN query to create such a view. Something like this:
CREATE VIEW v AS
SELECT * FROM users
JOIN op_users ON users.id=op_users.op_id
JOIN op_user_access_groups ON op_user_access_groups.access_group=users.access_group
WHERE op_users.access = true
It's not exactly clear how your data model works from your question, but creating a view with the JOIN that answers your question is the correct response here.

Related

GORM preload: How to use a custom table name

I have a GORM query with a preload that works just fine because I'm binding it to a struct called "companies" which is also the name of the corresponding database table:
var companies []Company
db.Preload("Subsidiaries").Joins("LEFT JOIN company_prod ON company_products.company_id = companies.id").Where("company_products.product_id = ?", ID).Find(&companies)
Now I want to do something similar, but bind the result to a struct that does not have a name that refers to the "companies" table:
var companiesFull []CompanyFull
db.Preload("Subsidiaries").Joins("LEFT JOIN company_prod ON company_products.company_id = companies.id").Where("company_products.product_id = ?", ID).Find(&companies)
I've simplified the second call for better understanding, the real call has more JOINs and returns more data, so it can't be bound to the "companies" struct.
I'm getting an error though:
column company_subsidiaries.company_full_id does not exist
The corresponding SQL query:
SELECT * FROM "company_subsidiaries" WHERE "company_subsidiaries"."company_full_id" IN (2,1)
There is no "company_subsidiaries.company_full_id", the correct query should be:
SELECT * FROM "company_subsidiaries" WHERE "company_subsidiaries"."company_id" IN (2,1)
The condition obviously gets generated from the name of the struct the result is being bound to. Is there any way to specify a custom name for this case?
I'm aware of the Tabler interface technique, however it doesn't work for Preload I believe (tried it, it changes the table name of the main query, but not the preload).
Updated: More info about the DB schema and structs
DB schema
TABLE companies
ID Primary key
OTHER FIELDS
TABLE products
ID Primary key
OTHER FIELDS
TABLE subsidiaries
ID Primary key
OTHER FIELDS
TABLE company_products
ID Primary key
Company_id Foreign key (companies.id)
Product_id Foreign key (products.id)
TABLE company_subsidiaries
ID Primary key
Company_id Foreign key (companies.id)
Subsidiary_id Foreign key (subsidiaries.id)
Structs
type Company struct {
Products []*Product `json:"products" gorm:"many2many:company_products;"`
ID int `json:"ID,omitempty"`
}
type CompanyFull struct {
Products []*Product `json:"products" gorm:"many2many:company_products;"`
Subsidiaries []*Subsidiary `json:"subsidiaries" gorm:"many2many:company_products;"`
ID int `json:"ID,omitempty"`
}
type Product struct {
Name string `json:"name"`
ID int `json:"ID,omitempty"`
}
type Subsidiary struct {
Name string `json:"name"`
ID int `json:"ID,omitempty"`
}
Generated SQL (by GORM)
SELECT * FROM "company_subsidiaries" WHERE "company_subsidiaries"."company_full_id" IN (2,1)
SELECT * FROM "subsidiaries" WHERE "subsidiaries"."id" IN (NULL)
SELECT companies.*, company_products.*, FROM "companies" LEFT JOIN company_products ON company_products.company_id = companies.id WHERE company_products.product_id = 1
Seems like the way to go in this case may be to customize the relationship in your CompanyFull model. Using joinForeignKey the following code works.
type CompanyFull struct {
Products []*Product `json:"products" gorm:"many2many:company_products;joinForeignKey:ID"`
Subsidiaries []*Subsidiary `json:"subsidiaries" gorm:"many2many:company_subsidiaries;joinForeignKey:ID"`
ID int `json:"ID,omitempty"`
}
func (CompanyFull) TableName() string {
return "companies"
}
func main(){
...
result := db.Preload("Subsidiaries").Joins("LEFT JOIN company_products ON company_products.company_id = companies.id").Where("company_products.product_id = ?", ID).Find(&companies)
if result.Error != nil {
log.Println(result.Error)
} else {
log.Printf("%#v", companies)
}
For more info regarding customizing the foreign keys used in relationships, take a look at the docs https://gorm.io/docs/many_to_many.html#Override-Foreign-Key

How do I filter data between multiple columns into new columns after INNER JOINS?

Brand new to SQL, so apologies that I don't really know how to word the question or find an existing answer. Let me explain further. I'm creating a Chat app for fun with a DM system. I have a table (dms_history) setup that has a row gen. for every new distinct chat between 2 users w/ the last DM being sent between the 2 users. eg:
CREATE TABLE users (
uid SERIAL PRIMARY KEY,
pid VARCHAR(40),
uname VARCHAR(50) NOT NULL UNIQUE,
email valid_email,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE TABLE dms (
dmid SERIAL PRIMARY KEY,
uid INT NOT NULL REFERENCES users(uid),
recip INT NOT NULL REFERENCES users(uid),
msg TEXT NOT NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
-- TABLE I'M TALKING ABOUT
CREATE TABLE dms_history (
user1 INT REFERENCES users(uid),
user2 INT REFERENCES users(uid),
last_dm INT REFERENCES dms(dmid),
PRIMARY KEY(user1, user2),
CHECK (user1 < user2)
);
Say I'm trying to get all the data w/ joins on this table (dms_history) for client user uid#2. eg:
SELECT
u1.uid as u1id,
u1.uname as u1name,
u2.uid as u2id,
u2.uname as u2name,
dms.dmid,
dms.msg
FROM dms_history h
INNER JOIN users u1 ON h.user1 = u1.uid
INNER JOIN users u2 ON h.user2 = u2.uid
INNER JOIN dms ON h.last_dm = dms.dmid
WHERE u1.uid = 2 OR u2.uid = 2;
That query I thought of is close to what I want, but what I really want is only to show the user opposite of the client user being queried (so NOT user uid#2 alice) along w/ the last message sent. How do I filter or UNION? those columns into a new column. Picture to explain what I want:
Think of Twitter DMs and how it shows the users you're DMing and a snippet of the last message sent.
Apologies if I did a poor job explaining, it's not my strong suit.
After doing some more reading the past few hours I found I could use subqueries with SELECTs and UNIONs to achieve what I was after. Not sure how efficient it is, but it works.
WITH chats AS (SELECT * FROM dms_history h WHERE h.user1 = 2 OR h.user2 = 2)
SELECT
chats2.user1 as recip_id,
u.uname as recip_uname,
chats2.last_dm as dmid,
dms.msg,
dms.created_at
FROM (
SELECT chats.user1, chats.last_dm FROM chats
UNION
SELECT chats.user2, chats.last_dm FROM chats
) AS chats2
INNER JOIN users u ON chats2.user1 = u.uid
INNER JOIN dms ON chats2.last_dm = dms.dmid
WHERE chats2.user1 != 2;

return joins as json with postgres 10

I have the following structure
task -> hasMany links -> hasOne user
what I would like to see returned is
task.id | [
{ linkType: 'foo', userid:1, taskid:1,
user : { name: 'jon', lastname: 'smith'},
... ]
being a sql noob, I have managed to get so far as this
select task.id, json_agg(links.*) as links from task
left outer join links on task.id = links.taskid
join "user" on "user".id = links.userid
group by task.id ;
which gives me
task.id | [{ linkType: 'foo', userid:1, taskid:1}, ... ]
but obviously missing the user
I'm kinda stuck now on how to add the user property to each of the link array items
I've read several documents but they always seem to stop at the first join
the schema design is
create table task (id int GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY)
create table "user" (id int GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, username text)
create table links (taskid integer references task (id), userid integer references "user" (id) );
Your example does not reference the table structure you provided, because "user" has a name and a lastname column in the former and a single username column in the latter, but you did provide enough information to give an answer. I am using the two-column user, because it allows to show how to construct nested JSON objects.
SELECT task.id,
json_agg(
json_build_object(
'linkType', 'foo',
'taskid', links.taskid,
'userid', links.userid,
'user', json_build_object('name', u.name, 'lastname', u.lastname)
)
) AS links
FROM task
LEFT OUTER JOIN links ON task.id = links.taskid
JOIN "user" u ON u.id = links.userid
GROUP BY task.id;
The general problem of building json objects with the appropriate key values is discussed in this excellent DBA.SE answer. I have adopted what is listed there as solution number 3 because I think it is the most flexible and the most readable. Your tastes might differ.

How to replace a row from a table with another row from another table?

I have the following code on SQL:
select u.openid, u.screenname, svd.user_name
from gw_svd_prefix_assignment svd
join user_ u
on u.screenname = svd.USER_NAME;
now it will show three rows, screenname, user_name and openID. The screenname and user_name are the exact same, that's why I joined them, but I want to change the user_name to the value of openID which is different.
How can I do so?
EDIT Below is an example:
OPENID SCREENNAME USER_NAME
==========================================
Smith.A Smith.Alan Smith.Alan
Someone.J Someone.Juan Someone.Juan
Foo.V Foo.Vallery Foo.Vallery
Hee.L Hee.Lee Hee.Lee
I want the table to look like:
OPENID SCREENNAME USER_NAME
==========================================
Smith.A Smith.Alan Smith.A
Someone.J Someone.Juan Someone.J
Foo.V Foo.Vallery Foo.V
Hee.L Hee.Lee Hee.L
so I want to replace the values in User_Name with the corresponding ones from OPENID
Try this
select u.openid, u.screenname, u.openid user_name
from gw_svd_prefix_assignment svd
join user_ u
on u.screenname = svd.USER_NAME;
If screenname is not unique in the user_ table, then the requirement doesn't make sense (which row in the user_ table should be used to update a row in svd, if the screenname is not unique?)
If screenname is unique, then the update can be done very easily as shown below, but this requires a UNIQUE constraint on screenname (or a Primary Key constraint). If a constraint doesn't exist currently, it can be added with an ALTER TABLE ... ADD CONSTRAINT ... statement.
update ( select user_name, openid, screenname
from svd join usr on svd.user_name = usr.screenname )
set user_name = openid;

Database Schema for Claims Authentication

On a database I have the tables: USERS, USERS_PROFILES, USERS_CLAIMS.
create table dbo.USERS
(
Id int identity not null,
Username nvarchar (120) not null,
Email nvarchar (120) not null
);
create table dbo.USERS_PROFILES
(
Id int not null,
[Name] nvarchar (80) not null
);
create table dbo.USERS_CLAIMS
(
Id int not null,
[Type] nvarchar (200) not null,
Value nvarchar (200) not null,
);
I am using Claims authorization. When a user signs up and Identity is created.
The identity contains claims and each claim has a type and a value:
UsernameType > Username from USERS
EmailType > Email from USERS
NameType > Name from USERS_PROFILES
RoleType > Directly from USERS_CLAIMS
So I am creating the Identity from many columns in 3 tables.
I ended up with this because I migrated to Claims Authentication.
QUESTION
Should I move the Username, Email and Name to USERS_CLAIMS?
The USERS_PROFILES table would disappear ...
And USERS table would contain only info like "UserId, LastLoginDate, CreatedDate, ..."
If I want get a user by username I would just get the Claim of type username ...
If I want to sign in the user I just get all claims and create the identity.
So the Identity Model is much similar to the SQL tables.
Does this make sense? How would you design the tables?
Thank You,
Miguel
You are creating a key value store. They are a nightmare to query in SQL. Consider the difficulty of querying user attributes by a value on the USER_CLAIMS table. Example:
-- Users with name and email by username
SELECT p.ID, p.Username, p.Name, p.Email, u.LastLoggedIN
FROM USER_PROFILES p
INNER JOIN Users u on p.ID = u.ID
WHERE p.ID = #UserID
-- Users with name and email by username with a claims table
-- Does not specify whether there is only one email, so this could return multiple
-- rows for a single user.
SELECT p.ID, cUName.Value as Username, cName.Value as Name, cEMail.Value as Email, u.LastLoggedIN
FROM Users u
LEFT OUTER JOIN USER_CLAIMS cName ON u.ID = cName.ID and cName.[Type] = 'http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name'
LEFT OUTER JOIN USER_CLAIMS cUName ON u.ID = cUName.ID and cUName.[Type] = 'http://schemas.xmlsoap.org/ws/2005/05/identity/claims/privatepersonalidentifier'
LEFT OUTER JOIN USER_CLAIMS cEmail ON u.ID = cEmail.ID and cEmail.[Type] = 'http://schemas.xmlsoap.org/ws/2005/05/identity/claims/email'
WHERE p.ID = #UserID
Can a user have multiple profiles? If not, there is no need for the "USERS_PROFILES" table. Keep the "Username" and "Email" columns on the "USERS" table. If you put them on the "USERS_CLAIMS" table, you would be storing redundant information anytime a user files a claim.
I am not sure what kind of tracking you'd like to have for your users, but I would recommend having a separate table that tracks when a user signs in. Something like this:
CREATE TABLE USERS_LOG (user_id INT, log_in DATETIME);
You can then get rid of the "LastLoginDate" on your "USERS" table and do a join to get the last time the user signed in. It'll give you more ways to track your users and you won't be creating blocks on your "USERS" table by updating it constantly.