How to sum values when joining tables? - sql

<hyperbole>Whoever answers this question can claim credit for solving the world's most challenging SQL query, according to yours truly.</hyperbole>
Working with 3 tables: users, badges, awards.
Relationships: user has many awards; award belongs to user; badge has many awards; award belongs to badge. So badge_id and user_id are foreign keys in the awards table.
The business logic at work here is that every time a user wins a badge, he/she receives it as an award. A user can be awarded the same badge multiple times. Each badge is assigned a designated point value (point_value is a field in the badges table). For example, BadgeA can be worth 500 Points, BadgeB 1000 Points, and so on. As further example, let's say UserX won BadgeA 10 times and BadgeB 5 times. BadgeA being worth 500 Points, and BadgeB being worth 1000 Points, UserX has accumulated a total of 10,000 Points ((10 x 500) + (5 x 1000)).
The end game here is to return a list of top 50 users who have accumulated the most badge points.
Can you do it?

My sample tables are:
user:
+-------+--------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+-------+--------------+------+-----+---------+-------+
| id | int(11) | YES | | NULL | |
| name | varchar(200) | YES | | NULL | |
+-------+--------------+------+-----+---------+-------+
badge:
+-------+---------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+-------+---------+------+-----+---------+-------+
| id | int(11) | YES | | NULL | |
| score | int(11) | YES | | NULL | |
+-------+---------+------+-----+---------+-------+
award:
+----------+---------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+----------+---------+------+-----+---------+-------+
| id | int(11) | YES | | NULL | |
| user_id | int(11) | YES | | NULL | |
| badge_id | int(11) | YES | | NULL | |
+----------+---------+------+-----+---------+-------+
Thus the query is:
SELECT user.name, SUM(score)
FROM badge JOIN award ON badge.id = award.badge_id
JOIN user ON user.id = award.user_id
GROUP BY user.name
ORDER BY 2
LIMIT 50

No, that's not the worlds most challenging query. Something simple like this should do it:
select u.id, u.name, sum(b.points) as Points
from users u
inner join awards a on a.user_id = u.id
inner join badges b on b.id = a.badge_id
group by u.id, u.name
order by 2 desc
limit 50

Related

get category with child categories next to it

I have a categories table.
Categories
CREATE TABLE IF NOT EXISTS public.categories
(
id integer NOT NULL DEFAULT nextval('categories_id_seq'::regclass),
name text COLLATE pg_catalog."default" NOT NULL,
description text COLLATE pg_catalog."default",
shell text COLLATE pg_catalog."default",
createdat timestamp with time zone DEFAULT now(),
"isChild" boolean DEFAULT false,
"motherCategory" text COLLATE pg_catalog."default" DEFAULT 'none'::text,
CONSTRAINT categories_pkey PRIMARY KEY (id)
)
im looking for an output similar to this:
+-------------------------------------------------------+-----------------------------------------------------+
| motherCategory | childCategories |
+----+----------+---------------+----------+------------+----+---------+--------------+----------+------------+
| id | name | description | shell | createdat | id | name | description | shell | createdat |
+----+----------+---------------+----------+------------+----+---------+--------------+----------+------------+
| 1 | mother 1 | mother 1 desc | m1/shell | 13/12/2013 | 2 | child 1 | child 1 desc | c1/shell | 01/01/2014 |
| | | | | +----+---------+--------------+----------+------------+
| | | | | | 3 | child 2 | child 2 desc | c2/shell | 6/9/2069 |
+----+----------+---------------+----------+------------+----+---------+--------------+----------+------------+
| 4 | mother 2 | mother 2 desc | m2/shell | 01/02/2033 | none |
+----+----------+---------------+----------+------------+----+---------+--------------+----------+------------+
| 5 | mother 3 | mother 3 desc | m3/shell | 11/11/2011 | 6 | child 3 | child 3 desc | c3/shell | 05/05/2005 |
+----+----------+---------------+----------+------------+----+---------+--------------+----------+------------+
its a fairly complex query, well atleast for my level, basically my categories table has both mother and child categories in one place, and differs them with two columns (isChild: boolean, motherCategory: integer), isChild lets sql know that category is a child, and motherCategory stores the id of the mother category located in the same table.
as for the query i think its self explanatory, basically i want to show a list of categories where every mother category is stored next to all its children, displaying all their data aswell, and incase a mother doesn't have children, it returns none as the child element.
To be completely honest im new to sql, so im not even sure if an
output like this is possible, but incase you have any idea, help me
out!
Thanks
Please help me if you have any ideas
As per my opinion You should separate two table for mother and child
I am assuming that motherCategory is having id of mother of particular child
So your expected output can be generate by below query
SELECT
m.id, m.name, m.description, m.shell, m.createdat
,c.id, c.name, c.description, c.shell, c.createdat
FROM
(SELECT id, name, description, shell, createdat,motherCategory FROM categories WHERE isChild=0)m LEFT JOIN
(SELECT id, name, description, shell, createdat,motherCategory FROM categories WHERE isChild=1)c
ON m.id=c.motherCategory

'ON REPLACE CASCADE' in sqlite

Let's say I'm tracking donations for a charity:
CREATE TABLE people {
id INTEGER PRIMARY KEY,
name TEXT NOT NULL,
account_number INTEGER NOT NULL,
UNIQUE(name),
UNIQUE(account_number)
};
CREATE TABLE donations {
donor_id INTEGER NOT NULL
REFERENCES people(id)
ON DELETE CASCADE
ON UPDATE CASCADE,
amount FLOAT NOT NULL
};
I can guarantee that a person's name and account number are both individually unique.
Sometimes I need to update either a user's name or their account number. I receive data that contains both a name and an account number, one of which will have changed. Because both name and account_number are individually unique, it shouldn't matter which one has changed - so long as one of them is the same as an existing entry in the table, the correct row will be replaced.
This is my understanding of what will happen if I do this with a REPLACE statement. The person in the people table will be deleted, along with their referenced donation (because of ON DELETE CASCADE), and a new person added with no associated donations:
people donations
| id | name | account_number | | donor_id | amount |
|----|------|----------------| |----------|--------|
| 1 | John | 11111111111111 | | 1 | 100.00 |
| 2 | Mark | 22222222222222 | | 1 | 200.00 |
| 2 | 250.00 |
>>> REPLACE INTO people ( name, account_number ) VALUES ( 'Suzy', 22222222222222 );
people donations
| id | name | account_number | | donor_id | amount |
|----|------|----------------| |----------|--------|
| 1 | John | 11111111111111 | | 1 | 100.00 |
| 3 | Suzy | 22222222222222 | | 1 | 200.00 |
How can I ensure that referenced values are propagated correctly? This is my desired end result (where '3' is any people ID so long as it matches):
people donations
| id | name | account_number | | donor_id | amount |
|----|------|----------------| |----------|--------|
| 1 | John | 11111111111111 | | 1 | 100.00 |
| 3 | Suzy | 22222222222222 | | 1 | 200.00 |
| 3 | 250.00 |
This is my understanding of what will happen if I do this with a
REPLACE statement. The person in the people table will be deleted,
along with their referenced donation (because of ON DELETE CASCADE),
and a new person added with no associated donations
Correct, but the referenced donations will be permanently deleted.
See the demo.
Sometimes I need to update either a user's name or their account
number. I receive data that contains both a name and an account
number, one of which will have changed.
What you can do is a simple UPDATE:
UPDATE people
SET name = 'Suzy',
account_number = 22222222222222
WHERE name = 'Suzy' OR account_number = 22222222222222
See the demo.

SQL - get from table and join with same table

I have two tables, ChatRoom and ChatRoomMap, I want to get a list of chatrooms a user belongs to, along with all the other users in each chatroom.
// this contains a map of user to chatroom, listing which user is in what room
CREATE TABLE ChatRoomMap
(
user_id bigint NOT NULL,
chatroom_id text NOT NULL,
CONSTRAINT uniq UNIQUE (userid, roomid)
)
// sample values
==========================
| user_id | chatroom_id |
| 1 | 7 |
| 1 | blue |
| 7 | red |
==========================
And
CREATE TABLE ChatRoom
(
id text NOT NULL,
admin bigint,
name text,
created timestamp without time zone NOT NULL DEFAULT now(),
CONSTRAINT uniqid UNIQUE (id)
)
// sample values
======================================================
| id | admin | name | timestamp |
| blue | 7 | blue room | now() |
| red | 2 | red | now() |
| 7 | 11 | mine | now() |
======================================================
To get a list of rooms a user is in, I can do:
SELECT DISTINCT ON (id) id, userid, name, admin
FROM ChatRoomMap, ChatRoom WHERE ChatRoomMap.user_id = $1 AND ChatRoomMap.chatroom_id = ChatRoom.id
This will get me a distinct list of chat rooms a user is in.
I would like to get the distinct list of rooms along with all the users in each room (concatenation of all as a separate column), how can this be done?
Example result:
=======================================================
| user_id | chatroom_id | name | admin | other_users |
| 10 | 7 | One | 1 | 1, 2, 3, 8 |
| 10 | 4 | AAA | 10 | 7, 11, 15 |
=======================================================
First up, use proper joins - the explicit join syntax was introduced to the SQL92 standard and the major vendors implemented it in the early 2000's (and it's the only way to achieve an outer join).
Try this:
SELECT DISTINCT id, crm2.user_id, name, admin,
FROM ChatRoomMap crm1
JOIN ChatRoom ON crm1.chatroom_id = ChatRoom.id
LEFT JOIN ChatRoomMap crm2 ON crm2.chatroom_id = crm1.chatroom_id
AND crm2.user_id != crm1.user_id -- only other users
WHERE crm1.user_id = $1
The LEFT JOIN is needed in case there are no other users in the room it will still list the room (with a null for other user id).

SQL design for notification of new registered users

I'm with a great difficulty in formulate a SQL for a module of notifications when a new user register.
I have a database of Notifications, I set up a notification to be sent. Examples:
Send notification when a man and blue eyes register;
Send notification when a woman register;
Send a notification when a blue-eyed woman, brown and work in the company Foo;
With these rules we can see that there can be several possibilities (so the table columns are optional).
Some details:
The table columns are defined as integers because are FK. I just did not put tables because the structure is unnecessary, since the SQL will only relashionship between User and Notification;
The date field is used to store both the date of registration of the notice of such person. So I can only filter to notify the new register of user;
Table Structure
User:
+------------+----------+------+-----+---------+------------+
| Field | Type | Null | Key | Default | Extra |
+------------+----------+------+-----+---------+------------+
| Id | int(11) | NO | PRI | | auto_incre |
| Gender | int(11) | YES | | | |
| HairColor | int(11) | YES | | | |
| EyeColor | int(11) | YES | | | |
| Company | int(11) | YES | | | |
| Date | datetime | NO | | | |
| ... | | | | | |
+------------+----------+------+-----+---------+------------+
Notification:
+------------+----------+------+-----+---------+------------+
| Field | Type | Null | Key | Default | Extra |
+------------+----------+------+-----+---------+------------+
| Id | int(11) | NO | PRI | | auto_incre |
| Gender | int(11) | YES | | | |
| HairColor | int(11) | YES | | | |
| EyeColor | int(11) | YES | | | |
| Company | int(11) | YES | | | |
| Date | datetime | NO | | | |
+------------+----------+------+-----+---------+------------+
Initial idea
The initial idea I had was doing a select for each possibility and joining via union:
-- Selects new users by gender notification
SELECT *
FROM Notification
inner join User on (
User.Date >= Notification.Date and
Notification.Gender = User.Gender and
Notification.HairColor is null and
Notification.EyeColor is null and
Notification.Company is null
)
union all
-- Selects new users by gender and hair color notification
SELECT *
FROM Notification
inner join User on (
User.Date >= Notification.Date and
Notification.Gender = User.Gender and
Notification.HairColor = User.HairColor and
Notification.EyeColor is null and
Notification.Company is null
)
-- ... and so on, doing a select for each option, resulting in 16 selects (4 columns: gender, hair color, eye color and company)
My question is:
Is there another way I can do this SQL querying all the possibilities of notifications in a more easy?
Following this structure of 4 columns we already have 16 selects. In my real structure will have more columns with something unfeasible to keep it that way.
Is there any other suggestion storage structure of the data for a better way to do this functionality?
SELECT *
FROM Notification
inner join User on (
User.Date >= Notification.Date and
(Notification.Gender is null or Notification.Gender = User.Gender) and
(Notification.HairColor is null or Notification.HairColor = User.HairColor) and
(Notification.EyeColor is null Notification.EyeColor = User.EyeColor) and
(Notification.Company is null or Notification.Company = User.Company)
)
This way you get every set of user with the notification stored in the tables.
This is the way I would implement this user registration / notification functionality:
Three tables: Users, Notif_type, Notif_queue.
A trigger on insert on table Users which calls a stored procedure SendNotification(user_id).
The stored proc will have the logic which you can change overtime without having to modify the schema/data. The logic will be:
to select the type of notification (form Notif_type) the new user should receive based on your rules;
to insert a row in Notif_queue which holds a FK to user_id and notif_type_id, so that the functionality notifying the user is completely de-coupled from the notification rules.
why can't you just use the one table "user" and put an extra field/flag called [Notified] so that every time you want to send notifications just refer it to the flag.
i find it irrelevant to use the notification table.

MySQL syntax for Join Update

I have two tables that look like this
Train
+----------+-------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+----------+-------------+------+-----+---------+-------+
| TrainID | varchar(11) | NO | PRI | NULL | |
| Capacity | int(11) | NO | | 50 | |
+----------+-------------+------+-----+---------+-------+
Reservations
+---------------+-------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+---------------+-------------+------+-----+---------+----------------+
| ReservationID | int(11) | NO | PRI | NULL | auto_increment |
| FirstName | varchar(30) | NO | | NULL | |
| LastName | varchar(30) | NO | | NULL | |
| DDate | date | NO | | NULL | |
| NoSeats | int(2) | NO | | NULL | |
| Route | varchar(11) | NO | | NULL | |
| Train | varchar(11) | NO | | NULL | |
+---------------+-------------+------+-----+---------+----------------+
Currently, I'm trying to create a query that will increment the capacity on a Train if a reservation is cancelled. I know I have to perform a Join, but I'm not sure how to do it in an Update statement. For Example, I know how to get the capacity of a Train with given a certain ReservationID, like so:
select Capacity
from Train
Join Reservations on Train.TrainID = Reservations.Train
where ReservationID = "15";
But I'd like to construct the query that does this -
Increment Train.Capacity by ReservationTable.NoSeats given a ReservationID
If possible, I'd like to know also how to Increment by an arbitrary number of seats. As an aside, I'm planning on deleting the reservation after I perform the increment in a Java transaction. Will the delete effect the transaction?
Thanks for the help!
MySQL supports a multi-table UPDATE syntax, which would look approximately like this:
UPDATE Reservations r JOIN Train t ON (r.Train = t.TrainID)
SET t.Capacity = t.Capacity + r.NoSeats
WHERE r.ReservationID = ?;
You can update the Train table and delete from the Reservations table in the same transaction. As long as you do the update first and then do the delete second, it should work.
Here is another example of an UPDATE statement that contains joins to determine the value that is being updated. In this case, I want to update the transactions.payee_id with the related account payment id, if the payee_id is zero (wasn't assigned).
UPDATE transactions t
JOIN account a ON a.id = t.account_id
JOIN account ap ON ap.id = a.pmt_act_id
SET t.payee_id = a.pmt_act_id
WHERE t.payee_id = 0