Beginner MySQL query with COUNT-"filter" - mysql

I post this question here to get more clarity over my query while learning SQL
(The example is simplfied)
I have the following tables:
BookTable(bookID, isbn, title) // Holds every book, not the ammuont, just the writing
CopyTable(copyID, bookID) // Represent a physical copy
AuthorTable(authorID, fName, lName) // Represents an author
WriteTable(authorID, bookID) // Represents who wrote what
I want to select every author (Preferably like {authordID, fname, lname} ), if that author has a book written, which has more than 5 copies.
I am trying something like this:
SELECT DISTINCT authorID, fname, lname // My final "output table"
FROM T_Author
WHERE authorID IN
SELECT authorID, bookID
FROM T_Write
WHERE bookID IN
SELECT bookID, COUNT(*) AS count
FROM T_Copy
GROUP BY bookID // This part I doubt the most
WHERE count > 5
So my idea is:
Select every BookID that appears more than 5 times in CopyTable
Select every author that wrote any of those books from WriteTable
Write out the name of the author with data from AuthorTable
I am not able to test this if it acutally works, but is this the "Right" way to think in this problem?
Thanks in advance for any guidance.

You are pretty close. Try this:
SELECT a.authorID, a.fname, a.lname // My final "output table"
FROM T_Author a
WHERE a.authorID IN (SELECT w.authorID
FROM T_Write w
WHERE w.bookID IN (SELECT c.bookID
FROM T_Copy c
GROUP BY c.bookID // This part I doubt the most
HAVING COUNT(*) > 5
)
);
Notes:
Subqueries need their own parentheses.
For IN, the returned value has to exactly match what is being compared. In general, you cannot return two columns.
Use HAVING to filter after aggregation.
SELECT DISTINCT is not needed in the outer query. It just adds processing overhead.
Use table aliases and qualified column names in any query that has more than one table reference.

Related

how to get number only from string field mysql

I have field that has value kind of store878 . I would like to have 878 from select statement. How do I get that numbers from select statement
select
store,
address
from
detail,
store_number
where
store (here i would like to have number) = store_mumber.id
I haven't tested this to make sure is works:
SELECT store, address
FROM detail d
INNER JOIN store_number s
ON CAST(SUBSTRING(d.store, LOCATE('%[0-9]%', d.store)) AS int) = s.id
But you should really consider changing the structure of your database.

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.

Distinct value in two column

I have a query in mysql
select f1,f2,f3 from tableName;
I want to remove duplicate value in field f1 and f2 only,
I have tired as follows
select f1,f2,f3 from tableName
group by f1,f2;
But it will remove duplicates in f1 only.Any body can suggest me that how to remove duplicates in f1 and f2.
topic_id topic_name question_type
2237 Understanding Diversity Comprehensive
2237 Understanding Diversity Easy
2237 Understanding Diversity Application
44315 Bhasha, boli, lipi, or Vayakaran Intermediate
above is sample output having distinct value in question_type column only
here i want to remove duplicates from question_type and topic_id
expected output: having both topic_id and question_type distinct values
44315 Bhasha, boli, lipi, or Vayakaran Intermediate
2237 Understanding Diversity Comprehensive
You were almost there - you need to group the last column also. I can just guess the usage, so I added a sorted summary of covered question_types that might be handy for search or output:
SELECT
topic_id,
topic_name,
GROUP_CONCAT(DISTINCT question_type ORDER BY question_type) AS question_types
FROM tableName
GROUP BY topic_id, topic_name;
Output is:
'2237', 'Understanding Diversity', 'Application,Comprehensive,Easy'
'44315', 'Bhasha, boli, lipi, or Vayakaran', 'Itermediate'
My SQL really isn't that great, but I think you need to do what is called a nested SELECT statement.
Here is the manual reference.
http://dev.mysql.com/doc/refman/5.0/en/subqueries.html
That would make the SQL look something like this.
SELECT f1,f2,f3 FROM (SELECT f1,f2,f3 FROM tableName AS table1 GROUP BY f1) AS table2 GROUP BY f2;
Note, that I had to use the AS to create an alias, else "tableName" would conflict with the parent SQL select.

Dynamic query string

I want to add some dynamic content in from clause based on one particular column value.
is it possible?
For Example,
SELECT BILL.BILL_NO AS BILLNO,
IF(BILL.PATIENT_ID IS NULL,"CUS.CUSTOMERNAME AS NAME","PAT.PATIENTNAME AS NAME")
FROM
BILL_PATIENT_BILL AS BILL
LEFT JOIN IF(BILL.PATIENT_ID IS NULL," RT_TICKET_CUSTOMER AS CUS ON BILL.CUSTOMER_ID=CUS.ID"," RT_TICKET_PATIENT AS PAT ON BILL.PATIENT_ID=PAT.ID")
But This query is not working.
Here
BILL_PATIENT_BILL table is a common table.
It can have either PATIENT_ID or CUSTOMER_ID. If a particular record has PATIENT_ID i want PATIENTNAME in RT_TICKET_PATIENT as NAME OtherWise it will hold CUSTOMER_ID. If it is i want CUSTOMERNAME as NAME.
Here I m sure That BILL_PATIENT_BILL must have either PATIENT_ID or CUSTOMER_ID.
Can anyone help me?
You can also use IF() to select the right values instead of constructing your query from strings:
SELECT
BILL.BILL_NO AS BILLNO,
IF( BILL.PATIENT_ID IS NULL, cus.CUSTOMERNAME, pat.PATIENTNAME ) AS NAME
FROM
BILL_PATIENT_BILL AS BILL
LEFT JOIN RT_TICKET_CUSTOMER cus ON BILL.CUSTOMER_ID = cus.ID
LEFT JOIN RT_TICKET_PATIENT pat ON BILL.PATIENT_ID = pat.ID
However, it would also be possible to PREPARE a statement from strings and EXECUTE it but this technique is prone to SQL injections, i can only disadvise to do so:
read here: Is it possible to execute a string in MySQL?

What is the cleanest way of the following (MySQL)

I have a huge cities table containing around 3,000,000 rows. I needed to create a new column which contains the following line:
'City name, City name with accent, Country name'
Basic Schema is as follows:
city_id int
name varchar
status varchar
date_created int(11)
country_id int
accent_name varchar
city_country text
And there is the countries table which contains the contry_id and its name.
Now I figured out 2 ways to fill the city_country column.
Attempt 1:
delimiter //
CREATE FUNCTION getConcat(x INT(11))
RETURNS TEXT
READS SQL DATA
BEGIN
DECLARE var1 TEXT;
SELECT concat(CT.name, ', ', CT.accent_name, ', ', CR.name) AS Combined INTO var1 FROM `wp_City` AS CT LEFT JOIN `wp_Country` AS CR ON CR.country_id = CT.country_id WHERE CT.city_id = x;
RETURN var1;
END//
UPDATE `wp_City` SET `city_country`=(SELECT getConcat(city_id)) WHERE 1;
Attempt 2:
I created a new table containing just one column:
INSERT INTO `_myCity` (name, status, date_created, country_id, accent_name, lat, `long`, region, city_country)
SELECT c.name, c.status, c.date_created, c.country_id, c.accent_name, c.lat, c.long, c.region, _c.name
FROM `wp_City` as c inner join `wp_Country` _c on c.country_id = _c.country_id
Now the second way is much faster, but which is cleaner? The above will be executed only once, so the question is simply out of curiosity. If there are better ways of achieving this please do share!!
Thank you in advance.
If I were to do something like this I would rather go with a view that adds the column when needed (thus avoiding the additional overhead imposed by storing redundant data in the table on disk)