I have a table with random names (along with an id as primary key):
CREATE TABLE `people` (
`id` int(4) NOT NULL AUTO_INCREMENT,
`name` varchar(30) NOT NULL,
PRIMARY KEY (`id`)
);
I have inserted in it 100 random names along with their ids. I also have another table with other names:
CREATE TABLE `names` (
`id` int(4) NOT NULL AUTO_INCREMENT,
`name` varchar(30) NOT NULL,
PRIMARY KEY (`id`)
);
This table also has 100 (different) random names along with their ids. I want to update the column name of table people with the names from the column name of table names.
I obviously have to use UPDATE and SET but in most ways I saw that people are also using INNER JOIN. Personally, I am wondering if there is simpler way to do this (without using INNER JOIN) and I am missing it?
the update with inner join as eg:
update people
inner join names on people.id = names.id
set people.name = names.name
Is the simplest and also the more clear, compact and expressive.
others methods need normally subselect or implicit join based on where condition. In one case there query is more verbouse and in the second the query is more confused and often less performant.
Related
We have a table with orders of customers like:
CREATE TABLE `orders` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`number` varchar(20) NOT NULL,
`ordered` datetime DEFAULT NULL,
`email` varchar(255) NOT NULL,
...
);
The table is already filled with data. I need to add a field:
`user` int(10) unsigned NOT NULL,
which contains a unique number for each customer. A customer is defined by the same email-address, so all orders with the email 'test#example.com' should get a 1, with 'something_else#example.com' should get a 2 and so on.
For this 'user'-number it doesn't matter if it starts with 1 or is somehow incrementing, it just should be different for every email-address.
Is there a way to do this in one SQL-statement? I know how to do it with some php-code for example, but we where curious if it's possible just with SQL. We know it would be a better design if there was a table "customer", but it's not our design, we just trying to fix the worst things ;)
It's not possible to do what you describe in one SQL statement.
Even if you didn't care to make the user id unique per email, your ALTER TABLE wouldn't work. You show adding a column that is NOT NULL but has no DEFAULT. So what value is it supposed to add to the table, given that the table has rows in it? It can't use NULL, and it has no DEFAULT value. You can't add the column as an AUTO_INCREMENT because you already have an id column that is AUTO_INCREMENT, and MySQL doesn't allow a table to have two such columns.
Here's the way I'd do it:
CREATE TABLE customers (
id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
email VARCHAR(255) NOT NULL
);
INSERT INTO customers (email)
SELECT DISTINCT email FROM orders;
ALTER TABLE orders ADD COLUMN user_id INT UNSIGNED; -- this allows NULLs until we fill it
UPDATE orders JOIN customers USING (email)
SET orders.user_id = customers.id;
Before the next step, make sure that it has populated orders.user_id the way you think is correct. Once you drop the orders.email column, there's no undo. It would be a good idea to make a backup first.
ALTER TABLE orders DROP COLUMN email,
MODIFY COLUMN user_ID INT UNSIGNED NOT NULL; -- disallow NULLs from now on
I have two tables with the following structure and example content. Table one has the membership_no set to the correct values, but table two has some incorrect values in the membership_no column. I am needing to query both tables and check to see when the membership_no values are not equal, then update table two's membership_no column with the value from table one.
Table One:
id membership_no
====================
800960 800960
800965 800965
Table Two:
id membership_no
====================
800960 800970
800965 800975
Update query so far. It is not catching all of the incorrect values from table two.
UPDATE
tabletwo
INNER JOIN
tableone ON tabletwo.id = tableone.id
SET
tabletwo.membership_no = tableone.membership_no;
EDIT: Including SHOW CREATE and SELECT queries for unmatched membership_no column values.
Table One SHOW:
CREATE TABLE `n2z7m3_kiduka_accounts_j15` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`user_id` int(11) NOT NULL,
`membership_no` int(11) NOT NULL,
...
`membershipyear` varchar(100) NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `user_id` (`user_id`)
) ENGINE=InnoDB AUTO_INCREMENT=800987 DEFAULT CHARSET=utf8
Table Two SHOW:
CREATE TABLE `n2z7m3_kiduka_accounts` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`user_id` int(11) NOT NULL,
`membership_no` int(11) NOT NULL,
...
`membershipyear` varchar(100) NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `user_id` (`user_id`)
) ENGINE=InnoDB AUTO_INCREMENT=801072 DEFAULT CHARSET=utf8
SELECT query for unmatched membership_no column values:
SELECT
u.name,
a.membership_no as 'Joomla 1.5 accounts table',
j.membership_no as 'Joomla 3.0 accounts table'
FROM
n2z7m3_kiduka_accounts_j15 AS a
INNER JOIN n2z7m3_users AS u ON a.user_id = u.id
INNER JOIN n2z7m3_kiduka_accounts AS j ON a.user_id = j.membership_no
and a.membership_no != j.membership_no
ORDER BY u.name;
While Tim's Answer is perfectly valid, another variation is to add the filter qualifier to the ON clause such that:
UPDATE tabletwo
INNER JOIN
tableone ON tabletwo.id = tableone.id AND tabletwo.membership_no <> tableone.membership_no
SET
tabletwo.membership_no = tableone.membership_no;
This means that you don't have the WHERE filter so it will process all rows, but will act on only those with differing membership_no values. Because it is an INNER JOIN the results will be both tables or no tables (Skipped/NULL result).
EDIT:
If you suspect you have a problem still, what does the MySQL command respond, do you have a specific error notice? With 80k columns, it may take a while for the comand to actually process , so are you giving the command time to complete or is PHP or the system causing the command to abort due to execution time expiry? (Update your execution time on PHP and MySQL and rerun query just to see if that causes it to complete successfully?)
Suggestion
As another sggestion I think your UNIQUE KEY should also be your AI key so for both tables:
DROP INDEX `user_id` ON <table> #removes the current unique index.
then
CREATE UNIQUE INDEX `id` ON <table> #addes unique index to the A_I column.
You just need to add a WHERE clause:
UPDATE
tabletwo
INNER JOIN
tableone
ON tabletwo.id = tableone.id
SET
tabletwo.membership_no = tableone.membership_no
WHERE tabletwo.membership_no <> tableone.membership_no
There are some similar questions, but none of them matches my case.
SQL Optimization - Join different tables based on column value
How to JOIN on different tables based on column value
MySQL query to JOIN tables based on column values
MySQL: Use CASE/ELSE value as join parameter
MySQL query where JOIN depends on CASE
https://dba.stackexchange.com/questions/53301/mysql-getting-result-using-3-tables-and-case-statements
I have notifications table with this structure
CREATE TABLE `notifications` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`notificaiton_type_id` int(11) DEFAULT NULL,
`table1_id` int(11) DEFAULT NULL,
`table2_id` int(11) DEFAULT NULL,
`table3_id` int(11) DEFAULT NULL,
`table4_id` int(11) DEFAULT NULL,
`table5_id` int(11) DEFAULT NULL,
`user_id` int(11) DEFAULT NULL,
`created` datetime DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `userIdIndex` (`user_id`)
) ENGINE=InnoDB AUTO_INCREMENT=17 DEFAULT CHARSET=utf8 COLLATE=utf8_bin;
and 5 tables, from table1 to table5, with these structure (others are the same: I set this for testing, not sure if it matters, but those tables (1 to 5) in addition to posted fields have other fields as well, just they do not participate in the query, so for simplicity I just skipped them)
CREATE TABLE `table1` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`name` varchar(300) COLLATE utf8_bin DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=34 DEFAULT CHARSET=utf8 COLLATE=utf8_bin;
table*_id is foreign key for tables: table1 - table5 with one to many relationship.
I should select notifications based on user_id. Based on notification type, appropriate table*_id has some value, other foreign_keys are null(btw there are notification types that 2 or even 3 table *_id's can be different from null ). The initial thought was to have a query that would join only those tables, if the foreign key has some value different from null via using CASE, WHEN, but as I learnt from the answer of this question,
MySQL query where JOIN depends on CASE
it can not be used in this case.
Tables table1-table5 are going to be relatively big, having kinda millions or dozens of millions records. So I would not prefer to join extra 2-4 tables if foreign keys are null. Also, I do not think it is any better to separate the query into 2 main parts, like - first getting the notifications and then in a loop find associated tables' values.
So, the point is to only join those tables that table*_id is not null if it can be done in mysql.
The main question is what would be the most efficient way to achieve this - get notification info with its related tables data.
general query with joins to all tables is a usual left join, smth like this
EXPLAIN SELECT
n.`id`,
n.`user_id`,
n.`table1_id`,
n.`table2_id`,
n.`table3_id`,
n.`table4_id`,
n.`table5_id`
// other fields
FROM
notifications AS n
LEFT JOIN table1 AS t1
ON t1.`id` = n.`table1_id`
LEFT JOIN table2 AS t2
ON t2.`id` = n.`table2_id`
LEFT JOIN table3 AS t3
ON t3.`id` = n.`table3_id`
LEFT JOIN table4 AS t4
ON t4.`id` = n.`table4_id`
LEFT JOIN table5 AS t5
ON t5.`id` = n.`table5_id`
WHERE user_id = 5
here is sql fiddle with data
http://sqlfiddle.com/#!2/3bf8f/1/0
Thanks
I think you are worrying over nothing. MySQL will handle your query, as it is, without any more effort from you.
You state:
I would not prefer to join extra 2-4 tables if foreign keys are null.
Good news: MySQL won't.
It will see that the key is null in the notifications table, see that there are no records in the corresponding table you are joining to, and then just move on. I'm not even sure what you imagine it may be trying to do that you are trying to optimize away, but your query is already optimized as it is.
If you are already running this query and have performance problems, you issue is likely elsewhere. Please provide more information in that case. In particular, your // other fields line may actually affect things more than you think, depending on where those other fields are located.
Would it not make more sense to use a single ID as the foreign key then a column for which table to query:
CREATE TABLE `notifications` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`notification_type_id` int(11) DEFAULT NULL,
`table_id` int(11) DEFAULT NULL,
`table_name` VARCHAR(10) DEFAULT NULL
...
Then you can select which table to query for the actual data you need.
SELECT `table_id`,`table_name` FROM `notifications`;
SELECT * FROM #table_name WHERE `id`=#table_id;
No expensive LEFT JOINs are necessary in this scenario and two queries (or a compound query as a stored procedure) would negate the need for a large index on the foreign key and so simplify the construct. It also has the advantage of being scalable, for example what if you needed a 6th, 7th or 100th partition table?
Why not use VIEW for this Left join query?
Here's something more about View's performance: Is a view faster than a simple query?
Assuming that your query works fine, you could create view from it:
CREATE VIEW view_myView AS
SELECT
n.`id`,
n.`user_id`,
n.`table1_id`,
n.`table2_id`,
n.`table3_id`,
n.`table4_id`,
n.`table5_id`
FROM
notifications AS n
LEFT JOIN table1 AS t1
ON t1.`id` = n.`table1_id`
LEFT JOIN table2 AS t2
ON t2.`id` = n.`table2_id`
LEFT JOIN table3 AS t3
ON t3.`id` = n.`table3_id`
LEFT JOIN table4 AS t4
ON t4.`id` = n.`table4_id`
LEFT JOIN table5 AS t5
ON t5.`id` = n.`table5_id`
WHERE user_id = 5
Then you access the data from this view simply by:
SELECT * FROM view_myView;
and it should be faster than calling the query everytime.
It's also much shorter to write as you see.
This is second time that i can face this kind of issue to retrieve the data.
CREATE TABLE `pm_projects` (
`project_id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`project_name` varchar(255) NOT NULL,
`assigned_client` varchar(100) NOT NULL,
`project_detail` longtext NOT NULL,
`creation_date` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
`project_status` tinyint(1) NOT NULL,
PRIMARY KEY (`project_id`),
KEY `assigned_client` (`assigned_client`)
) ENGINE=MyISAM AUTO_INCREMENT=4 DEFAULT CHARSET=utf8
On the above table i have a field assigned_client that holds the multiple id's of the clients that are assigned to the project with comma separated(3,4,...).
And i am trying to fetch the result on this table with the Assigned Client's Name(that is on my pm_users table) with JOIN, I tried the following:
SELECT
p.project_id, u.user_name, p.project_name,
p.creation_date, p.project_status
FROM pm_projects p
LEFT JOIN pm_users u ON u.user_id
IN (
'p.assigned_clients'
)
that returns the NULL value of u.user_name field.
Can i have to change my schema, if yes then how?
OR i am trying with wrong Query?
You can use find_in_set for this:
on find_in_set(u.user_id, p.assigned_clients) > 0;
Note that there are no single quotes around p.assigned_clients. This is another error in your query (but even if you replaced it with back quotes, the query still wouldn't work).
However, the problem is your table schema. You should have a separate association table, with one row per user and assigned client.
Trying to store this all in one field will only lead to problems, overly-complicated queries, and performance problems in the future.
I would go with a many to many link approach.
Something like
CREATE TABLE pm_project_client_link(
project_id INT,
client_id INT
)
That would allow you to write the query something like
SELECT
p.project_id,
u.user_name,
p.project_name,
p.creation_date,
p.project_status
FROM pm_projects p INNER JOIN
pm_project_client_link pcl ON p.project_id = pcl.project_id INNER JOIN
pm_users u ON pcl.client_id = user_id
MySQL question.
I have a contract and orders file. A standard parent and child - one to many relationship.
A contract can have many orders. The common join field is c_contract_id = co_contract_id.
A contract has a c_type_code that qualified the contract and the orders also have a co_order_type_code.
I am building a general search that allows the user to select based on many fields.
The query that is giving me trouble is finding the contract_ids of those contracts that have a specific type_code in either the contract or contract_orders tables.
There are many fields int he tables that the user can search on. The relevant fields for my question are:
CREATE TABLE `ndx_contracts` (
`c_contract_id` int(11) NOT NULL AUTO_INCREMENT,
`c_type_code` char(2) NOT NULL,
PRIMARY KEY (`c_contract_id`),
KEY `c_type_code` (`c_type_code`)
) ENGINE=InnoDB AUTO_INCREMENT=1
CREATE TABLE `ndx_contract_orders` (
`co_contract_id` int(11) NOT NULL,
`co_contract_orderid` int(11) NOT NULL AUTO_INCREMENT,
`co_order_type_code` varchar(2) NOT NULL,
`co_data` varchar(255),
PRIMARY KEY (`co_contract_orderid`,`co_contractid`),
KEY `co_order_type_code` (`co_order_type_code`)
) ENGINE=InnoDB AUTO_INCREMENT=1
SELECT count(1)
FROM contracts
WHERE
c_type_code IN ('02')
OR (
(SELECT count(co_order_type_code) FROM contract_orders
WHERE
co_contract_id = c_contract_id
AND co_order_type_code IN ('02')
) > 0
)
;
This query works but it is terribly slow. I have only about 40,000 contracts and each has about 4 order records and the search takes over 311 seconds to return 320 seconds rows.
I would do an outer join, but that would return many extra rows.
thank you
It sounds like you want to count the distinct contract id, so this should do it for you.
SELECT COUNT(DISTINCT c.c_contract_id) AS `contract_id_count`
FROM ndx_contracts AS c
LEFT OUTER JOIN ndx_contract_orders AS co
ON c.c_contract_id = co.co_contract_id
WHERE c.c_type_code = ? OR co.co_order_type_code = ?
Note this will allow for cases with contracts with no orders. The ? would obviously be substituted with whatever your search value is. If you want the actual order id's themselves, just remove the COUNT() function in the select.
Your primary key on ndx_contract_orders is actually a problem though. To do this join, where you only lookup by the contract_id, you would need the field order of that primary index to be reversed in able to enable indexed lookups by contract_id only. Alternatively, you could just add an additional index for co_contract_id. Honestly though if order id is an autoincrement, that field alone should probably be your primary key and the co_contract_id field should just have it's own index. There would be no need to force a unique index across both fields, as the primary key autoincrement on order_id would ensure uniqueness.