MySQL Simple Nested Select Statement - mysql

I have one table reports, which contains all info and read reports which just has Report ID and ID (of owner of the report)
I'm trying to do this statment (correct me if theres better out there) so it gets all the reports ID from read reports which match ID 1, and pick out all the details from reports for it. (Report ID's are the same on reports and read reports)
But this statement is giving me back no rows:
SELECT a.*
FROM `Reports` AS a,
(SELECT `Report ID` FROM `Read Reports` WHERE `Id` = 1) AS b
WHERE a.`Report ID` = b.`Report ID`;
Whats wrong with it/how can I improve it?
Thanks,
EDIT: My bad, it works fine!! Id 1 had no reports. Close this. :L
EDIT2: Still post if you have improvements though :P

Try this:
SELECT a.*, (SELECT `Report ID` FROM `Read Reports` WHERE `Id` = 1) AS b_report_id
FROM `Reports` AS a
HAVING a.`Report ID` = b_report_id;

There seems to be nothing wrong with your query and it should return the records unless there are no matching records. But if you say that there do exist matching records, I'd suggest that you re-read your query to confirm that you are using the right column names, i.e. not replaced "Id" with "Report ID"?
Can you give a snapshot of your data in your post?
By the way, the below query should be better because it does not involve derived table:
SELECT `a`.*
FROM `Reports` AS `a`
INNER JOIN `Read Reports` AS `b` ON `a`.`Report ID` = `b`.`Report ID`
WHERE `b`.`Id` = 1;

Related

Returning all records from derived table name that also have IN statements

I'm trying to retrieve a single record from my database, BUT I don't know which table name to query until I query another record.
If I knew the table name and ID the end query would look something like this.
SELECT * FROM `materials_sheet_stock` WHERE `id` = 2
But since I do NOT know the table name or the ID in that table I'm trying to break it up a bit.
This query will successfully retrieve the table name for the above query
SELECT tb1.*
FROM (SELECT `tag_table`
FROM `materials_group_tags_mapping`
WHERE `tag_id` IN
(SELECT `materials_group_tags`.`id`
FROM `materials_group_tags`
WHERE `materials_group_tags`.`name` = "frameless_base_side_material_unexposed")) AS tb1
which in this case is materials_sheet_stock
This query will successfully retrieve the ID that I need for the above query
(SELECT `materials_group_tags_mapping`.`tag_value`
FROM `materials_group_tags_mapping`
WHERE `materials_group_tags_mapping`.`tag_id` IN
(SELECT `materials_group_tags`.`id`
FROM `materials_group_tags`
WHERE `materials_group_tags`.`name` = "frameless_base_side_material_unexposed"))
But now when I put them all together in 1 query using IN it keeps throwing errors about not finding columns, or that all tables need an alias. I've tried editing the following code for like an hour with no luck. Hopefully you guys can spot the error. Here's the final code that I am using.
SELECT tb2.*
FROM (SELECT `tag_table`
FROM `materials_group_tags_mapping`
WHERE `tag_id` IN
(SELECT `materials_group_tags`.`id`
FROM `materials_group_tags`
WHERE `materials_group_tags`.`name` = "frameless_base_side_material_unexposed") ) as tb2
WHERE tb2.`id` IN
(SELECT `materials_group_tags_mapping`.`tag_value`
FROM `materials_group_tags_mapping`
WHERE `materials_group_tags_mapping`.`tag_id` IN
(SELECT `materials_group_tags`.`id`
FROM `materials_group_tags`
WHERE `materials_group_tags`.`name` = "frameless_base_side_material_unexposed"))

Table is specified twice, both as a target for 'UPDATE' and as a separate source for data in mysql

I have below query in mysql where I want to check if branch id and year of finance type from branch_master are equal with branch id and year of manager then update status in manager table against branch id in manager
UPDATE manager as m1
SET m1.status = 'Y'
WHERE m1.branch_id IN (
SELECT m2.branch_id FROM manager as m2
WHERE (m2.branch_id,m2.year) IN (
(
SELECT DISTINCT branch_id,year
FROM `branch_master`
WHERE type = 'finance'
)
)
)
but getting error
Table 'm1' is specified twice, both as a target for 'UPDATE' and as a
separate source for data
This is a typical MySQL thing and can usually be circumvented by selecting from the table derived, i.e. instead of
FROM manager AS m2
use
FROM (select * from manager) AS m2
The complete statement:
UPDATE manager
SET status = 'Y'
WHERE branch_id IN
(
select branch_id
FROM (select * from manager) AS m2
WHERE (branch_id, year) IN
(
SELECT branch_id, year
FROM branch_master
WHERE type = 'finance'
)
);
The correct answer is in this SO post.
The problem with here accepted answer is - as was already mentioned multiple times - creating a full copy of the whole table. This is way far from optimal and the most space complex one. The idea is to materialize the subset of data used for update only, so in your case it would be like this:
UPDATE manager as m1
SET m1.status = 'Y'
WHERE m1.branch_id IN (
SELECT * FROM(
SELECT m2.branch_id FROM manager as m2
WHERE (m2.branch_id,m2.year) IN (
SELECT DISTINCT branch_id,year
FROM `branch_master`
WHERE type = 'finance')
) t
)
Basically you just encapsulate your previous source for data query inside of
SELECT * FROM (...) t
Try to use the EXISTS operator:
UPDATE manager as m1
SET m1.status = 'Y'
WHERE EXISTS (SELECT 1
FROM (SELECT m2.branch_id
FROM branch_master AS bm
JOIN manager AS m2
WHERE bm.type = 'finance' AND
bm.branch_id = m2.branch_id AND
bm.year = m2.year) AS t
WHERE t.branch_id = m1.branch_id);
Note: The query uses an additional nesting level, as proposed by #Thorsten, as a means to circumvent the Table is specified twice error.
Demo here
Try :::
UPDATE manager as m1
SET m1.status = 'Y'
WHERE m1.branch_id IN (
(SELECT DISTINCT branch_id
FROM branch_master
WHERE type = 'finance'))
AND m1.year IN ((SELECT DISTINCT year
FROM branch_master
WHERE type = 'finance'))
The problem I had with the accepted answer is that create a copy of the whole table, and for me wasn't an option, I tried to execute it but after several hours I had to cancel it.
A very fast way if you have a huge amount of data is create a temporary table:
Create TMP table
CREATE TEMPORARY TABLE tmp_manager
(branch_id bigint auto_increment primary key,
year datetime null);
Populate TMP table
insert into tmp_manager (branch_id, year)
select branch_id, year
from manager;
Update with join
UPDATE manager as m, tmp_manager as tmp_m
inner JOIN manager as man on tmp_m.branch_id = man.branch_id
SET status = 'Y'
WHERE m.branch_id = tmp_m.branch_id and m.year = tmp_m.year and m.type = 'finance';
This is by far the fastest way:
UPDATE manager m
INNER JOIN branch_master b on m.branch_id=b.branch_id AND m.year=b.year
SET m.status='Y'
WHERE b.type='finance'
Note that if it is a 1:n relationship the SET command will be run more than once. In this case that is no problem. But if you have something like "SET price=price+5" you cannot use this construction.
Maybe not a solution, but some thoughts about why it doesn't work in the first place:
Reading data from a table and also writing data into that same table is somewhat an ill-defined task. In what order should the data be read and written? Should newly written data be considered when reading it back from the same table? MySQL refusing to execute this isn't just because of a limitation, it's because it's not a well-defined task.
The solutions involving SELECT ... FROM (SELECT * FROM table) AS tmp just dump the entire content of a table into a temporary table, which can then be used in any further outer queries, like for example an update query. This forces the order of operations to be: Select everything first into a temporary table and then use that data (instead of the data from the original table) to do the updates.
However if the table involved is large, then this temporary copying is going to be incredibly slow. No indexes will ever speed up SELECT * FROM table.
I might have a slow day today... but isn't the original query identical to this one, which souldn't have any problems?
UPDATE manager as m1
SET m1.status = 'Y'
WHERE (m1.branch_id, m1.year) IN (
SELECT DISTINCT branch_id,year
FROM `branch_master`
WHERE type = 'finance'
)

subquery in where clause of UPDATE statement

I have the database of ATM card in which there are fields account_no,card_no,is_blocked,is_activated,issue_date
Fields account number and card numbers are not unique as old card will be expired and marked as is_block=Y and another record with same card number ,account number will be inserted into new row with is_blocked=N . Now i need to update is_blocked/is_activated with help of issue_date i.e
UPDATE card_info set is_blocked='Y' where card_no='6396163270002509'
AND opening_date=(SELECT MAX(opening_date) FROM card_info WHERE card_no='6396163270002509')
but is doesn't allow me to do so
it throws following error
1093 - You can't specify target table 'card_info' for update in FROM clause
Try this instead:
UPDATE card_info ci
INNER JOIN
(
SELECT card_no, MAX(opening_date) MaxOpeningDate
FROM card_info
GROUP BY card_no
) cm ON ci.card_no = cm.card_no AND ci.opening_date = cm.MaxOpeningDate
SET ci.is_blocked='Y'
WHERE ci.card_no = '6396163270002509'
That's one of those stupid limitations of the MySQL parser. The usual way to solve this is to use a JOIN query as Mahmoud has shown.
The (at least to me) surprising part is that it really seems a parser problem, not a problem of the engine itself because if you wrap the sub-select into a derived table, this does work:
UPDATE card_info
SET is_blocked='Y'
WHERE card_no = '6396163270002509'
AND opening_date = ( select max_date
from (
SELECT MAX(opening_date) as_max_date
FROM card_info
WHERE card_no='6396163270002509') t
)

INSERT INTO between tables with INNER JOIN

An earlier data import in CiviCRM placed some member numbers into a custom field (member_number) instead of the more useful (external_id) field.
My (admittedly limited) SQL skills are way too rusty, but what I'm trying to do is:
IF external_id field is empty,
AND the contact_type is "Individual"
THEN copy the data from member_number to external_id for the matching internal id number.
I've tried a few variations of this, with different errors:
INSERT INTO test_table (external_id)
SELECT member_number
FROM member_info
INNER JOIN test_table
ON memberinfo.entity_id=test_table.id
WHERE test_table.external_id IS NULL AND test_table.contact_type = "Individual"
Do I even really need the INNER JOIN on this? And I know the WHERE statement usually refers to the table you're pulling from, not the one you're inserting to, but I can't remember the right way to do this.
update test_table
set external_id =
if(external_id = '' and contact_type = 'Individual', member_number,external_id)

Using SQL to get the Last Reply on a Post

I am trying to replicate a forum function by getting the last reply of a post.
For clarity, see PHPBB: there are four columns, and the last column is what I like to replicate.
I have my tables created as such:
discussion_id (primary key)
user_id
parent_id
comment
status
pubdate
I was thinking of creating a Link Table that would update for each time the post is replied to.
The link table would be as follow:
discussion_id (primary key)
last_user_id
last_user_update
However, I am hoping that theres a advance query to achieve this method. That is, grabbing each Parent Discussion, and finding the last reply in each of those Parent Discussions.
Am I right that there is such a query?
Here is a update.
I am still having a little trouble but I feel like I am almost there.
My current query:
SELECT
`discussion_id`,
`parent_id`,
`user_id` as `last_user_id`,
`user_name` as `last_user_name`
FROM `table1`, `table2`
WHERE `table1`.`id` = `table2`.`user_id`
Results:
discussion_id---------parent_id-----last_user_id-------last_user_name
30---------------------NULL-------------3--------------raiku
31---------------------30---------------2--------------antu
32---------------------30---------------1--------------admin
33---------------------NULL-------------3--------------raiku
Adding this:
GROUP BY `parent_id`
Turns it into:
discussion_id---------parent_id-----last_user_id-------last_user_name
32---------------------30---------------1--------------admin
33---------------------NULL-------------3--------------raiku
But I want it to turn it into:
discussion_id---------parent_id-----last_user_id-------last_user_name
30---------------------NULL-------------3--------------raiku
32---------------------30---------------1--------------admin
33---------------------NULL-------------3--------------raiku
Id 30, and ID 33 share the same parent_id: NULL but they are the "starting thread" or the "parent post"
They should not be combined, how would I go on by "Grouping" but "ignoring" null values?
This query will take the highest (thus assuming latest) discussion per parent_id. Not the neatest solution however ...
select discussion_id, user_id, pubdate
from tablename
where discussion_id in
(
select max(discussion_id)
from tablename
group by parent_id
)
You could try something like this:
SELECT parent.discussion_id,
child.discussion_id as last_discussion_id,
child.user_id as last_user_id,
child.pubdate as last_user_update
FROM Discussion parent
INNER JOIN Discussion child ON ( child.parent_id = parent.discussion_id )
LEFT OUTER JOIN Discussion c ON ( c.parent_id = parent.discussion_id AND c.discussion_id > child.discussion_id)
WHERE c.discussion_id IS NULL
The left join to Discussion c will not match when you have the post with the highest id, which should be the row that you want.
You want GROUP BY. This should work out OK:
SELECT MAX(`pubdate`), `discussion_id`, `user_id` FROM `table` GROUP BY `parent_id`
You'll obviously need to fill in an appropriate the WHERE clause and LIMIT as needed.