Optimize and reduce size of Union Select Query - mysql

My question is about how to optimize and reduce size of a sql query. I want to join more than 20 multiple queries using UNION, it is giving me the correct result as per the below logic, but I am looking for two things here
something more efficient
I already have 20 UNIONS in my query, and every month I have to add 2-4 UNIONS more which make this query very long so is there any way this query can be rephrased with less code
Select
'343' As 'Manual ID',
'24/07/2020' As 'Date',
A.ID,
O.Order_Name,
C.Customer_Name,
Q.Quantity
From Shipper A
Left Join Order O A.ID = O.ID
Left Join Customer C A.ID = C.ID
Left Join Quantity Q Q.ID = C.ID
where A.ID IN (1)
UNION
Select
'323' As 'Manual ID',
'24/08/2020' As 'Date',
A.ID,
O.Order_Name,
C.Customer_Name,
Q.Quantity
From Shipper A
Left Join Order O A.ID = O.ID
Left Join Customer C A.ID = C.ID
Left Join Quantity Q Q.ID = C.ID
where A.ID IN(2,3,4)
and so on ...
Result
Manual ID | Date | Shipper | Order Name | Customer Name | Qty
343 | 24/07/2020 | 1 | order1 | A | 5
323 | 24/08/2020 | 2 | order2 | B | 2
323 | 24/08/2020 | 3 | order3 | C | 1
323 | 24/08/2020 | 4 | order4 | D | 12

You can try this:
Select
CASE
WHEN A.ID IN(1) THEN '343'
WHEN A.ID IN(2,3,4) THEN '323'
END As 'Manual ID',
CASE
WHEN A.ID IN(1) THEN '24/07/2020'
WHEN A.ID IN(2,3,4) THEN '24/08/2020'
END As 'Date',
A.ID,
O.Order_Name,
C.Customer_Name,
Q.Quantity
From Shipper A
Left Join Order O A.ID = O.ID
Left Join Customer C A.ID = C.ID
Left Join Quantity Q Q.ID = C.ID
Where A.ID IN(1,2,3,4)

First suggestion is to move the parameters in to another table, then join on it. You can even make that an inline view if you don't want to use a real table...
Second suggestion is to use UNION ALL to avoid the costs of deduplication incurred by UNION.
SELECT
params.*,
O.Order_Name,
C.Customer_Name,
Q.Quantity
FROM
(
SELECT '343' As 'Manual ID', '24/07/2020' As 'Date', 1 AS A_ID
UNION ALL SELECT '323' As 'Manual ID', '24/08/2020' As 'Date', 2 AS A_ID
UNION ALL SELECT '323' As 'Manual ID', '24/08/2020' As 'Date', 3 AS A_ID
UNION ALL SELECT '323' As 'Manual ID', '24/08/2020' As 'Date', 4 AS A_ID
)
AS params
INNER JOIN Shipper A ON A.ID = params.A_ID
Left Join Order O ON A.ID = O.ID
Left Join Customer C ON C A.ID = C.ID
Left Join Quantity Q ON Q.ID = C.ID
Alternatively, don't recompute this every month. Write a new query each month, and insert the results into another table?

If you just want to go for query the better way would be to use the case when statement but every now and then you need to keep updating the query adding new cases.
Another, optimized solution will be to create a new table to store
Manual ID, Date, (Common) ID present in Shipper (Table). Then create a view to join all above tables with new Table.
New Table
Manual ID | Date | ID |
343 | 24/07/2020 | 1 |
323 | 24/08/2020 | 2 |
323 | 24/08/2020 | 3 |
323 | 24/08/2020 | 4 |
Then Create a View Joining all Tables including new new table with ID.
In this you just need add new value to new table and you will complete result in view it self.
CREATE VIEW MY_VIEW
AS
SELECT * FROM
(
Select
T.[Manual ID],
T.[Date],
A.ID,
O.Order_Name,
C.Customer_Name,
Q.Quantity
From Shipper A
Left Join Order O A.ID = O.ID
Left Join Customer C A.ID = C.ID
Left Join Quantity Q Q.ID = C.ID
Left Join NewTable T T.ID = A.ID
)
Now just insert value in new table and fetch complete data from MY_VIEW. It will give the same result as you are excepting.

Related

Mysql SELECT only unique values in one column when left joined with another table

This is the query:
SELECT a.id, a.userName,if(o.userId=1,'C',if(i.userId=1,'I','N')) AS relation
FROM tbl_users AS a
LEFT JOIN tbl_contacts AS o ON a.id = o.contactId
LEFT JOIN tbl_invites AS i ON a.id = i.invitedId
ORDER BY relation
This returns the output as follows:
+----+--------------+-------------+
| ID | USERNAME | RELATION |
+----+--------------+-------------+
| 1 | ray | C |
+----+--------------+-------------+
| 2 | john | I |
+----+--------------+-------------+
| 1 | ray | N |
+----+--------------+-------------+
I need to remove the third row from the select query by checking if possible that id is duplicate. The priority is as follows:
C -> I -> N. So since there is already a "ray" with a C, I dont want it again with an I or N.
I tried adding distinct(a.id) but it doesn't work. How do I do this?
Why doesn't DISTINCT work for this?
From the specs you gave, all you have to do is group by ID and username, then pick the lowest value of relation you can find (since C < I < N)
SELECT a.id, a.userName, MIN(if(o.userId=1,'C',if(i.userId=1,'I','N'))) AS relation
FROM tbl_users AS a
LEFT JOIN tbl_contacts AS o ON a.id = o.contactId
LEFT JOIN tbl_invites AS i ON a.id = i.invitedId
GROUP BY a.id, a.username
There are multiple ways to get the group-wise maximum/minimum as you can see in this manual page.
The best one suited for you is the first one, if the order of the rows can not be defined by alphabetic order.
In this case, given if the desired order were z-a-m (see Rams' comment) you'd need the FIELD() function.
So your answer is
SELECT
a.id,
a.userName,
if(o.userId=1,'C',if(i.userId=1,'I','N')) AS relation
FROM tbl_users a
LEFT JOIN tbl_contacts AS o ON a.id = o.contactId
LEFT JOIN tbl_invites AS i ON a.id = i.invitedId
WHERE
if(o.userId=1,'C',if(i.userId=1,'I','N')) = (
SELECT
if(o.userId=1,'C',if(i.userId=1,'I','N')) AS relation
FROM tbl_users aa
LEFT JOIN tbl_contacts AS o ON aa.id = o.contactId
LEFT JOIN tbl_invites AS i ON aa.id = i.invitedId
WHERE aa.id = a.id AND aa.userName = a.userName
ORDER BY FIELD(relation, 'N', 'I', 'C') DESC
LIMIT 1
)
Note, you can also do it like ORDER BY FIELD(relation, 'C', 'I', 'N') to have it more readable / intuitive. I turned it the other way round, because if you'd have the possibility of having a 'X' in the relation, the FIELD() function would have returned 0 because X is not specified as a parameter. Therefore it would be sorted before 'C'. By sorting descending and turning the order of the parameters around this can not happen.

mysql query of 2 tables with inner join

I want to join Table A and Table B and display the most recent entry for each truck.
Table A columns: TA ID,Truck_Num,Serial_Num
Table B columns: TB ID,Serial_Num,Lat,Long,DateTime
Desired output
Truck | DateTime | Serial_Num | Lat | Long
1 | datestamp | 123 | 1 | -1
2 | datestamp | 456 | 2 | -2
using join query
SELECT a.Truck b.Lat b.Long b.DateTime FROM TB as b INNER JOIN TA AS a a.Serial_Num=b.Serial_Num;
I'v tried this but it is only giving me the most recent entry's time.
SELECT a.Truck b.Lat b.Long b.DateTime FROM TB as b INNER JOIN TA AS a a.Serial_Num=b.Serial_Num WHERE b.DateTime = (SELECT MAX(b.DateTime) FROM TB tb WHERE a.Truck_Num=Truck_Num);
This will show the most recent item per Truck. If the rest of the information that you want to show is the same in all rows then you can put it on the group by clause, if not then you have to do a join with this result
SELECT Serial_Num, MAX(DateTime) FROM TB GROUP BY Serial_Num;
This is the join you have to do if the data is not equal and you cannot put it on the group by clause
SELECT Truck_Num, Lat, Long, maxDateTime FROM TB as b
INNER JOIN TA AS a ON a.Serial_Num=b.Serial_Num
INNER JOIN (SELECT Serial_Num, MAX(DateTime) as maxDateTime
FROM TB GROUP BY Serial_Num) as c
ON b.Serial_Num=c.Serial_Num AND maxDateTime = DateTime
It's should work, you can check this question:
Select only one row with max field with One-Many Relationship Table
SELECT
ta.Truck,
tb.Lat,
tb.Long,
MAX(tb.DateTime),
ta.Serial_Num as Serial_Num
FROM
TB as tb
INNER JOIN
TA as ta
ON (tb.Serial_Num = ta.Serial_Num)
GROUP BY
tb.Serial_Num

SQL Union query simplification

I don't know if this question is apropriate on this forum.
I have a huge query :
SELECT threshold.id, brand.id, COUNT(brand.id), threshold
FROM current_stock, article, product, brand, delivery, threshold
WHERE current_stock.article_id = article.id
AND article.product_product_code = product.product_code
AND product.brand_id = brand.id
AND article.delivery_id = delivery.id
AND delivery.store_id = 'E260'
AND threshold.brand_id = brand.id
GROUP BY brand.id
HAVING COUNT(brand.id) <= threshold
UNION
SELECT threshold.id, brand.id, 0, threshold
FROM current_stock, article, product, brand, delivery, threshold
WHERE threshold.store_id = 'E260'
AND threshold.brand_id NOT IN (
SELECT brand_id FROM current_stock, article, product, delivery
WHERE current_stock.article_id = article.id
AND article.product_product_code = product.product_code
AND article.delivery_id = delivery.id
AND delivery.store_id = 'E260')
And I think it's possible to do better but after a entire day of try I haven't found a better query giving the same result.
For clarify, I have a stock (with current_stock, article, product and delivery). I also have thresholds. what I want is to check for each thresholds if there is the given minimum amount of stock for the given brand.
My problem is that if there is 0 article of a brand, the first part of the query will not take care about the threshold on this brand. It's why I have added an uggly Union.
Someone have an idea for a better way to do this ?
EDIT
This what I have done after the reading of comments and answers :
SELECT t.id, b.id, t.threshold, count(b.id) stock
FROM threshold t
inner join brand b on b.id = t.brand_id
left join product p on p.brand_id = b.id
inner join article a on a.product_product_code = p.product_code
inner join delivery d on d.id = a.delivery_id
inner join current_stock cs on cs.article_id = a.id
WHERE
t.store_id = 'E260' AND
d.store_id = 'E260'
GROUP BY b.id
HAVING stock <= t.threshold
My problem is that it don't gives all threholds... only ones that have at least one 'current_stock'. I have perhaps don't understand how joins are working.
Here an example of threshold table :
| id | brand_id | threshold |
-----------------------------
| 1 | 86 | 1 |
| 2 | 28 | 1 |
| 3 | 12 | 1 |
What I want as result this :
# with 2 entries in 'current_stock' for the brand id 28, 1 for 12 and 0 for 86
| t.id | b.id | threshold | stock |
-----------------------------------
| 1 | 86 | 1 | 0 |
| 3 | 12 | 1 | 1 |
Guessing a few parts here since you've used implicit joins in your sample. An explicit version would look something like this (provided I guessed correctly for how you are joining the threshold table).
SELECT
t.id,
b.id,
COUNT(b.id),
t.threshold
FROM
current_stock c
inner join article a on a.id = c.article_id
inner join delivery d on d.id = a.delivery_id
inner join product p on p.product_code = a.product_product_code
inner join brand b on b.id = p.brand_id
inner join threshold t on t.brand_id = b.id
WHERE
d.store_id = 'E260'
GROUP BY b.id
HAVING COUNT(b.id) <= t.threshold
Now to get your results to include rows where there aren't any 'articles' you can start switching out the inner joins for left joins. However, you can't simply use left outer join article... in the example above, because the store_id in the WHERE clause will just turn it back into a pseudo inner join.
Instead, is there a different field you can join the delivery table on from current_stock?
EDIT - 07/29/15
I think you're close, you may just have one too many filters and you're counting from 'b' when the wanted outcome suggests you should be counting from 'cs' instead. Try this:
SELECT t.id, b.id, t.threshold, count(cs.id) stock
FROM
threshold t
inner join brand b on b.id = t.brand_id
inner join product p on p.brand_id = b.id
inner join article a on a.product_product_code = p.product_code
left outer join delivery d on d.store_id = t.store_id
left outer join current_stock cs on cs.article_id = a.id
WHERE
t.store_id = 'E260'
GROUP BY b.id
HAVING stock <= t.threshold

Pass value from query 1 to query 2

I have a query join 2 table as below:
SELECT * FROM Staff s INNER JOIN Account a on s.AccNo = a.AccNo WHERE a.Status = 'Active'
The result as shown:
AccNo | Name | ID
------------------
1 | Alex | S01
2 | John | S02
After I get the staff ID,I write second query to find out the max sale as below:
SELECT s.ProductID,Max(s.Amount) from Sales s WHERE StaffID = 'S01' GROUP BY s.ProductID
The max sale for Staff 'S01' as below:
ProductID | Amount
------------------
Cloth | 2000
How to I combine these 2 queries and become result as below? Thanks
AccNo | Name | ID | Amount
--------------------------
1 | Alex | S01 | 2000
2 | John | S02 | 5000
You can create a subquery and join it:
SELECT a.AccNo, b.Name, b.ID, c.maximum
FROM transaction as a
INNER JOIN Account as b
ON a.AccNo = b.AccNo
LEFT JOIN (SELECT StaffID, Max(Amount) as maximum FROM Sales GROUP BY StaffID) as c
ON c.StaffID = b.ID
WHERE b.Status = 'Active'
See the SQLFiddle example (I've tried to guess the schema)
So what you want to do is join to sales on the staffId then group.
SELECT a.AccNo,a.Name,a.ID,Max(s.Amount)
FROM Transaction t
INNER JOIN Account a on t.AccNo = a.AccNo
INNER JOIN Sales s on s.staffId = a.ID
WHERE a.Status = 'Active'
GROUP BY a.AccNo,a.Name,a.ID
You could try something like this:
Select Account.*, Max(Sales.amount) from Sales
JOIN Account ON Sales.StaffID = Account.ID
where Account.status = 'Active'
group by Sales.ProductID, Account.AccNo, Account.Name, Account.ID
Honestly, I don't understand why do you use Transascation table in your queries, because you don't use it.
I think this should work
Just do a join and retrieve the max amount associated with each staff
SELECT t.AccNo , t.Name, t.ID, s.ProductID, Max(s.Amount) FROM Transaction t
INNER JOIN Account a ON t.AccNo = a.AccNo
INNER JOIN Sales s ON s.StaffID = a.ID
WHERE a.Status = 'Active';
Thanks

MySql query help : joints with sums and counts

I have this database structure:
TBL_A | TBL_B | TBL_C | TBL_D | TBL_E
-------+---------+---------+---------+----------
id | id_tbla | id_tbla | id_tbla | id
name | id_user | id_user | id_user | name_tbla
... | is_bool | | weight | id_user
Here is what I'm trying to achieve :
SELECT
a.id,
a.name,
b.is_bool,
count(c.id_user) AS nb_views,
sum(d.weight) AS total_weight,
count(distinct e.id_user) AS distinct_users,
FROM TBL_A AS a
LEFT JOIN (TBL_B AS b) on (b.id_tbla = a.id)
LEFT JOIN (TBL_C AS c) on (c.id_tbla = a.id)
LEFT JOIN (TBL_D AS d) on (d.id_tbla = a.id)
LEFT JOIN (TBL_E AS e) on (e.name_tbla = a.name)
where a.id = 1 and e.id_user = 1
The query is performed but the results (nb_views, total_weight, distinct_users) are wrong. Any idea why?
You're trying to compute too many aggregates in one query.
Enita non sunt multiplicanda praeter necessitatem
(Latin, "entities are not to be multiplied beyond necessity")
Your tables B, C, D, and E are produced Cartesian Products against each other. Suppose the
given row in A matches:
3 rows in B
6 rows in C
4 rows in D
1 row in E
The total number of rows in the result is 3 * 6 * 4 * 1 = 72 rows. So your count(c.id_user) is 12 times what it should be, your sum(d.weight) is 18 times what it should be, etc.
The simplest remedy is to compute each of these aggregates in a separate query:
SELECT a.id, a.name, COALESCE(b.is_bool, FALSE) AS is_bool
FROM TBL_A AS a LEFT JOIN TBL_B AS b ON (b.id_tbla = a.id)
WHERE a.id = 1;
SELECT a.id, COUNT(c.id_user) AS nb_views
FROM TBL_A AS a LEFT JOIN TBL_C AS c ON (c.id_tbla = a.id)
WHERE a.id = 1;
SELECT a.id, SUM(d.weight) AS total_weight,
FROM TBL_A AS a LEFT JOIN TBL_D AS d ON (d.id_tbla = a.id)
WHERE a.id = 1;
SELECT a.id, COUNT(DISTINCT e.id_user) AS distinct_users,
FROM TBL_A AS a LEFT JOIN TBL_E AS e
ON (e.name_tbla = a.name AND e.id_user = 1)
WHERE a.id = 1;