Working with a JSON array of objects in MYSQL - mysql

I have an SQL table:
CREATE TABLE pu_events(
int eid AUTO_INCREMENT PRIMARY KEY,
varchar(20) title,
varchar(255) description,
int(11) start_date UNSIGNED,
int(11) end_date UNSIGNED,
timestamp created DEFAULT CURRENT_TIMESTAMP,
json members
)
I plan on populating the members field with a members json object which will be an array of objects containing the user id (uid) and status of attending members, such as:
{members: [{uid:1, status:0}, {uid:2, status:1}]}
But I'm having trouble finding any resources which describe how to correctly reference this object structure to manipulate it, for example if i wish to 'register' a user to the event, to append their object to the array of members like: (members.push({uid:3, status:0}), or to update the status of a given user once they are confirmed or resign from the event, like: (update members set status = 2 where uid = 1;).
I understand that the pseudo-c I've used is a combination of js & mysql, and i also understand that MySQL now has JSON functions for manipulating this datatype, but I'm confused with the best way to approach this particular use case.
Many thanks for any advice in advance!

The proper solution is to create another table:
CREATE TABLE pu_event_members (
event_id INT NOT NULL,
user_id INT NOT NULL,
status TINYINT NOT NULL,
PRIMARY KEY (event_id, user_id)
);
Then it's easy to register a new member of the event:
INSERT INTO pu_event_members SET event_id=?, user_id=?, status=?
Or update their status:
UPDATE pu_event_members SET status=? WHERE event_id=? AND user_id=?

Working example but only select:
select pu_events.*
from pu_events, JSON_TABLE(members, "$.members[*].uid" COLUMNS(f INT PATH '$')) as list
where list.f = 1
Mysql 8 has support JSON_TABLE
https://dev.mysql.com/doc/refman/8.0/en/json-search-functions.html
https://dev.mysql.com/doc/refman/8.0/en/json-table-functions.html

Related

Can I have a string of TIMESTAMPS in MySQL?

I am just wondering to know can I have a string of TIMESTAMPs in a MySQL table schema like following:
CREATE TABLE IF NOT EXISTS auth (
id BINARY(16) PRIMARY KEY,
email VARCHAR(64) UNIQUE NOT NULL,
password VARCHAR(64) NOT NULL,
admin INT NOT NULL DEFAULT 0,
created_at [TIMESTAMP]
);
Unfortunately the above code gives me a syntax error!
My idea is to have a list of TIMESTAMs to store every future updates inside of it, something like ['2023-01-01' , '2023-02-02' , ....]
If this is not possible in my suggested way, how can I store any changes of one column of a table like created_at column? Should I do it in the web-server?

MySQL conditional INSERT based on user-provided data and existing data

Let's say I have a few hypothetical tables: User, Item, and Sale.
CREATE TABLE User (
id INT PRIMARY KEY NOT NULL AUTO_INCREMENT,
name VARCHAR(255) NOT NULL,
email VARCHAR(255) NOT NULL,
password VARCHAR(255) NOT NULL
);
CREATE TABLE Item (
id INT PRIMARY KEY NOT NULL AUTO_INCREMENT,
upc VARCHAR(255) NOT NULL,
description VARCHAR(255) NOT NULL,
price DECIMAL(5,2) NOT NULL
userId INT NOT NULL,
FOREIGN KEY(userId) REFERENCES User(id)
);
CREATE TABLE Sale (
id INT PRIMARY KEY NOT NULL AUTO_INCREMENT,
quantity INT NOT NULL,
total DECIMAL(5,2) NOT NULL,
itemId INT NOT NULL,
FOREIGN KEY(itemId) REFERENCES Item(id)
);
Each user can add multiple items, and can sell multiple quantities of each item. A record of each sale is going to go into the Sale table, but I have to make sure that the Item ID being entered into the Sale table is actually owned by the User who is "creating" the entry.
I've thought about a couple of ways of doing this from an application layer (e.g., JDBC).
Do a SELECT to make sure the User owns that Item.
SELECT id FROM Item WHERE id = ? AND userId = ?
If there is a match (i.e., rows returned), the User owns that Item and can insert a Sale record for that Item. This method seems a bit inefficient, however, since I have to do multiple, separate queries in order to accomplish one task. Having a connection pool (and thus reusing the same connection for each query) will help performance a little, but I'm not sure by how much.
Do a "conditional INSERT" via INSERT ... SELECT:
INSERT INTO Sale(quantity, total, itemId)
SELECT 4, 5.00, 3 FROM Dual
WHERE EXISTS(SELECT id FROM Item WHERE id = ? AND userId = ?);
I really like the idea of this option, but there's an outstanding issue that I haven't been able to work around:
The query itself would be done from an application. Parts of the insert statement are raw values, which are not known until the last second. And since the only way to do a "conditional insert" is to SELECT data from some table (dummy or otherwise), you can't put ? placeholders for a prepared statement for column names.
In other words, the 4, 5.00, and 3 in the above statement are raw values, and the only way I know of to get them into the SQL string is to do concatenation:
String sql = "INSERT INTO Sale(quantity, total, itemId) SELECT "
+ quantity + ", " + total + ...;
Which leaves the door wide open to potential SQL injection attacks. It's a bit trickier to do if the Java variables quantity and total are numeric data types (i.e., can't have quotes), but it's still a loophole that I don't want to leave open.
Is there a good way to accomplish what I'm trying to do efficiently in one SQL statement? Or is the best way option (1) above?

modeling issue with field with 4 values (1 or more)

lets say I have an account object in my application, which currently represented as:
CREATE TABLE Account (
accountId int NOT NULL AUTO_INCREMENT,
name varchar(255) NOT NULL,
PRIMARY KEY (accountId)
);
Now, Account object need to also have Solution field...and Status have 4 different possible values:
Solution1, Solution2, Solution3, Solution4
What would be the right way to represent it in the database?
Account can have few statuses, and status can have few accounts...
So at first I thought create in the db table of Solutions and than have another table to hold the relationship, but its seems too complicated for a field that have only 4 possible values...
Create a junction table to represent the relationships between accounts and solutions:
CREATE TABLE account_solution (
accountId int NOT NULL,
solutionId int NOT NULL
PRIMARY KEY (accountId, solutionId)
)
For your solution table, since there are only 4 values, you might be able to take advantage of MySQL's enum type, e.g.
CREATE TABLE solution
solutionId int NOT NULL PRIMARY KEY,
status ENUM('Solution1', 'Solution2', 'Solution3', 'Solution4')
);
You can use set Mysql SET type
CREATE TABLE Account (
accountId int NOT NULL AUTO_INCREMENT,
name varchar(255) NOT NULL,
status set('Solution1','Solution2','Solution3','Solution4') NOT NULL,
PRIMARY KEY (accountId)
);
And if you want to select a specific status
SELECT *
FROM `Account`
WHERE FIND_IN_SET( 'Solution2', `status` ) >0

Database Design for a status update system involving companies and users

I am having an issue that involves a differentiation between whether a status update belongs to a user or a company. Let me explain: Individuals can post statuses as a user or as a company. A user can be a member/owner of a company and switch to a company in the dashboard much like Facebook. The problem is: how will we set in the database whether a status was posted by a company or by a user?
My solution was to have a company column that was a Boolean variable and when we query for each status to display we check if it was from a company. If so then we grab the company_id and look up the name and other relevant information in the database and display it on the site. Does this sound like the right approach? Additionally, does the schema below look correct?
**Company**
id int(11)
name varchar (255)
**Company_members**
company_id int(11) FK_Company
user_id int(11) FK_Users
owner BOOLEAN
**Users**
id int(11)
name varchar (255)
**Status**
id
date DATETIME
user_id FK_Users
company_id FK_Company
company BOOLEAN
Your schema looks good, but the company boolean in the Status table seems redundant. You can just set the company_id to NULL when the status is for an individual's account, and then query SELECT * from Status WHERE company_id IS NULL to get individuals' statuses, and SELECT * FROM Status WHERE company_id IS NOT NULL to get companies' statuses.
Use the Party Model. You need to use table inheritance. Single Table inheritance is simpler and faster but may use nulls.
create table party_type (
id int primary key,
description text not null unique
);
insert into party_type values
(1, 'Individual'),
(2, 'Organization');
create table party (
id serial primary key,
type int not null references party_type(id),
name text not null
);
create table status_update(
id serial primary key,
date datetime,
party_id not null references party(id)
);
(syntax is postgres but you should be able to translate to mysql easily)

Using UNIQUE indices with NULL fields in MySQL?

I can check, periodically, for a list of users that are currently online. I want to turn this into something useful like a list of entries per user with login / logout time. There is no other way to determine this information apart from checking who is currently online.
After some thinking I came up with something like this:
CREATE TABLE onlineActivity (
id INT UNSIGNED NOT NULL AUTO_INCREMENT,
name CHAR (32) NOT NULL,
login_time DATETIME NOT NULL,
logout_time DATETIME NOT NULL,
time SMALLINT (3) NOT NULL DEFAULT 0,
online BOOL DEFAULT NULL,
UNIQUE (name, online),
PRIMARY KEY (id)
) ENGINE = MyISAM;
I run this query every few minutes to add/update names in the activity list:
INSERT INTO onlineActivity (name, login_time, logout_time, online)
SELECT name, now(), now(), true FROM onlineList ON DUPLICATE KEY UPDATE logout_time = now()
And this query is run for every user that has logged out:
(the names are determined by comparing two adjacent online lists, the current one and the previous one)
UPDATE onlineActivity SET online = NULL WHERE name = ? AND online = 1
The questions:
I'm worrying that using a NULL field (online) in a UNIQUE index is a bad idea, and will hurt performance. I figure that MySQL might have to do a full scan of all the online's (instead of using an index) for each name to find one that is not NULL. Could someone clarify if that is the case here? I couldn't find any information on how MySQL deals with this sort of situation.
Do other database systems (PostgreSQL, SQLite) behave differently then MySQL in this regard?
should I instead of the first query, run two queries for each name, to see if a specified user is currently online, and act accordingly on that?
I thought of this design because I wanted to minimize the amount of queries used, is this a flawed idea in itself?
This table will be getting around 300~500k new records per day. Is there something else I can do to lessen the performance decrease?
I want to store a full history of user activity, not a single entry.
I am not sure why you have a unique on name and online since what you are trying to do is create a list of online activity. Putting a unique key as you have specified will mean that you can only have a name in there three times, one for each state (null, true, false).
What you are effectively doing is trying to create a history table in which case to use your current method of populating the table you should put a unique key on (name, logout_time) with a null logout_time indicating a currently logged in user (since you would only want one logout time that is null).
Something like this:
CREATE TABLE onlineActivity (
id INT UNSIGNED NOT NULL AUTO_INCREMENT,
name CHAR (32) NOT NULL,
login_time DATETIME NOT NULL,
logout_time DATETIME NULL,
time SMALLINT (3) NOT NULL DEFAULT 0,
online BOOL not null DEFAULT false,
UNIQUE (name, logout_time),
PRIMARY KEY (id)
) ENGINE = MyISAM;
Then run this on a schedule to update the table
INSERT IGNORE INTO onlineActivity (name, login_time, logout_time, online)
SELECT name, now(), null, true FROM onlineList
And this on user logout
UPDATE onlineActivity SET online = false, logout_time = now() WHERE name = ? AND logout_time = null