Filtering items by relation using knex query - sql

I'm trying to filter items by relation using a knex query. I'm almost there (I think) but struggling a little and could use some help as this is new to me.
I have a list of users who are following people and have followers. I'm trying to return a list of users who I'm not already following. Below is my code so far:
const users = await knex("users-permissions_user").whereNotExists(
function () {
this.select("*")
.from("users_followers__users_followings")
.where("user_id", "users-permissions_user.id")
.where("follower_id", id);
}
);
This returns a list of users who currently have no followers and users where I'm the only follower. Any users who I follow and also have more followers are still returned. I thought like would achieve this type of filter but I must be doing it wrong.
Here is how the table for the followers/following relation appears in my db:
And here is the data that would be returned from the above query:
[
{
"id": "138",
"followers": [
{
"id": "143"
}
]
},
{
"id": "140",
"followers": [
{
"id": "160"
},
{
"id": "136"
}
]
},
{
"id": "135",
"followers": []
},
{
"id": "136",
"followers": []
}
]
As you can see, users with no followers are returned as are users who I'm not already following but users who have multiple followers, including me (ID 160), are returned when they should be omitted.
Any advice would be greatly appreciated!

So the reason that users like 140 are being returned even though they are following you (160) is because they are following at least one other person who isn't you, which means your where clause will match them.
If you want to return only users who are not following you, you could achieve this by replacing your left join and where clause with a where not exists clause. In knex that would look something like:
qb.whereNotExists(function() {
this.select(1)
.from('users_followers__users_followings')
.where('users_followers__users_followings.user_id', knex.ref('users-permissions_user.id'))
.where('users_followers__users_followings.follower_id', id);
});

Related

Is a Select() done against a key in the table's struct?

I have a database where one of the tables is managed through
type Token struct {
gorm.Model
Value string
User User
UserID uint
}
At some point, I am trying to retrieve all the elements in that table, but limited to only one field (Users). When doing the query without the Select(), I get everything as expected:
var tokens []Token
db.
Preload("User").
// Select("User").
Find(&tokens)
An extract from the output:
(...)
{
"ID": 27,
"CreatedAt": "2022-03-11T20:24:01.0505503+01:00",
"UpdatedAt": "2022-03-11T20:24:01.0505503+01:00",
"DeletedAt": null,
"Value": "admintoken",
"User": {
"ID": 27,
"CreatedAt": "2022-03-11T20:24:01.0521975+01:00",
"UpdatedAt": "2022-03-11T20:24:01.0521975+01:00",
"DeletedAt": null,
"UserName": "admin",
"UserType": {
"ID": 0,
"Name": "",
"UserID": 0
}
}
}
(...)
Since I only want to get the "User" object, I tried
var tokens []Token
db.
Preload("User").
Select("User").
Find(&tokens)
but now the output is empty.
What should the Select() be against?
To select all Users that have at least one token you could use the following query:
var users []User
db.Where("id IN (?)","SELECT DISTINCT(user_id) FROM tokens").Find(&users)
I don't think Preload is a good option since it would do 2 queries to retrieve the result, why do 2 queries if you can retrieve expected result in only 1 query
To retrieve users who has token code can be updated as below:
var users []User
db.Table("users").Select("users.id, users.name").Joins("inner join tokens on tokens.user_id = users.id").Find(&users)
rows, err := db.Table("users").Select("users.id, users.name").Joins("inner join tokens on tokens.user_id = users.id").Rows()
for rows.Next() {
...
}

How not to expose duplicated (normalize?) nodes via GraphQL?

Given "user has many links" (what means a link was created by a user) DB entities relations, I want to develop API to fetch links along with users so that the returned data does not contain duplicated users.
In other words, instead of this request:
query {
links {
id
user {
id email
}
}
}
that returns the following data:
{
"data": {
"links": [
{
"id": 1,
"user": {
"id": 2,
"email": "user2#example.com"
}
},
{
"id": 2,
"user": {
"id": 2,
"email": "user2#example.com"
}
}
]
}
}
I want to make a request like this (note the "references" column):
query {
links {
id
userId
}
references {
users {
id
email
}
}
}
that returns associated users without duplicates:
{
"data": {
"links": [
{
"id": 1,
"userId": 2
},
{
"id": 2,
"userId": 2
},
],
"references": {
"users": [
{
"id": 2,
"email": "user2#example.com"
}
]
}
}
}
That should reduce amount of data transferred between client and server that adds a bit of speed boost.
Is there ready common implementation on any language of that idea? (Ideally, seeking for Ruby)
It's not a query or server role to normalize data.
there are no such possibilities in GraphQL specs;
server must return all asked fields within queried [response] structure;
... but you can implement some:
standarized (commonly used) pagination (relay style edges/nodes, nodes only or better both);
query [complexity] weights to promote this optimized querying style - separate problem;
reference dictionary field within queried type;
links {
egdes {
node {
id
title
url
authorId
# possible but limited usage with heavy weights
# author {
# id
# email
# }
}
}
pageInfo {
hasNextPages
}
referencedUsers {
id
email
}
}
where:
User has id and email props;
referencedUsers is [User!] type;
node.author is User type;
Normalizing Graphql client, like Apollo, can easily access cached user fields without making separate requests.
You can render (react?) some <User/> component (within <Link /> component) passing node.authorId as an argument like <User id={authorId} />. User component can useQuery hook with cache-only policy to read user props/fields.
See Apollo docs for details. You should implement this for yourself and document this to help/guide API users.

how to select a single item and get it's relations in faunadb?

I have two collections which have the data in the following format
{
"ref": Ref(Collection("Leads"), "267824207030650373"),
"ts": 1591675917565000,
"data": {
"notes": "voicemail ",
"source": "key-name",
"name": "Glenn"
}
}
{
"ref": Ref(Collection("Sources"), "266777079541924357"),
"ts": 1590677298970000,
"data": {
"key": "key-name",
"value": "Google Ads"
}
}
I want to be able to query the Leads collection and be able to retrieve the corresponding Sources document in a single query
I came up with the following query to try and use an index but I couldn't get it to run
Let(
{
data: Get(Ref(Collection('Leads'), '267824207030650373'))
},
{
data: Select(['data'],Var('data')),
source: q.Lambda('data',
Match(Index('LeadSourceByKey'), Get(Select(['source'], Var('data') )) )
)
}
)
Is there an easy way to retrieve the Sources document ?
What you are looking for is the following query which I broke down for you in multiple steps:
Let(
{
// Get the Lead document
lead: Get(Ref(Collection("Leads"), "269038063157510661")),
// Get the source key out of the lead document
sourceKey: Select(["data", "source"], Var("lead")),
// use the index to get the values via match
sourceValues: Paginate(Match(Index("LeadSourceValuesByKey"), Var("sourceKey")))
},
{
lead: Var("lead"),
sourceValues: Var("sourceValues")
}
)
The result is:
{
lead: {
ref: Ref(Collection("Leads"), "269038063157510661"),
ts: 1592833540970000,
data: {
notes: "voicemail ",
source: "key-name",
name: "Glenn"
}
},
sourceValues: {
data: [["key-name", "Google Ads"]]
}
}
sourceValues is an array since you specified in your index that there will be two items returned, the key and the value and an index always returns the array. Since your Match could have returned multiple values in case it wasn't a one-to-one, this becomes an array of an array.
This is only one approach, you could also make the index return a reference and Map/Get to get the actual document as explained on the forum.
However, I assume you asked the same question here. Although I applaud asking questions on stackoverflow vs slack or even our own forum, please do not just post the same question everywhere without linking to the others. This makes many people spend a lot of time while the question is already answered elsewhere.
You might probably change the Leads document and put the Ref to Sources document in source:
{
"ref": Ref(Collection("Leads"), "267824207030650373"),
"ts": 1591675917565000,
"data": {
"notes": "voicemail ",
"source": Ref(Collection("Sources"), "266777079541924357"),
"name": "Glenn"
}
}
{
"ref": Ref(Collection("Sources"), "266777079541924357"),
"ts": 1590677298970000,
"data": {
"key": "key-name",
"value": "Google Ads"
}
}
And then query this way:
Let(
{
lead: Select(['data'],Get(Ref(Collection('Leads'), '267824207030650373'))),
source:Select(['source'],Var('lead'))
},
{
data: Var('lead'),
source: Select(['data'],Get(Var('source')))
}
)

Elasticsearch filter results excluding by id

I need to return results that do not include docs with certain ids. Elasticsearch allows us to specify which ids are allowed but I see no way to disallow certain ids. In my case I want to not return things the user has already seen so the list will be different for each user.
You can achieve this by adding a bool/must_not filter containing an ids filter with an array of ids you don't want to appear, like this:
{
"query": {
"bool": {
"must": [
... <--- your other filters go here
],
"must_not": [
{
"ids": {
"values": [
"id1", "id2" <--- add all the ids you DON'T want in here
]
}
}
]
}
}
}

How to manage fine grain permissions in Elasticsearch?

I need to store in a consistent way the role/groups that can access the information but I'm not sure what's the best way to do it.
Summary: I have 2 kinds of docs "tweet" and "blog":
At tweet level, I store the group name allowed to access the information
blog is more complex, there are metadata (title, description, nature, ...) but some of those informations can be restricted to some groups of user (only admin, or logged_in users)
What the best way to map this with Elasticsearch ?
As of today, I end up with documents like:
/tweet/455
{
id: 112,
ugroups: [ "restricted_user", "admin" ],
description: "foo",
},
{
id: 113,
ugroups: [ "anonymous" ]
description: "foo",
}
and
/blog/500
{
id: 5,
fields: [
{
"nature": {
"value": "foo",
"ugroup": [ "admin" ]
}
}
]
}
{
id: 6,
fields: [
{
"comment": {
"value": "foo",
"ugroup": [ "anonymous" ]
}
}
]
}
When user want to search in tweet, that's easy, I build a term query with words submitted by the user and I append the groups the user belongs to this query.
But how to make a query that will take this "ugroup" thing at various level ?
Ideally I could issue a query like:
search in tweet with tweet.ugroup: "anonymous" and in blog with blog.fields.*.ugroup: "anonymous"
Is there a way to write such a query ?