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?
Related
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
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
I am confused about the correct/most efficient way to place data in my dababase table when there is a OneToOne relationship.
For example, I have a users table.
I now wish for each user to be able to state his current country location.
i then want to be able to search the datatable for users by current location.
The way that I have done this is to create 3 separate tables. i.e
table one - users : just contains the user information:
CREATE TABLE users(
id MEDIUMINT UNSIGNED NOT NULL AUTO_INCREMENT,
firstName VARCHAR(30) NOT NULL,
lastName VARCHAR(40) NOT NULL,
);
Table two country list: a list of countries and respective Ids for each country
PHP Code:
CREATE TABLE countrylist(
country_id MEDIUMINT UNSIGNED NOT NULL,
country VARCHAR(60) NOT NULL,
INDEX country_id ( country_id, country ),
INDEX countrylist (country, country_id ),
UNIQUE KEY (country)
);
Table 3; contains the userId and the countryId he lives in:
PHP Code:
CREATE TABLE user_countrylocation(
country_id VARCHAR(60) NOT NULL,
id MEDIUMINT UNSIGNED NOT NULL,
INDEX country_id (country_id, id ),
INDEX user_id (id, country_id )
);
Alternatively, should I place the countryId in the users table and completely get rid of the user_countrylocation. i.e in each user column, I will place a country_id for the country he lives in.
The problem is that I have over 20 similar tables as above that give details on users; i.e languages spoken, age-group, nationality etc.
My concerns is that if I place this unique information in each users column in the user table, then what would be the most efficient way to search the database: that is why I opted for the style above.
So, I really request for some advice on the most efficient/correct way to plan the database.
If you are going to have a huge data then you should keep the same approach and use the following method to keep the one to one constraint satisfied
if you don't have a huge data then you should keep the look up tables like country and use the reference for user in a column. but then you may need to allow them nulls that is make such optional information columns nullable.
The most efficient and exactly correct way is to first delete the data from the third table "user_countrylocation" for the user to be updated. Then insert the new location for the user. don't forget to use transaction.
your table 3 should have
country_id MEDIUMINT UNSIGNED NOT NULL,
instead of
country_id VARCHAR(60) NOT NULL,
and also change tyhe column name from id to user_id in all tables.
if you are using a stored procedure it would be like
create procedure sp_UpdateUserCurrentCountry (
#userID MEDIUMINT UNSIGNED,
#CountryID MEDIUMINT UNSIGNED)
begin
as
delete from user_countrylocation
where user_id = #userID
insert into user_countrylocation
(
country_id,
user_id
)
values
(
#CountryID,
#userID
)
END
One to One relations are usually mapped via Foreign Keys linking the two tables together. A third mapping table is only required for Many to Many relationships. So, you should ideally have a Foreign Key Country_ID in your Users table.
Your SELECT query would then look like
SELECT * FROM Users
WHERE Country_ID = (
SELECT Country_ID FROM Countries
WHERE Country_Name = 'USA'
);
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
I'm working on an application which tracks prices for certain items.
Each price has a reference to an item, a business that sells that item, and the location the item is being sold at. Now, normally, this would do just fine:
CREATE TABLE `price` (
`priceId` INT UNSIGNED NOT NULL AUTO_INCREMENT, -- PK
`businessId` INT UNSIGNED NOT NULL,
`itemId` INT UNSIGNED NOT NULL,
`locationId` INT UNSIGNED NOT NULL,
`figure` DECIMAL(19,2) UNSIGNED NOT NULL,
-- ...
)
But I have the following problem:
The application logic is such that one item at one business at one location can have multiple prices (at this point it's not really important why), and one of those prices can be an official price - an item doesn't have to have an official price, but if it does, there can be only one.
The question is; how to model this to ensure data integrity?
My initial idea was to create an additional table:
CREATE TABLE `official_price` (
`priceId` INT UNSIGNED NOT NULL -- PK + FK (references price.priceId),
-- ...
)
This table would hold priceId:s for prices that are official, and the PK/UNIQUE constraint would take care of the 'one-or-none' constraint.
This seems like a workable solution, but I'm still wondering if there's a better way to handle this situation?
You can use this dirty hack:
add a field is_official to price table, null as a value is possible in it
create an unique composite index priceId + is_official
for the official prices put 1 to is_official
for not official left it to be null
You could make the price table hold only official prices (with the figure possibly null), put a unique constraint on (businessId, itemId, locationId), and add another table of auxiliary prices referencing priceId.