Copy MySQL table and populate with additional fields - mysql

I want to copy an existing MySQL table over to a duplicate table, but with two additional fields populated by data retrieved from other queries. It's for a forums software that never captured the original creation date of the thread (it always overwrote the time with the most recent reply).
So I want to take the existing table, 'threads'
thread_id
thread_author
thread_subject
thread_last_reply_date
and create a new table, 'new_threads' of the same structure, but with two extra fields:
thread_id
thread_author
thread_subject
thread_last_reply_date
thread_creation_date
thread_last_poster_id
Both thread_last_reply_date and thread_last_poster_id could be populated from dependent queries.. For example, last_poster_id with a query of:
SELECT post_author FROM posts WHERE thread_id = ? AND post_date = thread_last_reply_date
And for the thread_creation_date:
SELECT MIN(post_date) FROM posts WHERE thread_id = ?
That's essentially the process I would do with a PHP script.. A series of SELECTs and then inserting records one by one. Any advice or direction on how to do this type of process in pure SQL would be crazy helpful (if it's possible).
EDIT: an example of the technique would be helpful. I don't need an exact answer for the above. I'm sure everyone has better things to do.

Just had a similar problem.
This solution worked for me:
INSERT INTO `new_thread` SELECT *,null,null FROM `thread` WHERE 1=1

To create your target table (empty):
CREATE TABLE new_threads
SELECT t.*, thread_creation_date date, thread_last_poster_id number
FROM thread where 1=0;
and to populate it:
insert into new_threads(thread_id, thread_author, thread_subject, thread_last_reply_date, thread_creation_date, thread_last_poster_id)
(select t.*,
min(p.post_date),
(SELECT p1.post_author
FROM posts p1
WHERE p1.thread_id = t.thread_id
AND p1.post_date = t.thread_last_reply_date) thread_last_poster_id
from threads t, posts p
where t.thread_id = p.thread_id;
(untested)

Related

SQL locking of table in an inner join

Every day, I run a SQL statement to set an estimate of an aggregated value. This is a mysql server and the code looks like this:
UPDATE users
INNER JOIN (
SELECT user_id, COUNT(*) AS action_count
FROM action_log
GROUP BY user_id
) AS action_log
ON users.id = action_log.user_id
SET users.action_count = action_log.action_count
As my database has grown, this is taking longer to run and it seems to be affecting other queries. As the code stands now, is there a lock held on my action_log table for the entirety of the update?
Since this is an estimate and doesn't need to be totally accurate, I'm considering splitting this up into multiple SQL statements. One that does the select to get the aggregated counts per users. And then do single updates for each user row.
I'm hoping that would help. Running EXPLAIN on this query didn't give me much info and I'm not sure if breaking things up in this fashion will actually help.
Yep, it's probably going to hold onto that lock. If you can separate out the SELECT your problem will go away, something like this:
DECLARE #THETABLE TABLE(
USERID INT,
TOTAL INT
)
INSERT INTO #THETABLE
SELECT user_id, COUNT(*) AS action_count
FROM action_log
GROUP BY user_id
UPDATE users
INNER JOIN (
#THETABLE
) AS action_log
ON users.id = action_log.user_id
SET users.action_count = action_log.action_count
You could also dump the results into a #TEMP_TABLE but I like using a table variable. Another option is to play with locking hints like NOLOCK or READPAST or ROWLOCK but I wouldn't suggest it, that can get you into all kinds of other trouble.

SQL Code to order by data from related table

Okay this SQL query is giving me a headache, hoping theres someone who's done something like this before.
I have two tables (truncated)
tblTickets: tblNotes:
ticketno (int) noteid (int)
firmid (int) ticketno (int)
ticket_desc (text) datecreated (datetime)
... ...
They are related in that a Ticket can have many Notes
What I need to do is create a query that searches by firmid (i.e. 32) and orders the "Tickets" by their latest "Note" using tblNotes.datecreated (ordered newest first)
Thanks!
NB. MySQL server (5.5.32)
EDIT: To those who've marked the question down: I have tried, and the furthest successful SQL I got was to list all tickets and notes joined by using JOIN on ticketno, I didnt add this code to the question because I guessed I was going about it all the wrong way, and maybe I needed to use a UNION, something I've always found tricky to use.
I need it to only search by the latest note for each ticket. Thats what I needed help on.
You need to use a sub-query within the WHERE clause of your SQL to identify the last note date and then join to the SQ to limit the notes that are returned.
The following should be enought to get you started.
SELECT ...
FROM tblTickets T
INNER JOIN
tblNotes N
ON N.ticketno = T.ticketno
INNER JOIN
(SELECT N1.ticketno
,MAX(N1.datecreated) AS last_note_date
FROM tblNotes N1
GROUP BY
N1.ticketno
)SQ
ON N.ticketno = SQ.ticketno
AND N.datecreated = SQ.last_note_date

MySQL query help moving data between tables

I've imported by phpbb3 forum in bbpress using the built-in importer. All of the anonymous users from bbpress who didn't have accounts, but were allowed to post are disconnected from there posts and everything is showing up as anonymous in bbpress. I grabbed all the post_usernames from phpbb_posts and created users with this query:
INSERT INTO wp_users (user_login)
SELECT DISTINCT post_username
FROM phpbb_posts
Now I'm trying to do a query between the 3 different tables. Something along these lines:
SELECT ID FROM wp_users
INSERT INTO wp_posts(post_author)
WHERE wp_posts(post_date) = phpbb_posts(post_time)
AND phpbb_posts(post_username) = wp_users(user_login)
Obviously this isn't right... probably syntax errors, but I also need to add some way of telling MySQL that the user_login has to be attached to the ID from the first line. Hopefully this makes sense. Thanks in advance for any help!
Updated queries:
SELECT ID FROM wp_users
SELECT post_time FROM phpbb_posts = post_date
SELECT post_username FROM phpbb_posts = user_login
hopefully this syntax makes more sense. These did work and they select the right information. The problem is I don't know how to write the WHERE statement properly and like you said baskint, I think I need to make the last statement a sub-query somehow. Thanks again!
I am still not sure what are the PK's (Primary Key) and FK's (Foreign Key) relationships of each table. However, assuming that wp_users is the primary table and phpbb_posts.post_username is the FK of wp_users.user_login...:
SELECT `wp_users`.`ID`
FROM `wp_users` INNER JOIN
(SELECT `phpbb_posts`.`post_username` FROM `phpbb_posts`, `wp_posts` WHERE `phpbb_posts`.`post_time` = `wp_posts`.`post_date` ) AS `posts`
ON `wp_users`.`user_login` = `posts`.`post_username`;
EDIT (Dec-05-2012):
After chatting and going through specific, #sbroways had to change data-types on some fields and a few other modifications. In turn, the final query turned out to be:
SELECT wp_users.*, ws_posts.*
FROM wp_users INNER JOIN ws_posts
ON wp_users.user_login = ws_posts.user_login
you're right. your syntax is confusing and not correct. trying to understand what you are trying to accomplish. in second query, why are you selecting and inserting at the same time? perhaps i am missing something, but can you state what you are trying to pull out from which tables and how you would like to see the results in plain English?
Also you can think in terms of sub-queries (SELECT * FROM b WHERE id IS IN (SELECT Id from a). You can cascade this a few times and perhaps get to your answer.

Get the first and last posts in a thread

I am trying to code a forum website and I want to display a list of threads. Each thread should be accompanied by info about the first post (the "head" of the thread) as well as the last. My current database structure is the following:
threads table:
id - int, PK, not NULL, auto-increment
name - varchar(255)
posts table:
id - int, PK, not NULL, auto-increment
thread_id - FK for threads
The tables have other fields as well, but they are not relevant for the query. I am interested in querying threads and somehow JOINing with posts so that I obtain both the first and last post for each thread in a single query (with no subqueries). So far I am able to do it using multiple queries, and I have defined the first post as being:
SELECT *
FROM threads t
LEFT JOIN posts p ON t.id = p.thread_id
ORDER BY p.id
LIMIT 0, 1
The last post is pretty much the same except for ORDER BY id DESC. Now, I could select multiple threads with their first or last posts, by doing:
SELECT *
FROM threads t
LEFT JOIN posts p ON t.id = p.thread_id
ORDER BY p.id
GROUP BY t.id
But of course I can't get both at once, since I would need to sort both ASC and DESC at the same time.
What is the solution here? Is it even possible to use a single query? Is there any way I could change the structure of my tables to facilitate this? If this is not doable, then what tips could you give me to improve the query performance in this particular situation?
You could do something with a subquery and joins:
SELECT first.text as first_post_text, last.text as last_post_text
FROM
(SELECT MAX(id) as max_id, MIN(id) as min_id FROM posts WHERE thread_id = 1234) as sub
JOIN posts first ON (sub.max_id = first.id)
JOIN posts last ON (sub.min_id = last.id)
But that doesn't solve your problem of doing it without subqueries.
You could add columns to your threads table so that you keep the id of the first and last post of each thread. The first post would never change, but every time you added a new post you would have to update that record in the threads table, so that would double your writes, and you may need to use a transaction to avoid race conditions.
Or you could go so far as to duplicate information about the first and last post in the threads row. Say you needed the user_id of the poster, the timestamp it was posted, and the first 100 characters of the post. You could create 6 new columns in the threads table to contain those pieces of data for the first and last post. It duplicates data, but it means you may be able to display a list of threads without needing to query the posts table at all.

MySql Delete - same like select ( join )

I copied some records from one table to another
with this query :
insert into pages_finished (keyword,pages,resultlist,done,current)
select keyword,pages,resultlist,done,current
from pages_done o
where (select count(*) as totalPages from pages_done x where x.keyword = o.keyword)-1 = pages
Now I want to delete the same records from the source table,
I was thinking it would be simple as:
delete from pages_done o
where (select count(*) as totalPages from pages_done x where x.keyword = o.keyword)-1 = pages
but that doesn't work.
Could anyone tell me what is the right way to do that?
After #bgdrl answer, I'm thinking about running only the select,
get the id's of all records that should be copied,
and then delete;
but I think there must be an easier solution, anyone?
Even though marked #bgdrl answer as the right answer,
it is only because of that a fact.
To anyone interested with what I ended up doing :
I did the same select I started with (but selected only the id column, since selecting all the columns would have killed my poor computer),
exported it to an INSERT STATMENTS (using mysqlworkbench),
opened the text file in notepad,
replaced all the INSERT INTO... with DELETE FROM WHERE ID=,
and run that query in mysql.
I feel so stupid using this way, but had no other choice apparently.
From mysql site: "You cannot modify a table and select from the same table in a subquery. This applies to statements such as DELETE, INSERT, REPLACE, UPDATE". http://dev.mysql.com/doc/refman/5.1/en/subqueries.html
PLEASE BACKUP THE TABLE BEFORE FOLLOWING THE STEPS.
Follow the following STEPS
STEP 1
CREATE TABLE pages_done_ids
SELECT o.id FROM pages_done AS o
WHERE
(
SELECT count(*) AS totalPages
FROM pages_done AS x
WHERE x.keyword = o.keyword
)-1 = o.pages
STEP 2
DELETE FROM pages_done AS o
WHERE o.id IN (SELECT id FROM pages_done_ids)
STEP 3
DROP TABLE pages_done_ids;
OK, you may accomplish it with one transaction using TEMPORARY TABLES.
Happy Querying!