mysql search in main query and subquery - mysql

Having the following query where i am trying to search in a query, i might be writing a wrong syntax, but i am not sure how to correct it. I want to search for the text in the main table and the subquery table too.
here is my query
select mytable.*
from mytable
where spam = 0
and deleted = 0
and draft = 0
and (subject like '%guss%' or body like '%guss%' or
(select CONCAT(users.firstname,' ', users.lastname) as fname,users.email
from users where firstname like '%guss%' or lastname like 'guss'))
and id = 24
order by id desc
getting this Error
[Err] 1241 - Operand should contain 1 column(s)
Update #1
select mytable.*
from mytable
where spam = 0
and deleted = 0
and draft = 0
and (subject like '%eli%' or body like '%eli%' or
(select users.firstname
from users where firstname like '%eli%') or
(select users.lastname
from users where lastname like '%eli%'))
and id_receiver = 24
order by id desc

Here the error:
(select CONCAT(users.firstname,' ', users.lastname) as fname,users.email
from users where firstname like '%guss%' or lastname like 'guss'))
In your subquery you can return only one column
This is the first column: CONCAT(users.firstname,' ', users.lastname) as fname
This is the second column: users.email

You put a 2-column result as an operand of OR expression.
This causes the issue. It should return one column or be compared to something.
But if you want to search in both tables I guess UNION would be something you need.

Though I think this is wrong I don't believe mytable.id relates to users.id but that's what a comment said...
SELECT Distinct mytable.*
FROM mytable
INNER JOIN users
on myTable.ID = users.Id
WHERE mytable.spam = 0
and mytable.deleted = 0
and mytable.draft = 0
and CONCAT_WS(mytable.subject, mytable.body, users.firstname, users.lastname) like '%eli%'
and mytable.id_receiver = 24
ORDER BY mytable.id desc
I removed the or using string concatenation. We want any record having the text of 'eli' in any of the columns subject, body, firstname, lastname. The system would have to loop through each column checking for a %eli% value. In theory up to 4 loops. By adding all the columns together to form one string and checking for eli we eliminate extra looping the engine would have to do at an overhead of the string concatenation. This should be faster.
I used distinct as I don't know what results you want and if the join will result in multiple records that serve no purpose. Since a * is being used I couldn't use a group by properly.
I joined to users assuming that you only want records in mytable that link to a user. This may be a wrong assumption.

Related

How could I get a list of unique records from below duplicated records by ignoring first two characters?

Here is the rule:
When comparing userId, only search userId starting with 'AB' and its matching duplicates (except 'AB'). Then get a list of "unique userId" by only returning above duplicated userId that is having 'AB' at the beginning.
For returned duplicated string starting with 'AB', we need to make sure there is "duplicate"; otherwise, we should not return 0 record
I know it sounds confusing, please see example below:
Table UserName with ten records, and its userId fields (10 records) are:
ABC1234
C1234
C12345
BC12345
BBC1234
ABF1235
F1235
ABY1236
BCD3456
D3456
Desired Result after running query:
ABC1234
ABF1235
Please note: Although ABY1236 starts with 'AB', this record should not be returned in output, since it doesn't have a "duplicate" match like Y1236 (ignoring first two character, 'AB').
I have a sample query below, but it only returned duplicated record NOT starting with 'AB', also it will return ABY1236.
SELECT distinct substr(userId , -(length(userID)-2))
from UserName where userId like 'AB%';
Thanks for the help!
You can use EXISTS to check if there is a userId that is equal to the right part of "AB.." starting from the 3d char:
select u.userId from UserName u
where
u.userId like 'AB_%'
and
exists (
select 1 from UserName where userId = substr(u.userId, 3)
)
You could try using a selct join for check only the matching result
SELECT substr(a.userId , -(length(a.userID)-2))
from UserName a
INNER JOIN UserName b ON a.substr(a.userId , -(length(a.userID)-2)) = b.UserId
AND userId like 'AB%'

regular expression to extract json values in mysql field

I have a "users" table with an "assignments" field that has a list of course IDs and when then are assigned and whether they are required or optional in one json-like string (missing the top-level braces)
"BUS1077":{"startDate":"2013-09-16","hasPrerequisite":"","list":"required"},
"CMP1042":{"startDate":"2013-09-16","hasPrerequisite":"","list":"optional"},
"CMP1108":{"startDate":"2013-09-16","hasPrerequisite":"","list":"required"}
I have a another table, called "progress" that lists the course ids, like BUS1078, and whether they are completed or not.
I need a query to select the users who have completed all their required courses.
somthing like:
SELECT userid FROM users
where (count([ids from users.assignments where list:"required"] as courseid)
=count([extracted ids] joined using( courseid) where "complete"=1))
so there are just two tables
users (userid,assignments)
progress (id,userid,courseid,complete)
in the end I want to have selected the userids where each REQUIRED course is complete
(note, the database itself is much more complex, but this represents the gist of the problem)
As of MySQL 5.1 you can do this with built-in functions of common_schema you can use for this purpose. I haven't used it myself but I've found a nice blog about how you can parse JSON stored data and do something usefull with it.
The blog: http://mechanics.flite.com/blog/2013/04/08/json-parsing-in-mysql-using-common-schema/
I'm not familiar with the RegEx implementation in MySQL, but this basic approach should work:
SELECT userid FROM users WHERE NOT EXISTS(
SELECT NULL FROM assignments WHERE NOT EXISTS(
SELECT NULL FROM progress WHERE
progress.userid = users.userid
AND REGEXMATCH(
assignments.assignment,
'(^|,)"' + progress.courseid + '":.*?"list":"required"\}') >= 0
)
)
)
This should find all users where there is not a required assignment that the user hasn't completed.
Given the course IDs and the word "required" are unlikely to appear out of context, the regular expression itself could likely be much more naive, such as:
'"' + progress.courseid + '"[^}]+"required"'
I don't know about MySQL's current limitations when it comes to correlated subqueries, but the same thing could be accomplished with joins. Using EXISTS should be preferred over COUNT, since counting requires aggregation across the entire dataset rather than allowing a short-cut on the first non-match found.
if your courseid is always 7 characters long and the list in assignments field can have up to maximum of 10 courses
you can use this sqlFiddle
SELECT U.userId
FROM users U
WHERE NOT EXISTS
(SELECT 1 FROM
(SELECT users.userid,courseName,
(Assignments REGEXP CONCAT('"',courseName,'"[^}]+(:"required"})'))as Required,
Assignments,
courseid,complete
FROM
(SELECT userid,courseName FROM
(SELECT userid,SUBSTRING_INDEX(SUBSTRING_INDEX(assignments,'":{"startDate',course.num),'"',-1) as courseName
FROM users,(SELECT 1 as num
UNION SELECT 2
UNION SELECT 3
UNION SELECT 4
UNION SELECT 5
UNION SELECT 6
UNION SELECT 7
UNION SELECT 8
UNION SELECT 9
UNION SELECT 10)course
)T WHERE LENGTH(courseName)=7
)Courses
INNER JOIN users ON users.userid = Courses.userid
LEFT JOIN progress ON users.userid = progress.userid
AND Courses.courseName = progress.courseId
AND progress.complete = 1
)AllCourses
WHERE AllCourses.userId = U.userId
AND AllCourses.Required = 1
AND Complete IS NULL
)
What the query does is it grabs the courseName(s) from assignment fields and see if it's required and sets required flag, then LEFT JOIN with progress and we have the Required column and Complete is NULL when the course doesn't exist in progress or when complete is not 1.
We then select user id WHERE there does not EXISTS (a record in their Courses where Required = 1 AND Complete IS NULL)
In the fiddle, I have user 2 having only completed an optional course. So userId 2 is not returned.
You can just run the inner select for AllCourses subquery and see the data of all the courses for all users and whether they completed a course that is required or not.

Unknown column 'wp_cons_users.id' in 'on clause'

I have three table one is for users and other one is for subject and third one contain user_id, subject_id foreign keys.
I get unknow coloumn when I run the following sql.
SELECT wp_cons_users.first_name, wp_cons_subject.subject, wp_cons_skilllist.skill_level
FROM `wp_cons_subject`
JOIN wp_cons_skilllist ON wp_cons_skilllist.user_id = wp_cons_users.id
JOIN wp_cons_users ON wp_cons_users.id = wp_cons_skilllist.user_id
WHERE wp_cons_subject.id = '1'
ORDER BY `wp_cons_skilllist`.`skill_level` DESC
I can't find the error with this query.
wp_cons_skilllist
column link to
id (primay)
user_id wp_cons_users -> id
subj_id wp_cons_subject -> id
skill_level
Here I try to get the username, skill level and subject for any given subject id.
Looks like your main problem is with the ordering of your JOINs. In your first join, you are matching with wp_cons_users.id, but you don't join that table until later in the query. If you re-order the joins it should work better. Also, based on your table description, it seems that you will also need to join on subject_id. This query should help:
SELECT wp_cons_users.first_name
, wp_cons_subject.subject
, wp_cons_skilllist.skill_level
FROM wp_cons_users
JOIN `wp_cons_subject`
ON wp_cons_users.id=`wp_cons_subject`.user_id
AND wp_cons_subject.id = '1'
JOIN
wp_cons_skilllist
ON wp_cons_skilllist.user_id = wp_cons_users.id
AND wp_cons_skilllist.subject_id = `wp_cons_subject`.id
ORDER BY `wp_cons_skilllist`.`skill_level` DESC
I am guessing about the field names that weren't in your original query, so you may have to make some changes if they're different from what I'm assuming.
Without information about your attributes in your table, I'm afraid we can only assume that there is no ID column in your wp_cons_users table.
when I corrected the query to following it started to work.
SELECT wp_cons_users.first_name, wp_cons_subject.subject, wp_cons_skilllist.skill_level
FROM `wp_cons_skilllist`
JOIN wp_cons_subject ON wp_cons_subject.id = wp_cons_skilllist.subject_id
JOIN wp_cons_users ON wp_cons_users.id = wp_cons_skilllist.user_id
WHERE wp_cons_skilllist.subject_id = '1'
ORDER BY `wp_cons_skilllist`.`skill_level` DESC
LIMIT 0 , 30

Update with SELECT and group without GROUP BY

I have a table like this (MySQL 5.0.x, MyISAM):
response{id, title, status, ...} (status: 1 new, 3 multi)
I would like to update the status from new (status=1) to multi (status=3) of all the responses if at least 20 have the same title.
I have this one, but it does not work :
UPDATE response SET status = 3 WHERE status = 1 AND title IN (
SELECT title FROM (
SELECT DISTINCT(r.title) FROM response r WHERE EXISTS (
SELECT 1 FROM response spam WHERE spam.title = r.title LIMIT 20, 1)
)
as u)
Please note:
I do the nested select to avoid the famous You can't specify target table 'response' for update in FROM clause
I cannot use GROUP BY for performance reasons. The query cost with a solution using LIMIT is way better (but it is less readable).
EDIT:
It is possible to do SELECT FROM an UPDATE target in MySQL. See solution here
The issue is on the data selected which is totaly wrong.
The only solution I found which works is with a GROUP BY:
UPDATE response SET status = 3
WHERE status = 1 AND title IN (SELECT title
FROM (SELECT title
FROM response
GROUP BY title
HAVING COUNT(1) >= 20)
as derived_response)
Thanks for your help! :)
MySQL doesn't like it when you try to UPDATE and SELECT from the same table in one query. It has to do with locking priorities, etc.
Here's how I would solve this problem:
SELECT CONCAT('UPDATE response SET status = 3 ',
'WHERE status = 1 AND title = ', QUOTE(title), ';') AS sql
FROM response
GROUP BY title
HAVING COUNT(*) >= 20;
This query produces a series of UPDATE statements, with the quoted titles that deserve to be updated embedded. Capture the result and run it as an SQL script.
I understand that GROUP BY in MySQL often incurs a temporary table, and this can be costly. But is that a deal-breaker? How frequently do you need to run this query? Besides, any other solutions are likely to require a temporary table too.
I can think of one way to solve this problem without using GROUP BY:
CREATE TEMPORARY TABLE titlecount (c INTEGER, title VARCHAR(100) PRIMARY KEY);
INSERT INTO titlecount (c, title)
SELECT 1, title FROM response
ON DUPLICATE KEY UPDATE c = c+1;
UPDATE response JOIN titlecount USING (title)
SET response.status = 3
WHERE response.status = 1 AND titlecount.c >= 20;
But this also uses a temporary table, which is why you try to avoid using GROUP BY in the first place.
I would write something straightforward like below
UPDATE `response`, (
SELECT title, count(title) as count from `response`
WHERE status = 1
GROUP BY title
) AS tmp
SET response.status = 3
WHERE status = 1 AND response.title = tmp.title AND count >= 20;
Is using GROUP BY really that slow ? The solution you tried to implement looks like requesting again and again on the same table and should be way slower than using GROUP BY if it worked.
This is a funny peculiarity with MySQL - I can't think of a way to do it in a single statement (GROUP BY or no GROUP BY).
You could select the appropriate response rows into a temporary table first then do the update by selecting from that temp table.
you'll have to use a temporary table:
create temporary table r_update (title varchar(10));
insert r_update
select title
from response
group
by title
having count(*) < 20;
update response r
left outer
join r_update ru
on ru.title = r.title
set status = case when ru.title is null then 3 else 1;

How to find the next record after a specified one in SQL?

I'd like to use a single SQL query (in MySQL) to find the record which comes after one that I specify.
I.e., if the table has:
id, fruit
-- -----
1 apples
2 pears
3 oranges
I'd like to be able to do a query like:
SELECT * FROM table where previous_record has id=1 order by id;
(clearly that's not real SQL syntax, I'm just using pseudo-SQL to illustrate what I'm trying to achieve)
which would return:
2, pears
My current solution is just to fetch all the records, and look through them in PHP, but that's slower than I'd like. Is there a quicker way to do it?
I'd be happy with something that returned two rows -- i.e. the one with the specified value and the following row.
EDIT: Sorry, my question was badly worded. Unfortunately, my definition of "next" is not based on ID, but on alphabetical order of fruit name. Hence, my example above is wrong, and should return oranges, as it comes alphabetically next after apples. Is there a way to do the comparison on strings instead of ids?
After the question's edit and the simplification below, we can change it to
SELECT id FROM table WHERE fruit > 'apples' ORDER BY fruit LIMIT 1
SELECT * FROM table WHERE id > 1 ORDER BY id LIMIT 1
Even simpler
UPDATE:
SELECT * FROM table WHERE fruit > 'apples' ORDER BY fruit LIMIT 1
So simple, and no gymnastics required
Select * from Table
where id =
(Select Max(id) from Table
where id < #Id)
or, based on the string #fruitName = 'apples', or 'oranges' etc...
Select * from Table
where id =
(Select Max(id) from Table
where id < (Select id from Table
Where fruit = #fruitName))
I'm not familiar with the MySQL syntax, but with SQL Server you can do something with "top", for example:
SELECT TOP 1 * FROM table WHERE id > 1 ORDER BY id;
This assumes that the id field is unique. If it is not unique (say, a foreign key), you can do something similar and then join back against the same table.
Since I don't use MySQL, I am not sure of the syntax, but would imagine it to be similar.
Unless you specify a sort order, I don't believe the concepts of "previous" or "next" are available to you in SQL. You aren't guaranteed a particular order by the RDBMS by default. If you can sort by some column into ascending or descending order that's another matter.
This should work. The string 'apples' will need to be a parameter.
Fill in that parameter with a string, and this query will return the entire record for the first fruit after that item, in alphabetical order.
Unlike the LIMIT 1 approach, this should be platform-independent.
--STEP THREE: Get the full record w/the ID we found in step 2
select *
from
fruits fr
,(
--STEP TWO: Get the ID # of the name we found in step 1
select
min(vendor_id) min_id
from
fruits fr1
,(
--STEP ONE: Get the next name after "apples"
select min(name) next_name
from fruits frx
where frx.name > 'apples'
) minval
where fr1.name = minval.next_name
) x
where fr.vendor_id = x.min_id;
The equivalent to the LIMIT 1 approach in Oracle (just for reference) would be this:
select *
from
(
select *
from fruits frx
where frx.name > 'apples'
order by name
)
where rownum = 1
I don't know MySQL SQL but I still try
select n.id
from fruit n
, fruit p
where n.id = p.id + 1;
edit:
select n.id, n.fruitname
from fruits n
, fruits p
where n.id = p.id + 1;
edit two:
Jason Lepack has said that that doesn't work when there are gaps and that is true and I should read the question better.
I should have used analytics to sort the results on fruitname
select id
, fruitname
, lead(id) over (order by fruitname) id_next
, lead(fruitname) over (order by fruitname) fruitname_next
from fruits;
If you are using MS SQL Server 2008 (not sure if available for previous versions)...
In the event that you are trying to find the next record and you do not have a unique ID to reference in an applicable manner, try using ROW_NUMBER(). See this link
Depending on how savvy your T-SQL skill is, you can create row numbers based on your sorting order. Then you can find more than just the previous and next record. Utilize it in views or sub-queries to find another record relative to the current record's row number.
SELECT cur.id as id, nxt.id as nextId, prev.id as prevId FROM video as cur
LEFT JOIN video as nxt ON nxt.id > cur.id
LEFT JOIN video as prev ON prev.id < cur.id
WHERE cur.id = 12
ORDER BY prev.id DESC, nxt.id ASC
LIMIT 1
If you want the item with previous and next item this query lets you do just that.
This also allows You to have gaps in the data!
How about this:
Select * from table where id = 1 + 1