Here is my goods table.
+----------------------+---------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+----------------------+---------------+------+-----+---------+-------+
| ID | decimal(18,0) | NO | PRI | | |
| gkey | varchar(255) | YES | MUL | NULL | |
| GOODS | decimal(18,0) | YES | MUL | NULL | |
Column ID is auto-increment.
GOODS is the id of a goods category.
Here is the goods category table.
+-------+---------------------+
| ID | NAME |
+-------+---------------------+
| 1 | book |
| 2 | phone |
+-------+---------------------+
My question is in goods table I need the gkey is also an unique key with prefix-id(here id is started from 1.) Like BOOK-1,BOOK-2....PHONE-1,PHONE-2... when insert a new goods record into goods table.
Like that:
+--------------------+---------------+------+-----+---------+-------+
| ID | GKEY |GOODS | PRI | COUNTRY | Extra |
+--------------------+---------------+------+-----+---------+-------+
| 1 | BOOK-1 | 1 | 10 | | |
| 2 | PHONE-1 | 2 | 12 | | |
| 3 | BOOK-2 | 1 | 13 | | |
| 4 | BOOK-3 | 1 | 10 | | |
| 5 | PHONE-2 | 2 | 10 | | |
| 6 | PHONE-3 | 2 | 20 | | |
+--------------------+---------------+------+-----+---------+-------+
How to get that GKEY in PHP+MYSQL?
thanks.
You can use the UNIQUE constraint. For more information check here.
I don't think you want to be doing this at all. Instead, good.gkey should be a foreign key to goods_category.id. If the insertion order is important, you can add an insert_date date field to the goods table.
From there you can run all sorts of queries, with the added bonus of having referential integrity on your tables.
First you should do everything Database side. So no php. That would be my advice.
Then not having sequence in MySQL that what I would Suggest you :
SELECT COUNT(id)
FROM GOODS
WHERE GKEY LIKE('BOOK-%')
Then you insert
INSERT INTO goods (id, "BOOK-" || SELECT MAX(SUBSTR(LENGTH(GKEY -1), LENGTH(GKEY))FROM GOODS WHERE GKEY LIKE('BOOK-%') + 1, ...)
This way you will always have the next number available.
You can do it, but you need to be quite careful to make sure that two simultaneous inserts don't get given the same gkey. I'd design it with these two design points:
In the GoodsCategory table, have a counter which helps to generate the next gkey.
Use locks so that only one process can generate a new key at any one time.
Here's the details:
1. Add a couple of columns to the GoodsCategory table:
+----------------------+---------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+----------------------+---------------+------+-----+---------+-------+
| ID | TINYINT | NO | PRI | | |
| NAME | VARCHAR(80) | NO | | | |
| KeyCode | CHAR(5) | NO | | | |
| NextID | INT | NO | | 0 | |
+----------------------+---------------+------+-----+---------+-------+
The KeyCode has 'BOOK' or 'PHONE', and the NextID field stores an int which is used to generate the next key of that type i.e. if the table looks like this:
+----------------+------------+---------+--------+
| ID | NAME | KeyCode | NextID |
+----------------+------------+---------+--------+
| 1 | book | BOOK | 3 |
| 2 | phone | PHONE | 7 |
+----------------+------------+---------+--------+
Then the next time you add a book, it should be given gkey 'BOOK-3', and NextID is incremented to 4.
2: The locking will need to be done in a stored routine, because it involves multiple statements in a transaction and uses local variables too. The core of it should look something like this:
START TRANSACTION;
SELECT KeyCode, NextID INTO v_kc, v_int FROM GoodsCategory WHERE ID = 2 FOR UPDATE;
SET v_newgkey = v_kc + '-' + CAST(v_int AS CHAR);
INSERT INTO goods (gkey, goods, ...) VALUES (v_newgkey, 2, etc);
UPDATE GoodsCategory SET NextID = NextID + 1 WHERE ID = 2;
COMMIT;
The FOR UPDATE bit is crucial; have a look at the Usage Examples in the mysql manual where it discusses how to use locks to generate an ID without interference from another process.
Related
I have two similar data sets (table, view, CTE), one of which contains unique rows (guaranteed by DISTINCT or GROUP BY), the second contains duplicates (no primary key constraint involved).
How can I get the difference of two data sets so that I only get the duplicates of the second set in MySql 8?
Say I have a table called Animals, which stores NAME and SPECIES.
+---------+--------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+---------+--------------+------+-----+---------+-------+
| ID | int(11) | NO | PRI | NULL | |
| NAME | varchar(255) | YES | | NULL | |
| SPECIES | varchar(255) | YES | | NULL | |
+---------+--------------+------+-----+---------+-------+
ANIMALS
+----+---------+-------------+
| ID | NAME | SPECIES |
+----+---------+-------------+
| 1 | Lion | Carnivorous |
| 2 | Giraffe | Herbivores |
| 3 | Zebra | Herbivores |
| 4 | Trutle | Herbivores |
| 5 | Tiger | Carnivorous |
| 6 | Bear | Carnivorous |
+----+---------+-------------+
With that in place, I define the view DUPLICATED.
CREATE VIEW DUPLICATED AS
SELECT * FROM ANIMALS
UNION ALL
SELECT * FROM ANIMALS WHERE SPECIES = "Carnivorous";
(Duplicates every Carnivorous in the set)
DUPLICATED
+---------+-------------+-----+
| NAME | SPECIES | CNT |
+---------+-------------+-----+
| Lion | Carnivorous | 2 |
| Tiger | Carnivorous | 2 |
| Bear | Carnivorous | 2 |
| Giraffe | Herbivores | 1 |
| Zebra | Herbivores | 1 |
| Trutle | Herbivores | 1 |
+---------+-------------+-----+
Now I want to get the difference of SELECT * FROM ANIMALS and DUPLICATED or vice versa, essential getting all Carnivorous from ANIMALS.
Basically you can group by whatever combination of fields that guarantee the uniqueness of a record in your result. you haven't provided your queries or your table's schema, so i will try to demonstrate this using a general example. you can get my drift and apply it to your query.
SELECT field1, field2, field3 COUNT(*)
FROM MyTable
GROUP BY field1, field2, field3
HAVING COUNT(*) > 1
At first, I want to apologize for providing such a weak title; I couldn't describe it in a better way.
Consider the following: We have three tables, one for users, one for records and one for ratings. The tables are quite self-explanatory but the schema for database is as following:
+---------------------+
| Tables_in_relations |
+---------------------+
| records |
| ratings |
| users |
+---------------------+
The schema for records table is as following:
+----------+----------------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+----------+----------------------+------+-----+---------+----------------+
| id | smallint(5) unsigned | NO | PRI | NULL | auto_increment |
| title | varchar(256) | NO | | NULL | |
| year | int(4) | NO | | NULL | |
+----------+----------------------+------+-----+---------+----------------+
The schema for users table is as following:
+----------+----------------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+----------+----------------------+------+-----+---------+----------------+
| id | smallint(5) unsigned | NO | PRI | NULL | auto_increment |
| email | varchar(256) | NO | | NULL | |
| name | varchar(256) | NO | | NULL | |
| password | varchar(256) | NO | | NULL | |
+----------+----------------------+------+-----+---------+----------------+
ratings table is, obvoiusly, where the ratings are stored among with the record_id and user_id and works as a relation table.
It's schema is as following:
+----------+----------------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+----------+----------------------+------+-----+---------+----------------+
| id | smallint(5) unsigned | NO | PRI | NULL | auto_increment |
| record_id| smallint(5) unsigned | NO | MUL | NULL | |
| user_id | smallint(5) unsigned | NO | MUL | NULL | |
| rating | int(1) | NO | | NULL | |
+----------+----------------------+------+-----+---------+----------------+
Now, In my application, I have a search function that fetches records based on a certain keyword. The output should also include the average rating of a certain record and a total amount of ratings per record. This can be accomplished by following query:
SELECT re.id, re.title, re.year, ROUND(avg(ra.rating)) as avg_rate,
COUNT(ra.record_id) as total_times_rated
FROM records re
LEFT JOIN ratings ra ON ra.record_id = re.id
GROUP BY re.id;
which will give me the following output:
+----+------------------------+------+----------+-------------------+
| id | title | year | avg_rate | total_times_rated |
+----+------------------------+------+----------+-------------------+
| 1 | Test Record 1 | 2008 | 3 | 4 |
| 2 | Test Record 2 | 2012 | 2 | 4 |
| 3 | Test Record 3 | 2003 | 3 | 4 |
| 4 | Test Record 4 | 2012 | 3 | 3 |
| 5 | Test Record 5 | 2003 | 2 | 3 |
| 6 | Test Record 6 | 2006 | 2 | 3 |
+----+------------------------+------+----------+-------------------+
Question:
Now, here comes the tricky part, at least for me. Within my app, you can search records whether signed in or not and if signed in, I'd also like to include the user's own rating value in the above query.
I know that I can run a conditional to check whether user is signed in or not by reading the session value and execute a corresponding query based on that. I just don't know how to include that individual rating value of a certain user to the above query.
You can add user's rating in the result by adding a SELECT query in columns:
SELECT re.id, re.title, re.year, ROUND(avg(ra.rating)) as avg_rate,
COUNT(ra.record_id) as total_times_rated,
(SELECT rating FROM ratings WHERE user_id = ? AND record_id = re.id) as user_rating
FROM records re
LEFT JOIN ratings ra ON ra.record_id = re.id
GROUP BY re.id;
We can get the user_id from session and pass it to this query in order to generate user_rating column in the result.
Assuming user can rate a record multiple times, I have used SUM. If not, we can remove it from the query.
Update
If you don't want GROUP BY to consider that value then you can wrap the existing query into another query and add a column to it, e.g.:
SELECT a.id, a.title, a.year, a.avg_rate, a.total_times_rated,
(SELECT rating FROM ratings WHERE user_id = ? AND record_id = a.id) as user_rating
FROM (SELECT re.id as id, re.title as title, re.year as year, ROUND(avg(ra.rating)) as avg_rate,
COUNT(ra.record_id) as total_times_rated
FROM records re
LEFT JOIN ratings ra ON ra.record_id = re.id
GROUP BY re.id) a;
I have a db with 10 fields, of which only a few change very often. I would like to open the current record, update it, and enter it back into the db with a new auto id while retaining the previous record.
If I INSERT a new record, I have to re-enter all the info even tho it may not have changed.
If I UPDATE a record, it overwrites the previous values.
Any suggestion would be greatly appreciated
How about a suitably modified version of
insert into table select * from table where id='current_id'
Sample query:Say i want to read first record ,lift student, subject but change marks and insert a new record
insert into score(student,subject,marks)
select student,subject,'30' from score where id=1;
Sample Data
+---------+---------+-------+----+
| student | subject | marks | id |
+---------+---------+-------+----+
| A1 | phy | 20 | 1 |
| A1 | bio | 87 | 2 |
| A2 | che | 24 | 3 |
| A3 | che | 50 | 4 |
Table structure:
+---------+-------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+---------+-------------+------+-----+---------+----------------+
| student | varchar(20) | YES | | NULL | |
| subject | varchar(20) | YES | | NULL | |
| marks | int(1) | YES | | NULL | |
| id | int(10) | NO | PRI | NULL | auto_increment |
+---------+-------------+------+-----+---------+----------------+
You can do it via two different queries: SELECT and INSERT, or you can use INSERT INTO SELECT construction. Check documentation for complete reference http://dev.mysql.com/doc/refman/5.0/en/insert-select.html
I'm facing a problem of SELECT perfomance issue with MYSQL.
I have two tables "domain" and "email" which contain duplicates, theses tables are frequently updated (INSERT/DELETE) by different sources (every ten mins approximatively).
My primary objective was to make two views from thoses tables without any duplicates. I know a view is a stored query but this is my only way to keep it dynamic, creating a new table without duplicate every tens mins would be mad (maybe not?).
Both views are used by another thread (postfix) to check if the recipient is an allowed one. When i try to do a simple query
SELECT email FROM emailview WHERE email = 'john#google.com'`
the query takes 3-4seconds. On the contrary if I do my SELECT directly on the email table (with duplicates in) it takes 0,01sec.
How could i improve the SELECT performances on my system to obtain almost similar result with a view and not directly on the table ?
Here is the detail of the architecture (INNODB Engine, value 1 is random and doesn't really matter) :
Domain Table :
| field | type | null | key |
|--------------|--------------|------|------|
| domain | varchar(255) | NO | NULL |
| creationdate | datetime | NO | NULL |
| value 1 | varchar(255) | NO | NULL |
| source_fkey | varchar(255) | MUL | NULL |
| domain | creationdate | value 1 | source_fkey |
|------------|---------------------|-----------------------|
| google.com | 2013-05-28 15:35:01 | john | Y |
| google.com | 2013-04-30 12:10:10 | patrick | X |
| yahoo.com | 2011-04-02 13:10:10 | britney | Z |
| ebay.com | 2012-02-12 10:48:10 | harry | Y |
| ebay.com | 2013-04-15 07:15:23 | bill | X |
Domain View (duplicate domain are removed using the oldest creation date) :
CREATE VIEW domainview AS
SELECT domain.domain, creationdate, value1, source_fkey
FROM domain
WHERE (domain, creationdate) IN (SELECT domain, MIN(creationdate)
FROM domain GROUP BY domain);
| domain | creationdate | value 1 | source_fkey |
|------------|---------------------|-----------------------|
| google.com | 2013-04-30 12:10:10 | patrick | X |
| yahoo.com | 2011-04-02 13:10:10 | britney | Z |
| ebay.com | 2012-02-12 10:48:10 | harry | Y |
Email table :
| field | type | null | key |
|--------------|--------------|------|------|
| email | varchar(255) | NO | NULL |
| source_fkey | varchar(255) | MUL | NULL |
| email | foreign_key |
|--------------------|-------------|
| john#google.com | X |
| john#google.com | Y | <-- duplicate from wrong foreign/domain
| harry#google.com | X |
| mickael#google.com | X |
| david#ebay.com | Y |
| alice#yahoo.com | Z |
Email View (legit emails and emails from domain/foreign_key of the domain view) :
CREATE VIEW emailview AS
SELECT email.email, email.foreign_key
FROM email, domainview
WHERE email.foreign_key = domainview.foreign_key
AND SUBSTRING_INDEX(email.email,'#',-1) = domainview.domain;
| email | foreign_key |
|--------------------|-------------|
| john#google.com | X |
| harry#google.com | X |
| mickael#google.com | X |
| david#ebay.com | Y |
| alice#yahoo.com | Z |
There is no unique, no indexes, the only primary key is in the table where the foreign_key is.
Thanks for help.
Previous discussion : Select without duplicate from a specific string/key
both queries are slow - first because of the subselect in the IN clause - which is not optimized until MySQL 5.6; the second because uses a function in the where clause.
In the first query you can replace the subselect with a join
In the second, it's best to store the domain in separate column and use it for comparision
Make sure you have composite indexes on the fields used in joins, where and group by clauses
In my app, I allow people to create additional rooms for inventory purposes. Let's say I have a table called "user_data." The d_key field is the data type for a lack of a better description. I have that just in case I introduce more custom fields for the user. Anyways, I want every user to have a room that's called None. This way any item that doesn't have a room assigned to it will default to "None."
What's the best way to introduce None into the table? Users won't be able to delete this default room, but they can edit/delete rooms they input into the DB table.
user_data table
+---------+-------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+---------+-------------+------+-----+---------+----------------+
| id | int(11) | NO | PRI | NULL | auto_increment |
| user_id | int(11) | NO | | NULL | |
| d_key | varchar(20) | NO | | NULL | |
| d_value | varchar(40) | NO | | NULL | |
+---------+-------------+------+-----+---------+----------------+
Sample data:
+----+---------+----------+--------------+
| id | user_id | d_key | d_value |
+----+---------+----------+--------------+
| 20 | 2 | location | bedroom |
| 21 | 2 | location | living room |
| 22 | 2 | location | attic |
| 23 | 3 | location | kitchen |
+----+---------+----------+--------------+
Add the row as part of your user creation process.
For existing users, loop over the users and ensure they have a "none" room; if they don't, insert one.