MySQL Query Speed Up - mysql

I'm having a problem with the speed of a query - it's running at about 16 seconds at the moment, and I need to speed it up!
My table scheme is as follows:
Users:
id (int 10, primary key)
username (varchar 100)
password (varchar 100)
Users_meta:
id (int 10, primary key)
user (int 10)
meta (varchar 100)
value (longtext)
I need to return data from various rows in the user meta table (such as first name, last name etc.) as columns. This query does the job, but runs too slow:
SELECT
Users.id as id,
Users.username as username,
firstName.value as metaFirstName,
lastName.value as metaLastName,
userLevel.value as metaUsername,
employer.value as metaEmployer,
gto.value as metaGTO
FROM Users
LEFT JOIN (Users_meta as firstName) ON (firstName.user = Users.id AND firstName.meta = 'first_name')
LEFT JOIN (Users_meta as lastName) ON (lastName.user = Users.id AND lastName.meta = 'last_name')
LEFT JOIN (Users_meta as userLevel) ON (userLevel.user = Users.id AND userLevel.meta = 'user_level')
LEFT JOIN (Users_meta as employer) ON (employer.user = Users.id AND employer.meta = 'employer')
LEFT JOIN (Users_meta as gto) ON (gto.user = Users.id AND gto.meta = 'gto')
I also need to be able to add WHERE and ORDER BY clauses to the query.
Thanks for your help. :)

I don't know if this is faster. But maybe something like this:
SELECT
Users.id as id,
Users.username as username,
MAX(CASE WHEN Users_meta.meta = 'first_name' THEN Users_meta.value ELSE NULL END) AS metaFirstName,
MAX(CASE WHEN Users_meta.meta = 'last_name' THEN Users_meta.value ELSE NULL END) AS metaLastName,
MAX(CASE WHEN Users_meta.meta = 'user_level' THEN Users_meta.value ELSE NULL END) AS metaUsername,
MAX(CASE WHEN Users_meta.meta = 'employer' THEN Users_meta.value ELSE NULL END) AS metaEmployer,
MAX(CASE WHEN Users_meta.meta = 'gto' THEN Users_meta.value ELSE NULL END) AS metaGTO
FROM
Users
LEFT JOIN Users_meta
ON Users_meta.user = Users.id
GROUP BY
Users.ID,
Users.username

I would first add a compound index on table meta: (meta, user, value). Or (user, meta, value). These would sure help if you had additional WHERE conditions in your query.
The query has now to use (almost) all the data in table Users_meta so these indexes may not be used.
The longtext datatype is another problem. Are you sure you need so wide column there?

Related

How to return '1' if data exists and '0' if there is no data with mysql CASE

I have a purchase table and user_id is a foreign key.
If there is user_id in the purchase table, it returns 1,
I want to return 0 if there is none.
I tried using the case statement, but it returns the following result.
{
"data": []
}
Below is the query. Is there any other way? I need help.
dao.js
const getUserPurchase = async(userId) => {
const purchase = await myDataSource.query(
`SELECT
u.id,
CASE
WHEN p.user_id is null
THEN '0'
ELSE '1'
END AS userPurchase
FROM purchase p
JOIN users u ON p.user_id = u.id
WHERE u.id = ?`,
[userId],
);
return purchase;
};
I'd write it this way:
SELECT
u.id,
COUNT(*) > 0 AS userPurchase
FROM users u
LEFT OUTER JOIN purchase p ON p.user_id = u.id
WHERE u.id = ?
GROUP BY u.id
You want to SELECT all users and for those users who have purchased something, that is a corrsponding record in table purchase exists, you want to get a 1, for all others a 0.
SELECT u.id,
CASE
WHEN EXISTS (SELECT 1 FROM purchase p WHERE p.user_id = u.id) THEN 1 ELSE 0
END AS userPurchase
FROM users u
WHERE u.id = ?
It write its in this way...I am using table name "book" and "bookshelf"
and Status be 0 when book in not available in "bookshelf" table
SELECT bookshelf.`id_bookshelf`,bookshelf.name_bookshelf,book.id_book,
(CASE WHEN book.id_book IS NULL THEN 0 ELSE 1 END) as status
FROM `bookshelf`
LEFT JOIN book ON bookshelf.id_bookshelf = book.id_bookshelf
GROUP BY bookshelf.id_bookshelf

SQL JOIN Query not returning expected results

2I know I am having a simple issue .. But I cannot for the life of me solve it .. Here is what I am trying to do. I have 3 tables and some sample data:
customer_entity_varchar
entity_id attribute_id value
'2' '5' 'John'
'2' '7' 'Smith'
'2' '336' 'ADELANTO'
'3' '5' 'Jane'
'3' '7' 'Doe'
'3' '336' 'ADELANTO'
'4' '5' 'Peter'
'4' '7' 'Griffin'
'4' '336' 'Not ADELANTO'
customer_entity
entity_id email
'2' 'jsmith#whatever.com'
'3' 'janed#thisthat.com'
'4' 'peterg#notanemail.com'
What I am trying to come up with first name, last name and email for everyone that matches a certain district which is attribut_id = '336'. What I am trying is this:
SELECT CE.email as email,
max(case when CEV.attribute_id = '5' then CEV.value end) as FirstName,
max(case when CEV.attribute_id = '7' then CEV.value end) as LastName
FROM customer_entity_varchar CEV
LEFT JOIN customer_entity CE
ON ( CE.entity_id = CEV.entity_id)
WHERE CEV.value ='ADELANTO'
AND CEV.attribute_id='336'
My hopes for a result are:
email FirstName LastName
jsmith#whatever.com John Smith
janed#thisthat.com Jane Doe
However what I am getting back is a SINGLE row -- where email has a value, however both FirstName and LastName are blank. Is my logic flawed?
I would probably solve this like this. It's a solution that favours readability.
WITH firstNames AS
(SELECT entity_id, Value FROM customer_entity_char WHERE attribute_id = '5')
lastNames AS
(SELECT entity_id, Value FROM customer_entity_char WHERE attribute_id = '7')
districts AS
(SELECT entity_id, Value FROM customer_entity_char WHERE attribute_id = '336')
SELECT ce.email, fn.Value, ln.Value, d.Value FROM firstNames fn,lastNames ln, districts d
INNER JOIN customer_entity ce
WHERE fn.entity_id = ln.entity_id AND ln.entity_id = d.entity_id AND ce.entity_id = d.entity_id
AND d.Value = 'ADELANTO';
The WHERE condition in your query is excluding rows with attribute_id 5,7 and so it will not give value containing first and last name.
Try this
SELECT CE.email as email,
max(case when CEV.attribute_id = '5' then CEV.value end) as FirstName,
max(case when CEV.attribute_id = '7' then CEV.value end) as LastName
FROM
(
SELECT entity_id,attribute_id,value
FROM customer_entity_varchar
WHERE entity_id IN (
SELECT entity_id
FROM customer_entity_varchar
WHERE value ='ADELANTO'
AND attribute_id='336'
)
AND attribute_id IN ('5','7')
)As CEV
INNER JOIN customer_entity CE
ON CE.entity_id = CEV.entity_id
GROUP BY CEV.entity_id
SQL Fiddle Demo
You're dealing with an entity-attribute-value table. That means you're going to suffer the joys of an endless number of outer joins in every query. This is why EAV sucks and you're told not to do it unless you have to.
The single query fix:
SELECT DISTINCT
-- ^^^^You need this because you're cross joining the crap out of the EAV table.
CEV.entity_id,
CE.email,
CEV5.value "FirstName",
CEV7.value "LastName"
FROM customer_entity_varchar CEV
-- ^^^^ This is the base table that determines what entities exist. It makes sure you always have your entity_id even if attributes are missing.
LEFT JOIN customer_entity_varchar CEV5
ON CEV5.entity_id = CEV.entity_id AND CEV5.attribute_id = 5
-- ^^^^ This is the join that lets you access attribute 5, FirstName
LEFT JOIN customer_entity_varchar CEV7
ON CEV7.entity_id = CEV.entity_id AND CEV7.attribute_id = 7
-- ^^^^ This is the join that lets you access attribute 7, LastName
LEFT JOIN customer_entity_varchar CEV336
ON CEV336.entity_id = CEV.entity_id AND CEV336.attribute_id = 336
-- ^^^^ This is the join that lets you access attribute 336, City?
LEFT JOIN customer_entity CE
ON CE.entity_id = CEV.entity_id
-- ^^^^ Here's the other table, joined to the base table so we're sure it joins when partial data exists.
WHERE CEV336.value = 'ADELANTO'
Here's how to do it more easily.
First, make a view for your EAV table:
CREATE VIEW vw_customer_entity_varchar AS
SELECT DISTINCT
CEV.entity_id
CEV5.value "FirstName",
CEV7.value "LastName",
CEV336.value "City?"
FROM customer_entity_varchar CEV
LEFT JOIN customer_entity_varchar CEV5
ON CEV5.entity_id = CEV.entity_id AND CEV5.attribute_id = 5
LEFT JOIN customer_entity_varchar CEV7
ON CEV7.entity_id = CEV.entity_id AND CEV7.attribute_id = 7
LEFT JOIN customer_entity_varchar CEV336
ON CEV336.entity_id = CEV.entity_id AND CEV336.attribute_id = 336
Add additional joins for every field in your EAV table.
Then you can treat this view as a normal table, except it has terrible performance, and can't use it for INSERT, UPDATE, or DELETE.
Welcome to EAV hell.
First, do you really have those tick marks around all your values? In other words, are the quotations stored in the db?
As a couple of folk have pointed out, this database design is really a pain to query. Basically, you'll have to take two passes through your customer_entity_varchar table. One pass is to get the entity_id for whoever your're interested in (aliased as T1 in my query), and one to get their attributes (cev in the query). You will filter for this 'adelanto' value against the t1 query, which will give you the entity_ids. Then you can join that back to the CEV alias to get the actual information you want. (Hopefully all that made sense).
I think this what you are looking for:
SQL Fiddle
SELECT CE.email as email,
max(case when CEV.attribute_id = '5' then CEV.value end) as FirstName,
max(case when CEV.attribute_id = '7' then CEV.value end) as LastName
FROM customer_entity_varchar CEV
inner join (select distinct entity_id, value from customer_entity_varchar where attribute_id = 336) t1
on t1.entity_id = cev.entity_id
LEFT JOIN customer_entity CE
ON ( CE.entity_id = CEV.entity_id)
and t1.value = 'ADELANTO'
group by
ce.entity_id
EDIT: As MPelletier also pointed out, you need to include a group by. I can't stand that MySQL will allow you to submit that query without it.

MySQL nested ANDs and several conditions

I have two table for a multiple choice questionnaire (each user answers a series of questions):
users (userID, name, email)
votes (voteID, userID, questionID, answerID)
Sample data (users):
0, Some Name, some#thing.com
1, Other Name, some#one.com
Sample data (votes):
0, 1, 1, 1
1, 1, 2, 2
2, 1, 3, 2
I would like select all users who has the correct answers.
I tried this (where I've hardcoded the answers in):
$sql = "SELECT users.userID, users.name, users.email FROM users
INNER JOIN votes ON (users.userID = votes.userID)
WHERE (votes.questionID = '1' AND votes.answerID = '1')
AND (votes.questionID = '2' AND votes.answerID = '2')
AND (votes.questionID = '3' AND votes.answerID = '2')
AND (votes.questionID = '4' AND votes.answerID = '3')
AND (votes.questionID = '5' AND votes.answerID = '1')
GROUP BY users.userID";
But this doesn't return anything.
I've also tried something like this (where I've also hardcoded the answers in):
$sql = "SELECT users.userID, users.name, users.email FROM users
INNER JOIN transfertipsvotes ON (users.userID = transfertipsvotes.userID)
WHERE (transfertipsvotes.questionID = '1' AND transfertipsvotes.answerID = '1') GROUP BY users.userID
UNION
SELECT users.userID, users.name, users.email FROM users
INNER JOIN transfertipsvotes ON (users.userID = transfertipsvotes.userID)
WHERE (transfertipsvotes.questionID = '2' AND transfertipsvotes.answerID = '2') GROUP BY users.userID
UNION
SELECT users.userID, users.name, users.email FROM users
INNER JOIN transfertipsvotes ON (users.userID = transfertipsvotes.userID)
WHERE (transfertipsvotes.questionID = '3' AND transfertipsvotes.answerID = '2') GROUP BY users.userID";
But this just returns all users with one correct answer.
How do I make the correct query to select all users with the correct answers?
As far as I can see it now you need to use an INNER JOIN for each question, so each inner join will look like this:
INNER JOIN votes AS q1 ON (users.userID = q1.userID) AND q1.questionID = '1' AND q1.answerID ='1'
Repeat this for each question and you can check it.
If i understood you correctly, you want users who have answered all questions correct. In that case you should use INTERSECT instead of UNION
But for this you are hitting the table as many times as your questions. Its better to use OR clause in where "((question1 and Answer1) or (question2 and Answer2))". and at last do a group based on userid and get count of correct answer and fetch only those members whose has all correct answer.

MySQL COUNT() to return 0

I have a query that looks like this:
SELECT
app.application_id,
j.job_number,
j.job_id,
j.job_title,
j.job_city,
j.job_state,
p.person_id AS candidate_id,
p.first_name,
p.last_name,
app.start_date,
ope1.percent_complete,
MAX(CASE
WHEN r.role_display_name = 'ENG - Recruiter' THEN
(SELECT CASE WHEN COUNT(last_name) = 0 THEN
'Unassigned'
ELSE
COUNT(last_name)
END AS uname
FROM users
JOIN job_roles ON job_roles.user_id = users.user_id
WHERE job_id = j.job_id
AND role_id = r.role_id
)
ELSE '' END) AS role_3
My problem is that COUNT(last_name) will not return 0, because there are no records returned, so there is no value of NULL. All makes sense, however I have tried wrapping it in IFNULL(), ISNULL() and none of them seem to fix this problem. How can I get it to return 0 when there are no records? Do I need another subquery inside the COUNT() aggregate? I would really like to not use another subquery....
If understand correctly what you want you can try to rewrite it this way
SELECT ...
,MAX(CASE WHEN r.role_display_name = 'ENG - Recruiter'
THEN COALESCE(NULLIF(
(
SELECT COUNT(last_name)
FROM users JOIN job_roles
ON job_roles.user_id = users.user_id
WHERE job_id = j.job_id
AND role_id = r.role_id
), 0), 'Unassigned')
ELSE ''
END) as role_3
...

Unable to get left outer join result in mysql query

SELECT
BB.NAME BranchName,
VI.NAME Village,
COUNT(BAC.CBSACCOUNTNUMBER) 'No.Of Accounts',
SUM(BAC.CURRENTBALANCE) SumOfAmount,
SUM(CASE
WHEN transactiontype = 'C' THEN amount
ELSE 0
END) AS CreditTotal,
SUM(CASE
WHEN transactiontype = 'D' THEN amount
ELSE 0
END) AS DebitTotal,
SUM(CASE
WHEN transactiontype = 'C' THEN amount
WHEN transactiontype = 'D' THEN - 1 * amount
ELSE 0
END) AS CurrentBalance
FROM
CUSTOMER CU,
APPLICANT AP,
ADDRESS AD,
VILLAGE VI,
BANKBRANCH BB,
BANKACCOUNT BAC
LEFT OUTER JOIN
accounttransaction ACT ON BAC.CBSACCOUNTNUMBER = ACT.BANKACCOUNT_CBSACCOUNTNUMBER
AND ACT.TRANDATE <= '2013-03-21'
AND BAC.ACCOUNTOPENINGDATE < '2013-03-21'
AND ACT.BANKACCOUNT_CBSACCOUNTNUMBER IS NOT NULL
WHERE
CU.CODE = AP.CUSTOMER_CODE
AND BAC.ENTITY = 'CUSTOMER'
AND BAC.ENTITYCODE = CU.CODE
AND AD.ENTITY = 'APPLICANT'
AND AD.ENTITYCODE = AP.CODE
AND AD.VILLAGE_CODE = VI.CODE
AND AD.STATE_CODE = VI.STATE_CODE
AND AD.DISTRICT_CODE = VI.DISTRICT_CODE
AND AD.BLOCK_CODE = VI.BLOCK_CODE
AND AD.PANCHAYAT_CODE = VI.PANCHAYAT_CODE
AND CU.BANKBRANCH_CODE = BB.CODE
AND BAC.CBSACCOUNTNUMBER IS NOT NULL
AND ACT.TRANSACTIONTYPE IS NOT NULL
GROUP BY BB.NAME , VI.NAME;
Here is my information
I have two tables bankaccount and accountransactions table
If account is created it will go to bankaccount table and if any transaction is done so respective account number record in accounttrasactiosns table however I want to display the count of total account numbers respective to the branch which the account number existed in bankaccount and it is may or may not available in accounttransactions table.
I'm guessing that the problem you have is that you are not getting results for accounts that do not have data in your accounttransaction table, even though you are using a LEFT JOIN. If that is true, the reason is because your join condition includes AND ACT.BANKACCOUNT_CBSACCOUNTNUMBER IS NOT NULL, which defeats the LEFT JOIN. You also have two conditions in your WHERE clause that I bet should not be there.
You should learn to use explicit join syntax in your coding. Your code will be much clearer if you do that; it separates the join conditions from the WHERE clause, which does a very different thing. I took a stab at re-writing your query as an illustration:
SELECT
BB.NAME BranchName,
VI.NAME Village,
COUNT(BAC.CBSACCOUNTNUMBER) 'No.Of Accounts',
SUM(BAC.CURRENTBALANCE) SumOfAmount,
SUM(ACT.CurrentBalance) CurrentBalance,
SUM(ACT.DebitTotal) DebitTotal,
SUM(ACT.CreditTotal) CreditTotal
FROM CUSTOMER CU
JOIN APPLICANT AP
ON AP.CUSTOMER_CODE = CU.CODE
JOIN ADDRESS AD
ON AD.ENTITYCODE = AP.CODE
JOIN VILLAGE VI
ON VI.CODE = AD.VILLAGE_CODE
AND VI.STATE_CODE = AD.STATE_CODE
AND VI.DISTRICT_CODE = AD.DISTRICT_CODE
AND VI.BLOCK_CODE = AD.BLOCK_CODE
AND VI.PANCHAYAT_CODE = AD.PANCHAYAT_CODE
JOIN BANKBRANCH BB
ON BB.CODE = CU.BANKBRANCH_CODE
JOIN BANKACCOUNT BAC
ON BAC.ENTITYCODE = CU.CODE
LEFT OUTER JOIN (
SELECT BANKACCOUNT_CBSACCOUNTNUMBER,
SUM(CASE
WHEN transactiontype = 'C' THEN amount
ELSE 0
END) AS CreditTotal,
SUM(CASE
WHEN transactiontype = 'D' THEN amount
ELSE 0
END) AS DebitTotal,
SUM(CASE
WHEN transactiontype = 'C' THEN amount
WHEN transactiontype = 'D' THEN - 1 * amount
ELSE 0
END) AS CurrentBalance
FROM accounttransaction
WHERE TRANDATE <= '2013-03-21'
GROUP BY BANKACCOUNT_CBSACCOUNTNUMBER
) ACT
ON ACT.BANKACCOUNT_CBSACCOUNTNUMBER = BAC.CBSACCOUNTNUMBER
AND BAC.ACCOUNTOPENINGDATE < '2013-03-21'
WHERE BAC.ENTITY = 'CUSTOMER'
AND AD.ENTITY = 'APPLICANT'
GROUP BY BB.NAME , VI.NAME;
I removed this line from the LEFT JOIN condition
AND ACT.BANKACCOUNT_CBSACCOUNTNUMBER IS NOT NULL
And I removed these two lines from the WHERE clause
AND BAC.CBSACCOUNTNUMBER IS NOT NULL
AND ACT.TRANSACTIONTYPE IS NOT NULL
If that does not solve your problem, please revise your question to explain further.
UPDATE: Based on comments, the query is revised to calculate the debit, credit, and current balance by account using a derived table.
Also note the placement of the BAC.ACCOUNTOPENINGDATE < '2013-03-21' condition on left join. As written, this will return all accounts regardless of the opening date. If you want to only show accounts that were opened before that date, this condition should be moved to the WHERE clause.