SQL: How to virtually combine two tables and link to the result? - sql

Imagine three tables:
user:
- id
- name (string)
event:
- id
- description (string)
- points (int)
history:
- user_id
- event_id
Now imagine that the tables are currently filled with the following data:
user:
id: 1, name: foo
id: 2, name: bar
id: 3, name: beef
event:
id: 1, description: "walked 5 miles", points: +10
id: 2, description: "awake the whole night", points: +15
id: 3, description: "wasn't naughty", points: +20
history:
id: 1, user_id: 1, event_id: 1
id: 2, user_id: 1, event_id: 3
id: 3, user_id: 2, event_id: 1
So the schema is something like "how many points does a user have". Currently, e.g., the user 1 has in sum 10+20 = 30 points, right?
I would like to add another table with things the user can "buy" for his/her points.
gift:
- id
- points (int)
- description (string)
which is filled with e.g.
gift:
id: 1, points: -30, description: "a bottle of beer"
id: 2, points: -5, description: "coffee"
My problem:
Currently I am creating a new entry in history when the user gets points. But how would you substract points when he buys a gift?
I thought about something like a combined table which includes the events and the gifts entries:
[USER]-----[HISTORY]-----[COMBINED]--+--[EVENTS]
|
+--[GIFTS]
But I cannot really join them because I need the gift table somewhere else.
I have absolutely no clue how to do that in SQL, it's a long time since I learned it and I unfortunately never used it since then. I hope somebody can point me into the right direction :)
Thanks!

Related

Extract value from JSON array for use in where clause - SQL Server

I have a SQL Server table which contains two columns; Player (VARCHAR), and Availability (JSON/VARCHAR). The data in this table is similar to the below:
Player Availability
-------+-------------------------------
John | { "Availability": [1, 3, 6] }
Yusuf | { "Availability": [3, 4, 7] }
Ben | { "Availability": [1, 2, 3] }
What I'm wanting is to write a query which will show me what players are available on a certain day (the numbers in the Availability column represent days).
Something like:
SELECT Player
FROM TableName
WHERE JSON_VALUE(Availability, '$.Availability') = 3;
This would return: John, Yusuf, and Ben.
Is this achievable?
you can use OPENJSON:
SELECT t.player, json.value
FROM TableName t
CROSS APPLY OPENJSON(Availability,'$.Availability') json
WHERE json.value = 3

Can PostgreSQL JOIN on jsonb array objects?

I am considering switching to PostgreSQL, because of the JSON support. However, I am wondering, if the following would be possible with a single query:
Let's say there are two tables:
Table 1) organisations:
ID (INT) | members (JSONB) |
------------+---------------------------------------------------------|
1 | [{ id: 23, role: "admin" }, { id: 24, role: "default" }]|
2 | [{ id: 23, role: "user" }]
Table 2) users:
ID (INT) | name TEXT | email TEXT |
------------+-----------+---------------|
23 | Max | max#gmail.com |
24 | Joe | joe#gmail.com |
Now I want to get a result like this (all i have is the ID of the organisation [1]):
ID (INT) | members (JSONB) |
------------+--------------------------------------------------------|
1 | [{ id: 23, name: "Max", email: "max#gmail.com", role:
"admin" },
{ id: 24, name: "Joe", email: "joe#gmail.com ", role:
"default" }]
(1 row)
I know this is not what JSONB is intended for and that there is a better solution for storing this data in SQL, but I am just curious if it would be possible.
Thanks!
Yes it is possible to meet this requirement with Postgres. Here is a solution for 9.6 or higher.
SELECT o.id, JSON_AGG(
JSON_BUILD_OBJECT(
'id' , u.id,
'name' , u.name,
'email' , u.email,
'role' , e.usr->'role'
)
)
FROM organisations o
CROSS JOIN LATERAL JSONB_ARRAY_ELEMENTS(o.data) AS e(usr)
INNER JOIN users u ON (e.usr->'id')::text::int = u.id
GROUP BY o.id
See this db fiddle.
Explanation :
the JSONB_ARRAY_ELEMENTS function splits the organisation json array into rows (one per user) ; it is usually used in combination with JOIN LATERAL
to join the users table, we access the content of the id field using the -> operator
for each user, the JSONB_BUILD_OBJECT is used to create a new object, by passing a list of values/keys pairs ; most values comes from the users table, excepted the role, that is taken from the organisation json element
the query aggregates by organisation id, using JSONB_AGG to generate a json array by combining above objects
For more information, you may also have a look at Postgres JSON Functions documentation.
There might be more ways to do that. One way would use jsonb_to_recordset() to transform the JSON into a record set you can join. Then create a JSON from the result using jsonb_build_object() for the individual members and jsonb_agg() to aggregate them in a JSON array.
SELECT jsonb_agg(jsonb_build_object('id', "u"."id",
'name', "u"."name",
'email', "u"."email",
'role', "m"."role"))
FROM "organisations" "o"
CROSS JOIN LATERAL jsonb_to_recordset(o."members") "m" ("id" integer,
"role" text)
INNER JOIN "users" "u"
ON "u"."id" = "m"."id";
db<>fiddle
What functions are available in detail depends on the version. But since you said you consider switching, assuming a more recent version should be fair.

How to do an inclusion AND exclusion join query in SQL/Active Record?

The Problem
I have three models, greatly simplified for the purpose of this question:
User:
- id
Event:
- id
- user_id (FK)
Stream:
- id
- event_id (FK)
- user_id (FK)
I'm using PostgresQL
Lets say I have two users, with IDs: 1, 2.
I want to retrieve all Events that has at least one Stream by User 1. I also want to exclude any Events that have at least one Stream by User 2.
Data Set
Stream: ID: 1, User: 1, Event: 1
Stream: ID: 2, User: 1, Event: 1
Stream: ID: 3, User: 1, Event: 2
Stream: ID: 4, User: 2, Event: 2
Stream: ID: 5, User: 2, Event: 3
Stream: ID: 6, User: 2, Event: 3
For purposes of this example, let's say these are all of the Streams in the system.
Current Active Record Query
# Keep in mind this is on the User model. So self=User 1
Event.joins(:streams)
.merge( Stream.where(user: self).where.not(user: User.find(2)) )
.....
Resulting Query:
SELECT events.*
FROM events
INNER JOIN streams ON streams.event_id = events.id
WHERE streams.user_id = 1 AND streams.user_id != 2
Current Result
Expected:
Event 1
Actual:
Event 1
Event 2
Currently this appropriately only returns Events having at least one Stream by User 1, however it does not exclude those Events which also have at least one Stream by User 2.
How can you properly execute this in SQL, and an unnecessary bonus, in Active Record?
Try this
SELECT events.*
FROM events ev
INNER JOIN streams st
ON st.event_id = ev.id
and st.user_id = 1
left join streams st1
ON st1.event_id = ev.id
and st1.user_id = 2
where st1.event_id is null

Postgresql: Find row via text in json array of objects

I am trying to find rows in my Postgresql Database where a json column contains a given text.
row schema:
id | name | subitems
-----------------------------------------------------------------
1 | "item 1" | [{name: 'Subitem A'}, {name: 'Subitem B'}]
2 | "item 2" | [{name: 'Subitem C'}, {name: 'Subitem D'}]
My wanted result for query 'Subitem B'
id | name | subitems
-----------------------------------------------------------------
1 | "item 1" | [{name: 'Subitem A'}, {name: 'Subitem B'}]
I can search for the first subitem like this:
WHERE lower(subitems->0->>\'name\') LIKE '%subitem a%'
But obviously I can't find any other subitem but the first one this way.
I can get all the names of my subitems:
SELECT lower(json_array_elements(subitems)->>'name') FROM ...
But it gives me 2 rows containing the names:
lower
----------------------------------------------------------------
"subitem a"
"subitem b"
What I actually need is 1 row containing the item.
Can anyone tell me how to do that?
You're almost there. Your query:
SELECT lower(json_array_elements(subitems)->>'name') FROM foo;
That gets you what you want to filter against. If you plop that into a subquery, you get the results you're looking for:
# SELECT *
FROM foo f1
WHERE 'subitem a' IN
(SELECT lower(json_array_elements(subitems)->>'name')
FROM foo f2 WHERE f1.id = f2.id
);
id | name | subitems
----+--------+------------------------------------------------
1 | item 1 | [{"name": "Subitem A"}, {"name": "Subitem B"}]
(1 row)
Edited to add
Okay, to support LIKE-style matching, you'll have to go a bit deeper, putting a subquery into your subquery. Since that's a bit hard to read, I'm switching to using common table expressions.
WITH all_subitems AS (
SELECT id, json_array_elements(subitems)->>'name' AS subitem
FROM foo),
matching_items AS (
SELECT id
FROM all_subitems
WHERE
lower(subitem) LIKE '%subitem a%')
SELECT *
FROM foo
WHERE
id IN (SELECT id from matching_items);
That should get you what you need. Note that I moved the call to lower up a level, so it's alongside the LIKE. That means the filtering condition is in one spot, so you can switch to a regular expression match, or whatever, more easily.

Rails ActiveRecord only return distinct records based on a column

Suppose I have an ActiveRecord model called Checkin and I only want to return Checkin records distinct by the user_id. Is there way to do this that returns an AR relation? I need to apply multiple scopes to it so I would prefer to avoid find_by_sql.
Example:
Let's say we have the following records
id: 1
user_id : 5
location_id: 12
id: 2
user_id: 25
location_id: 12
id: 3
user_id: 5
location_id: 12
I want to be able to say something like:
Checkin.distinct_by(:user_id) and have that return only rows 1 and 2 (because user_id=5 should be distinct). I would like this to be an ActiveRecord:Relation ideally so that I can apply other conditions onto this.
I think you should make your question more clear, can you provide some example? I don't know this is what you are looking for: you can try to use "select" or "collect" function after your where statement.