how to execute sql select query with minimum runtime - php

I want to select from a table and then make select for each of them.
tables:
category
+====+=======+
| id | title |
+====+=======+
this table has list of category
email
+====+=======+==========+=============+
| id | eMail | domainId | elseColumns |
+====+=======+==========+=============+
this table has list of emails but domains are in another table
domain
+====+========+=============+
| id | domain | elseColumns |
+====+========+=============+
list of domains which used in email
subscriber_category
+========+============+
| userId | categoryId |
+========+============+
list of emails in categories
now the question is how can i list categories and count of emails in them with minimum runtime? my try is waiting 20sec for 200000 email and 20 category.
sql:
SELECT category.*,
(SELECT COUNT(DISTINCT subscriber_category.userId) FROM subscriber_category
JOIN email ON email.id=subscriber_category.userId
JOIN domain ON domain.id=email.domainId
WHERE subscriber_category.categoryId=category.id
AND email.blackList=0
AND domain.blackList=0
) AS qty
FROM category WHERE category.userId=1 ORDER BY category.title ASC

This is your query:
SELECT c.*,
(SELECT COUNT(DISTINCT sc.userId)
FROM subscriber_category sc JOIN
email e
ON e.id = sc.userId JOIN
domain d
ON d.id = e.domainId
WHERE sc.categoryId = c.id AND e.blackList = 0 AND d.blackList = 0
) AS qty
FROM category c
WHERE c.userId = 1
ORDER BY c.title ASC;
The structure is quite reasonable and indexes should help performance. The first index is on category(userId, title, id). This index should be used for the WHERE clause, the ORDER BY, and the correlated subquery.
Next, I assume that you have indexes on the id columns in email and domain. You could make these slightly more applicable to the query if you include the blacklist flag as a second column in the index. More importantly, you want an index on subscriber_category(categoryId, userId). I would also recommend removing the count(distinct) if it is not necessary.

Related

subquery returns more than one row for this query [duplicate]

I want to select information from two SQL tables within one query, the information is unrelated though, so no potential joints exist.
An example could be the following setup.
tblMadrid
id | name | games | goals
1 | ronaldo | 100 | 100
2 | benzema | 50 | 25
3 | bale | 75 | 50
4 | kroos | 80 | 10
tblBarcelona
id | name | games | goals
1 | neymar | 60 | 25
2 | messi | 150 | 200
3 | suarez | 80 | 80
4 | iniesta | 40 | 5
I want to have a query that gives me the following:
name | games | goals
messi | 150 | 200
ronaldo | 100 | 100
I tried to follow this logic: Multiple select statements in Single query but the following code did not work:
USE Liga_BBVA
SELECT (SELECT name,
games,
goals
FROM tblMadrid
WHERE name = 'ronaldo') AS table_a,
(SELECT name,
games,
goals
FROM tblBarcelona
WHERE name = 'messi') AS table_b
ORDER BY goals
Any advice on this one? Thanks
Info: The football stuff is just a simplifying example. In reality it is not possible to put both tables into one and have a new "team" column. The two tables have completely different structures, but I need something that matches the characteristics of this example.
You can do something like this:
(SELECT
name, games, goals
FROM tblMadrid WHERE name = 'ronaldo')
UNION
(SELECT
name, games, goals
FROM tblBarcelona WHERE name = 'messi')
ORDER BY goals;
See, for example: https://dev.mysql.com/doc/refman/5.0/en/union.html
If you like to keep records separate and not do the union.
Try query below
SELECT (SELECT name,
games,
goals
FROM tblMadrid
WHERE name = 'ronaldo') AS table_a,
(SELECT name,
games,
goals
FROM tblBarcelona
WHERE name = 'messi') AS table_b
FROM DUAL
The UNION statement is your friend:
SELECT a.playername, a.games, a.goals
FROM tblMadrid as a
WHERE a.playername = "ronaldo"
UNION
SELECT b.playername, b.games, b.goals
FROM tblBarcelona as b
WHERE b.playername = "messi"
ORDER BY goals;
You can union the queries as long as the columns match.
SELECT name,
games,
goals
FROM tblMadrid
WHERE id = 1
UNION ALL
SELECT name,
games,
goals
FROM tblBarcelona
WHERE id = 2
You can combine data from the two tables, order by goals highest first and then choose the top two like this:
MySQL
select *
from (
select * from tblMadrid
union all
select * from tblBarcelona
) alldata
order by goals desc
limit 0,2;
SQL Server
select top 2 *
from (
select * from tblMadrid
union all
select * from tblBarcelona
) alldata
order by goals desc;
If you only want Messi and Ronaldo
select * from tblBarcelona where name = 'messi'
union all
select * from tblMadrid where name = 'ronaldo'
To ensure that messi is at the top of the result, you can do something like this:
select * from (
select * from tblBarcelona where name = 'messi'
union all
select * from tblMadrid where name = 'ronaldo'
) stars
order by name;
select name, games, goals
from tblMadrid where name = 'ronaldo'
union
select name, games, goals
from tblBarcelona where name = 'messi'
ORDER BY goals
Using union will help in this case.
You can also use join on a condition that always returns true and is not related to data in these tables.See below
select tmd .name,tbc.goals from tblMadrid tmd join tblBarcelona tbc on 1=1;
join will help you even in case when tables do not have common columns
You can use UNION in this case
select id, name, games, goals from tblMadrid
union
select id, name, games, goals from tblBarcelona
you jsut have to maintain order of selected columns ie id, name, games, goals in both SQLs
as i see you want most goals in each team
you can try this
select name,games,max(goals) as 'most goals' from tblRealMadrid
union
select name,games,max(goals) as 'most goals' from tblBarcelona
In your case, the two tables have completely different structures and cannot be joined.
The UNION operator could be used. The UNION operator joins the results of two or more SELECT statements to produce a single result set. The first column in the SELECT statement is used to sort the result set.
SELECT name, games, goals
FROM tblMadrid
WHERE name = 'ronaldo'
UNION
SELECT name, games, goals
FROM tblBarcelona
WHERE name = 'messi'
ORDER BY goals;
Each SELECT statement must have the same number of columns and data types that are compatible. Also, if you want to keep the duplicates, use UNION ALL rather than UNION.

Performing a query on the basis of the results of another SQL query

I have two tables with the name of customers and installments.i.e.
Customer Table:
id | name |contact |address
1 | xyz |33333333|abc
2 | xrz |33322333|abcd
Installments Table:
id | customer_id |amount_paid |amount_left | date
1 | 1 |2000 |3000 | 13/05/2017
2 | 1 |2000 |1000 | 13/06/2017
Now, I want to fetch the latest installment row from installments table for every user, I can use that with the following query,
SELECT * FROM installments WHERE customer_id=1 ORDER BY `id` DESC LIMIT 1
Now, the issue is that I want to do it for all the customer ids. I tried sub query thing but that doesn't supports multiple rows. I tried to store all customer IDs in an array in PHP and used "IN" but because the number of customers is more than 3000, it takes too long and returns an error of execution time exceeded.
Any kind of help or tip will be really appreciated. Thanks
SELECT
Customers.CustomerId,
Customers.Name,
Customers.Contact,
Customers.Address,
Installments.InstallmentId,
Installments.AmountPaid,
Installments.AmountLeft,
Installments.Date
FROM
Customers
INNER JOIN
(
SELECT
MAX( InstallmentId ) AS MaxInstallmentId,
CustomerId
FROM
Installments
GROUP BY
CustomerId
) AS LatestInstallments ON Customers.CustomerId = LatestInstallments.CustomerId
INNER JOIN Installments ON LatestInstallments.MaxInstallmentId = Installments.InstallmentId
you can do something like this
SELECT c.*,I.* FROM Customer_Table c LEFT JOIN Installments_Table I ON c.id=I.customer_id ORDER BY c.id DESC LIMIT 1
If You wants to add limit of list then only set limit else leave limit part. Untill I got Your Question this will be help you. else your problem can be something else.
SELECT cust.*,inst_disp.* FROM Customer AS cust
LEFT JOIN (
SELECT MAX(id) AS in_id, customer_id
FROM installments
GROUP BY customer_id
) inst
ON inst.customer_id = cust.id
LEFT JOIN installments as inst_disp ON inst_disp.id = inst.in_id

MySQL: Select several rows based on several keys from two different tables

I have these two tables - user_schedules and user_schedule_meta, shown below:
------------------------------------
| id | scheduler_id | status |
------------------------------------
1 3 pending
2 5 active
3 6 active
and
----------------------------------------------
| id | user_schedule_id | meta_key |meta_value
----------------------------------------------
1 3 course-id 135
2 3 session-id 15
3 3 schedule-id 120
I want to write a query to enable me select, for example, from both tables where EVERYONE of the below 5 conditions are met:
user_schedule_id = 3
scheduler_id = 6
session_id = 15
course-id = 135
schedule-id = 120
This is what I have so far, but it is not working:
SELECT user_schedule_meta.`id` FROM user_schedule_meta, user_schedules
WHERE user_schedules.`scheduler_id` = 6
AND user_schedules.id = user_schedule_meta.`user_schedule_id`
AND (
(user_schedule_meta.`meta_key` = 'course-id' AND user_schedule_meta.`meta_value` = 135)
OR (user_schedule_meta.`meta_key` = 'session-id' AND user_schedule_meta.`meta_value` = 15)
OR (user_schedule_meta.`meta_key` = 'daily-schedule-id' AND user_schedule_meta.`meta_value` = 120)
)
GROUP BY user_schedule_meta.`id`
Any suggestions what I am not doing right?
This is a typical key-value store lookup problem. These are trickier than they look in SQL, in that they require multiple JOIN operations.
You need a virtual table with one row per user_schedules.id value, then you can filter it. So
SELECT u.id, u.scheduler_id
FROM user_schedules u
JOIN user_schedule_meta a ON u.id=a.user_schedule_id AND a.meta_key='course-id'
JOIN user_schedule_meta b ON u.id=b.user_schedule_id AND b.meta_key='session-id'
JOIN user_schedule_meta c ON u.id=c.user_schedule_id AND c.meta_key='daily-schedule-id'
WHERE a.meta_value = 135 -- value associated with course-id
AND b.meta_value=15 -- value associated with session-id
AND c.meta_value=120 -- value associated with daily-schedule-id
Notice also that you can list your table with associated attributes like this. This trick of joining the key/value table multiple times is a kind of pivot operation. I use LEFT JOIN because it will allow the result set to show rows where an attribute is missing.
SELECT u.id, u.scheduler_id, u.status,
a.meta_value AS course_id,
b.meta_value AS session_id,
c.meta_value AS daily_schedule_id
FROM user_schedules u
LEFT JOIN user_schedule_meta a ON u.id=a.user_schedule_id AND a.meta_key='course-id'
LEFT JOIN user_schedule_meta b ON u.id=b.user_schedule_id AND b.meta_key='session-id'
LEFT JOIN user_schedule_meta c ON u.id=c.user_schedule_id AND c.meta_key='daily-schedule-id'
try this is code
select * from user_schedule_meta where user_schedule_id=3 and
(meta_key='session-id' AND meta_value=15
or meta_key='daily-schedule-id' AND meta_value=120
or meta_key='course-id' AND meta_value=135
)

sql delete with select conditional

maybe someone can help me.
I have 2 sql tables:
// groups
| id_group | namegroup |
+------------+-----------+
| 30 | s |
// contacts
| name | group |
+------+-------+
| juan | s |
I need to DELETE a group from ID, but no has contacts associated with it.
I test the following query but doesnt work.
DELETE
FROM group
WHERE id_group = 30
AND (
SELECT
count(*) AS id
FROM contacts co
INNER JOIN GROUP c ON co. GROUP = c.namegroup
WHERE c.id_group = 30
) = 0
Thanks
You can try the following query if you want to delete group having ID = 30 only if the group is not associated to any contact :
Query #1:
DELETE
`groups`
FROM `groups`
LEFT JOIN contacts
ON contacts.`group` = `groups`.namegroup
WHERE `groups`.id_group = 30
AND contacts.`group` IS NULL;
And if you want to delete all groups which don't have any associated contact then try the following query instead:
Query #2:
DELETE
`groups`
FROM `groups`
LEFT JOIN contacts
ON contacts.`group` = `groups`.namegroup
WHERE contacts.`group` IS NULL;
If you define firigen key mysql will handle this issue and you do not need to do anything
In these cases(without forigen key) i usually first run following query:
select count(*) as id from contacts co inner join group c on co.group=c.namegroup where c.id_group=30
And say to user which if they can delete or not and if he could delete that row:
delete from group where id_group=30
I tried to find a solution like query which you had but could not and suggest this solution for you.

PHP: Find all products contained in an order

i want to notify users if all products they selected are available. I created a mysql table containing the following information:
+--------+-------------+------+-----+
| id | order_id | pro | avai|
+--------+-------------+------+-----+
| 1 | 28 | NO1 | y |
| 2 | 28 | NO2 | y |
| 3 | 28 | NO4 | n |
| 4 | 29 | NO8 | y |
+--------+-------------+------+-----+
I want to issue an email if all products (pro) from an order (order_id) are available (avai). The code i have so far:
$getData2 = mysql_query("SELECT * FROM notify WHERE avai = 'y'");
while($row = mysql_fetch_object($getData2))
{
//only gets the two with y, does not know that there is a third item that belongs to the order
}
That process should be automated, thats why i am not able to use a select based on the order_id.
Something like this, using an anti-join pattern should return the order_id where there are rows that are avail='y' and there no rows for that same order_id which are avail='n':
SELECT a.order_id
FROM notify a
LEFT
JOIN ( SELECT n.order_id
FROM notify n
WHERE n.avail = 'n'
GROUP BY n.order_id
) o
ON o.order_id = a.order_id
WHERE a.avail = 'y'
AND o.order_id IS NULL
GROUP BY a.order_id
There are other ways to get an equivalent result, such as using a NOT EXISTS predicate.
SELECT a.order_id
FROM notify a
WHERE a.avail = 'y'
AND NOT EXISTS (SELECT 1
FROM notify n
WHERE n.order_id = a.order_id
AND n.avail = 'n'
)
GROUP BY a.order_id
If there values other than 'y' and 'n', you may need to adjust the predicates to account for those conditions as well.
The logical inverse to avail = 'y' would be
NOT (avail <=> 'y')
To get only orders where ALL rows for a given order are avail='y', then we'd really need to check for any rows that are "not (avail='y')", rather than just "avail='n'". And can use the null-safe comparison operator ( <=> ) to conveniently handle comparisons to NULL values without having to code the OR avail IS NULL conditions.
SELECT a.order_id
FROM notify a
LEFT
JOIN ( SELECT n.order_id
FROM notify n
WHERE NOT (n.avail <=> 'y')
GROUP BY n.order_id
) o
ON o.order_id = a.order_id
WHERE a.avail = 'y'
AND o.order_id IS NULL
GROUP BY a.order_id
As your table is called notify, I assume it is just for this eMail notification?
Then you could use:
SELECT GROUP_CONCAT(avai SEPARATOR ',') FROM notify GROUP BY order_id
Then you loop through all the returned rows and explode() on ',' so you'll get an array for each row meaning for each order_id. So now, you just check the array for 'n'. If there is no 'n' in the array, you'll send the eMail and delete all rows containing that order_id. This way you don't send multiple eMails for the same order_id.
I hope that helps.
Here you can read up on GROUP_CONCAT if you want
I recommend you to change the n/y to Enum (statuses)
YES|NO|NOTIFY|ORDERED|SHIPPED|RECEIVED|RETURNED
SELECT t1.order_id,
(SELECT COUNT(*) FROM table AS t2 WHERE t2.order_id = t1.order_id) AS numOfProducts,
(SELECT COUNT(*) FROM table AS t3 WHERE t3.order_id = t1.order_id AND avai = 'y') AS
numOfAvaiProducts,
FROM table as t1
HAVING numOfAvaiProducts = numOfProducts
GROUP BY t1.order_id
The Query not tested but the it should work, you get here all the orders that
numOfAvaiProducts = numOfProducts and after notifying user you can update the status to notify and you dont get this result next time
Another advantage to do it like that is to save PHP code and runtime

Categories