Combine two queries into one and reorder - mysql

I have two queries that work fine by themselves, however, because the second one is ran within the loop of the first one, I can not reorder results returned by the first query.
The first query always returns results, the second query - sometimes, but when it does I need those results to be on top. By default the results are sorted by distance, starting with the closest.
For example, here's what I get now:
Name1 (phone1) - 0.1 mi
Name2 (phone2) - 0.4 mi
Name3 (phone3) - 1.3 mi <- Now open (query 2 match)
Name4 (phone4) - 2.4 mi
What I would like to see:
Name3 (phone3) - 1.3 mi <- Now open (query 2 match)
Name1 (phone1) - 0.1 mi
Name2 (phone2) - 0.4 mi
Name4 (phone4) - 2.4 mi
Here are my current queries (simplified):
Query 1:
SELECT
t1.busName,
t1.busPhone
FROM t1
WHERE t1.lat BETWEEN $min_lat AND $max_lat
AND t1.lon BETWEEN $min_lon AND $max_lon
ORDER BY (POW((t1.lon-$lon),2) + POW((t1.lat-$lat),2))
LIMIT 5
Query 2:
SELECT COUNT(t3.rule_id) AS rcount
FROM t3
LEFT JOIN t2 ON (t3.rule_busID = t2.busID)
WHERE t3.APIid = '".$APIid."'
Another problem that I have is that there's no way to establish a direct connection between t1 and t3. The only way is to have t2
t1.APIid = t2.APIid
t2.busID = t3.rule_busID
Table structure as follows:
t1
--------------------------------------
busName | busPhone | lon | lat | APIid
t2
--------------------------------------
busID | APIid
t3
--------------------------------------
rule_id | rule_busID
Currently, with two queries, if I return 10 results I have to run 11 queries. Ideally I'd like to do it just once.
Sorry, this might be quite obvious, but I'm stuck.

Assumption: t3 does not contain the column APIid ("t3.APIid" in query 2 in question should read "t2.APIid").
Because you are ordering by columns not included in the select clause you need to perform the count calculation either as a derived table, or as a correlated subquery.
Derived Table
Here you perform the COUNT() & GROUP BY within a subquery and that result joined to the main query.
SELECT
t1.busName
, t1.busPhone
, COALESCE(r.rcount,0) rcount
FROM t1
LEFT JOIN (
SELECT
t2.APIid
, COUNT(t3.rule_id) AS rcount
FROM t3
INNER JOIN t2 ON t3.rule_busID = t2.busID
GROUP BY
t2.APIid
) r ON t1.APIid = r.APIid
WHERE t1.lat BETWEEN $min_lat AND $max_lat
AND t1.lon BETWEEN $min_lon AND $max_lon
ORDER BY (POW((t1.lon - $lon), 2) + POW((t1.lat - $lat), 2))
#LIMIT 5
;
Correlated Subquery
An alternative approach is to perform the count calculation inside the select clause of the main query. This style of subquery can cause performance issues, but if the number of rows being returned from the main query isn't large then this approach may perform adequately well.
SELECT
t1.busName
, t1.busPhone
, COALESCE(SELECT COUNT(t3.rule_id)
FROM t3 INNER JOIN t2 ON t3.rule_busID = t2.busID
WHERE t2.APIid = t1.APIid
),0) as rCount
FROM t1
WHERE t1.lat BETWEEN $min_lat AND $max_lat
AND t1.lon BETWEEN $min_lon AND $max_lon
ORDER BY (POW((t1.lon - $lon), 2) + POW((t1.lat - $lat), 2))
#LIMIT 5
;
Note: In either approach there is no value in using a LEFT JOIN from t3 on t2. If t3 has rules that don't link to t2, it also becomes impossible to link those rules to t1. So, just use an INNER JOIN between t3 and t2.
You may require the use of COALESCE() or IFNULL() to return zero if there is no matching count. You can use either function but I prefer the ANSI standard COALESCE()
Adjust the LIMIT to suit your need.

Not sure if that really works, but you could try the following approach:
Use your Query1 as subquery in the FROM-clause (to add the inner sorting and limit), select the rule-count in the SELECT-clause. ORDER the result by count DESC.
Haven't tested it, but that should look something like:
SELECT
sub1.busName,
sub1.busPhone,
(SELECT COUNT(t3.rule_id) AS rcount FROM t3 LEFT JOIN t2 ON (t3.rule_busID = t2.busID) WHERE t2.APIid = t1.APIid) as rCount
FROM
(
SELECT
t1.busName,
t1.busPhone
FROM t1
WHERE t1.lat BETWEEN $min_lat AND $max_lat AND t1.lon BETWEEN $min_lon AND $max_lon
ORDER BY (POW((t1.lon-$lon),2) + POW((t1.lat-$lat),2)) ASC
LIMIT 5
) as sub1
ORDER BY rCount DESC
But actually I wouldn't to that, I'd probably stick with your current approach of individual less complex queries and to the re-ordering afterwards in the application.

you could use this approach:
select name, phone from (
select 0 order, name, phone
from ...
union all
select 1, name, phone
from ...
)q
order by q.order

You can directly provide the join if you have Relational Database(i.e connection between your each tables including direct or indirect).
so your query simplified into one like below:
SELECT
t1.busName,
t1.busPhone,
COUNT(t3.rule_id) AS rcount
FROM t1
INNER JOIN t2 on t2.APIid=t1.APIid
LEFT JOIN t3 on t2.busID=t3.rule_busID
WHERE t1.lat BETWEEN $min_lat AND $max_lat
AND t1.lon BETWEEN $min_lon AND $max_lon
AND t3.APIid = '".$APIid."'
ORDER BY (POW((t1.lon-$lon),2) + POW((t1.lat-$lat),2))
LIMIT 5
use LIMIT keyword only when you restrict your data to be display.

select name, phone from (
select ROW_NUMBER() over (order by name, phone) as order, name, phone
from ...
union all
select 1, name, phone
from ...
)q
order by q.order
use ROW_NUMBER() over (order by name, phone) you can partition and order asc and descending for generating and identity simulation and then you can union join or do what you want. (you can do the second select in the field of the first query select a, (select b from c where d.a = c.a) from d
I don't understand the output what you expect.
Sorry for my English :)

Related

Merge rows in mysql based on condition

I am trying to merge the rows based on condition in mysql.
I have table as shown below :
Looking merge the row 1 into row 2 (where the attendance count is larger)
and need to shown the result as :
I was trying to divide the dataset into 2 parts using the below query
select
a.student_id,a.school_id,a.name,a.grant,a.classification,a.original_classification,,a.consent_type
from (
select * from school_temp where original_classification='all' and availability='implicit')a
join(select * from school_temp where original_classification!='all' and availability!='implicit')b
on a.student_id = b.student_id and a.school_id=b.school_id and a.name=b.name
But unable to merge the rows and get total attendance count .
Please help me ,i am badly stuck in this
Split this into two queries that you combine with UNION.
The first joins the implicit row with the row with the highest attendance among the explicit rows for each student. See Retrieving the last record in each group - MySQL for how that works. Use SUM(attendance_count) to combine the attendances.
The second query in the UNION gets all the rows that don't have the highest attendance.
WITH explicit as (
SELECT *
FROM school_temp
WHERE original_classification!='all' and availability!='implicit'
)
SELECT a.student_id, a.school_id, a.name, SUM(attendance_count) AS attendance_count,
b.grant, b.classification, b.original_classification, b.consent_type
FROM school_temp AS a
JOIN (
SELECT t1.*
FROM explicit AS t1
JOIN (
SELECT student_id, school_id, name, MAX(attendance_count) AS max_attendance
FROM explicit AS t2
GROUP BY student_id, school_id, name
) AS t2 ON t1.student_id = t2.student_id AND t1.school_id = t2.school_id AND t1.name = t2.name AND t1.attendance_count = t2.max_attendance
) AS b ON a.student_id = b.student_id and a.school_id=b.school_id and a.name=b.name
WHERE a.original_classication = 'all' AND a.availability = 'implicit'
UNION ALL
SELECT t1.*
FROM explicit AS t1
JOIN (
SELECT student_id, school_id, name, MAX(attendance_count) AS max_attendance
FROM explicit AS t2
GROUP BY student_id, school_id, name
) AS t2 ON t1.student_id = t2.student_id AND t1.school_id = t2.school_id AND t1.name = t2.name AND t1.attendance_count < t2.max_attendance
I've used a CTE to give a name to the subquery that gets all the explicit rows. If you're using MySQL 5.x, you'll need to replace explicit with that subquery throughout the query. Or you could define it as a view.

why the sql correct and the inner mechanism for run it?

the sql as follows come from mysql document. it is:
SELECT * FROM t1 AS t
WHERE 2 = (SELECT COUNT(*) FROM t1 WHERE t1.id = t.id);
The document say It finds all rows in table t1 containing a value that occurs twice in a given column , and doesnot explain the sql.
t1 and t is the same table, so the
count(*) in subquery == select count(*) from t
, isn't it?
count(*) in subquery == select count(*) from t
is wrong. because in mysql you can't use it like that. so you have to run it like that to get result of same id having two rows.
if you want to get count of same occurrence,
SELECT id, name, count(*) AS all_count FROM t1 GROUP BY id HAVING all_count > 1 ORDER BY all_count DESC
And also you can get values as your query like this as well,
select * from t1 where id in ( select id from t1 group by id having count(*) > 1 )
The query contains a correlated subquery in WHERE clause:
SELECT COUNT(*) FROM t1 WHERE t1.id = t.id
It is called correlated because it is related to the main query via t.id. So, this subquery counts the number of records having an id value that is equal to the current id value of the record returned by the main query.
Thus, predicate
(SELECT COUNT(*) FROM t1 WHERE t1.id = t.id) = 2
evaluates to true for any row with an id value that occurs twice in the table.
SELECT * FROM t1 AS t
WHERE 2 = (SELECT COUNT(*) FROM t1 WHERE t1.id = t.id);
This query goes through each record in t1 and then in the subquery looks into t1 again to see if in this case id is found 2 times (and only 2 times). You can do the same for any other column in t1 (or any table for that matter).
When you would like to see all values that are multiple times in the table, change WHERE 2 = by WHERE 1 <. This will also give you the values that are 3 times, 4 times, etc. in the table.
{
SELECT id,count( * )
FROM
MyTable
group by id
having count( * )>1
}
with this code, you can see the rows which repet more than one,
and you can change this query by yourself
How about using GROUP BY and HAVING:
SELECT id, count(1) as Total FROM MyTable AS t1
GROUP BY t1.id
HAVING Total = 2

SQL Union Query - Referencing to alias of derived table

I have a complicated aggregate-functions query that produces a result-set, and which has to be amended with a single row that contains the totals and averages of that result-set.
My idea is to assign an alias to the result-set, and then use that alias in a second query, after a UNION ALL statement.
But, I can't successfully use the alias, in the subsequent SELECT statement, after the UNION ALL statement.
For the sake of simplicity, I won't post the original query here, just a simplified list of the variants I've tried:
SELECT * FROM fees AS Test1 WHERE Percentage = 15
UNION ALL
(SELECT * FROM fees AS Test2 WHERE Percentage > 15)
UNION ALL
(SELECT * FROM (SELECT * FROM fees AS Test3 WHERE Percentage < 10) AS Test4)
UNION ALL
SELECT * FROM Test3
The result is:
MySQL said: Documentation
#1146 - Table 'xxxxxx.Test3' doesn't exist
The result is the same if the last query references to the table Test1, Test2, or Test4.
So, how should I assign an alias to a result-set/derived table in earlier queries and use that same alias in latter queries, all within a UNION query?
Amendment:
My primary query is:
SELECT
COALESCE(referrers.name,order_items.ReferrerID),
SUM(order_items.quantity) as QtySold,
ROUND(SUM((order_items.quantity*order_items.price+order_items.shippingcosts)/((100+order_items.vat)/100)), 2) as TotalRevenueNetto,
ROUND(100*SUM(order_items.quantity*order_items.purchasepricenet)/SUM((order_items.quantity*order_items.price+order_items.shippingcosts)/((100+order_items.vat)/100)), 1) as PurchasePrice,
ROUND(100*SUM(order_items.quantity*COALESCE(order_items.calculatedfee,0)+order_items.quantity*COALESCE(order_items.calculatedcost,0))/SUM((order_items.quantity*order_items.price+order_items.shippingcosts)/((100+order_items.vat)/100)), 1) as Costs,
ROUND(100*SUM(order_items.calculatedprofit) / SUM( (order_items.quantity*order_items.price + order_items.shippingcosts)/((100+order_items.vat)/100) ) , 1) as Profit,
COALESCE(round(100*Returns.TotalReturns_Qty/SUM(order_items.quantity),2),0) as TotalReturns
FROM order_items LEFT JOIN (SELECT order_items.ReferrerID as ReferrerID, sum(order_items.quantity) as TotalReturns_Qty FROM order_items WHERE OrderType='returns' and OrderTimeStamp>='2017-12-1 00:00:00' GROUP BY order_items.ReferrerID) as Returns ON Returns.ReferrerID = order_items.ReferrerID LEFT JOIN `referrers` on `referrers`.`referrerId` = `order_items`.`ReferrerID`
WHERE ( ( order_items.BundleItemID in ('-1', '0') and order_items.OrderType in ('order', '') ) or ( order_items.BundleItemID is NULL and order_items.OrderType = 'returns' ) ) and order_items.OrderTimestamp >= '2017-12-1 00:00:00'
GROUP BY order_items.ReferrerID
ORDER BY referrers.name ASC
I want to make a grand-total of all the rows resulting from query above with:
SELECT 'All marketplaces', SUM(QtySold), SUM(TotalRevenueNetto), AVG(PurchasePrice), AVG(Costs), AVG(Profit), AVG(TotalReturns) FROM PrimaryQuery
I want to do this with a single query.
Your query is well-written. You may be able to get a total line by using a surrounding query with a dummy GROUP BY clause and WITH ROLLUP:
SELECT
COALESCE(Referrer, 'All marketplaces'),
SUM(QtySold) AS QtySold,
SUM(TotalRevenueNetto) AS TotalRevenueNetto,
AVG(PurchasePrice) AS PurchasePrice,
AVG(Costs) AS Costs,
AVG(Profit) AS Profit,
AVG(TotalReturns) AS TotalReturns
FROM
(
SELECT
COALESCE(referrers.name,order_items.ReferrerID) AS Referrer,
SUM(order_items.quantity) AS QtySold,
...
) PrimaryQuery
GROUP BY Referrer ASC WITH ROLLUP;
I'm not entirely sure what you are attempting to solve, but I guess something like the following:
Hypothetical 'main' query:
SELECT T1.ID
, Sum(total_grade)/COUNT(subjects) as AverageGrade
FROM A_Table T1
JOIN AnotherTable T2
ON T2.id = T1.id
GROUP BY T1.ID
You want sub resultsets, without having to keep querying the same data.
Edit: I mistakenly thought the linked documentation and method mentioned below was for the current version of mySQL. It is however a draft for a future version, and CTE's are not currently supported.
In the absence of CTE support, I would probably just insert the resultset into a temporary table. Something like:
CREATE TABLE TEMP_TABLE(ID INT, AverageGrade DECIMAL(15, 3))
INSERT INTO TEMP_TABLE
SELECT T1.ID
, Sum(total_grade)/COUNT(subjects) as AverageGrade
FROM A_Table T1
JOIN AnotherTable T2
ON T2.id = T1.id
GROUP BY T1.ID
SELECT ID, AverageGrade FROM TEMP_TABLE WHERE AverageGrade > 5
UNION ALL
SELECT COUNT(ID) AS TotalCount, SUM(AverageGrade) AS Total_AVGGrade FROM TEMP_TABLE
DROP TABLE TEMP_TABLE
(Disclaimer: I'm not too familiar with mySQL, there may be some syntax errors here. The general idea should be clear, though.)
That is, of course, if i had to do it like this, there are probably better ways to achieve the same. See Thorsten Kettner's comments on the matter.
(Previous answer assuming CTE is a posibility:)
A CTE approach looks like:
WITH CTE AS
(
SELECT T1.ID
, Sum(total_grade)/COUNT(subjects) as AverageGrade
FROM A_Table T1
JOIN AnotherTable T2
ON T2.id = T1.id
GROUP BY T1.ID
)
SELECT ID, AverageGrade FROM CTE WHERE AverageGrade > 5
UNION ALL
SELECT COUNT(ID) AS TotalCount, SUM(AverageGrade) AS Total_AVGGrade FROM CTE
You have the error because every query involved in UNION doens't know the alias of other.
DB Engine execute, in your case, 4 queries and then paste them with UNION operation.
Your real table is fees. Test3 is an alias used in the third query.
If you want to process the results of UNION operation, you must encapsulate your queries in a MAIN query.
It looks like you need something like below. Please try
SELECT * FROM fees AS Test2 WHERE Percentage >= 15
UNION ALL
SELECT * FROM fees AS Test3 WHERE Percentage < 10
You can't use a table alias based on a subquery (is not in the scope of the outer united select) you must repeat the code eg:
SELECT * FROM fees AS Test1 WHERE Percentage = 15
UNION ALL
SELECT * FROM fees AS Test2 WHERE Percentage > 15
UNION ALL
SELECT * FROM (
SELECT * FROM fees AS Test3 WHERE Percentage < 10
) AS Test4
UNION ALL
SELECT * FROM fees AS Test3 WHERE Percentage < 10

Select distinct records in mysql

My table
ANONYMOUS
ONE TWO
1 2
2 1
1 2
3 1
Now i want to select distinct set of one and two.
My selected list should be
ANONYMOUS
ONE TWO
1 2
3 1
Your question isn't very clear, but I guess you mean this:
SELECT DISTINCT one, two
FROM yourtable AS T1
WHERE one <= two
OR NOT EXISTS
(
SELECT *
FROM yourtable AS T2
WHERE T1.one = T2.two
AND T1.two = T2.one
)
It finds rows with (one, two) where the reversed pair (two, one) does not exist. If both exist, it chooses the pair such that one < two. It also selects rows where the values are equal.
See it working online: sqlfiddle
If you would prefer to use a JOIN instead of NOT EXISTS you can do that:
SELECT DISTINCT T1.one, T1.two
FROM yourtable AS T1
LEFT JOIN yourtable AS T2
ON T1.one = T2.two
AND T1.two = T2.one
WHERE T1.one <= T1.two
OR T2.one IS NULL
See it working online: sqlfiddle
SELECT DISTINCT a.*
FROM `ANONYMOUS` a
LEFT JOIN `ANONYMOUS` b ON (a.one=b.two and a.two=b.one)
WHERE b.one is null or a.one<b.one
ORDER BY 1,2

Mysql Select within IF condition

looking to perform a query that on a particular conditions gets the data from another table.
it like
select field1, field2.... IF(fieldX=3,select value from sub_table where cat_id = 3 order by id desc limit 0,1, NULL) from abc ;
The query within the if is valid.
I am used to with implementing if conditions without any issue but those were all for some static values or a field. But, this is the first time I am trying to get a select's result in if and unable to do it.
The case is because for some particular value of 'fieldX' I need to get a record from another table.
Regards
Junaid
wrap you inner select in ( )
IF(fieldX=3, (select value from sub_table where cat_id = 3 order by id desc limit 0,1), NULL)
why not use a left join and use fieldX=3 as a join condition? if fieldX is different from 3, sql fills the field with NULL
select a.field1, a.field2, sub.value
from abc a
left join
(
select value from sub_table
where cat_id = 3
limit 0,1
) sub
on a.fieldX = 3
or, if you do want to get all rows for the corresponding values (i see you have cat_id = 3 and fieldX = 3, so basically cat_id = fieldX), just use a simple join. no need to use complicated if constructs. sql was built to do fast and efficient joins:
select a.field1, a.field2, sub.value
from abc a
left join sub_table sub
on a.fieldX = sub.cat_id
note however, that the second query will return multiple rows, when there are more matches between fieldX and cat_id (non-unique cat_id)
you could do something like:
select fields... from sub_table st
where st.idSubTable in(
Select IF(fieldX=3
,(
select st.idSubTable from sub_table where cat_id = 3 order by id desc limit 0,1
),
NULL)
from abc);
it will solve your problem.