Simplify subquery mysql to get all column in relation table - mysql

I have a table named tb_customer master.
mysql> select COSTUMER_ID, NAMA, ATTENTION, IN_DATE, IN_REF, JOB_REF, LAST_CARGO FROM tb_customer_master;
+-------------+----------------------+-------------------------+------------+--------+---------+----------------------+
| COSTUMER_ID | NAMA | ATTENTION | IN_DATE | IN_REF | JOB_REF | LAST_CARGO |
+-------------+----------------------+-------------------------+------------+--------+---------+----------------------+
| 2 | Eagletainer | Ms. Joyce Ong Chong Mei | NULL | 1234 | 123 | Lube |
| 5 | APL | Test | 21-11-2015 | sgdgfa | sgfsd | FOOD |
+-------------+----------------------+-------------------------+------------+--------+---------+----------------------+
2 rows in set (0.00 sec)
And I have table too as table master that have behavior as a report master
mysql> select REPAIR_ESTIMATE_ID, EIR_REF, COSTUMER_ID FROM tb_master_repair_estimate;
+--------------------+------------+-------------+
| REPAIR_ESTIMATE_ID | EIR_REF | COSTUMER_ID |
+--------------------+------------+-------------+
| 38 | 1545053 | 5 |
| 40 | 1545052 | 5 |
| 41 | 1545054 | 5 |
+--------------------+------------+-------------+
3 rows in set (0.00 sec)
Now, for a case, I want to subquery of them like this
mysql> SELECT
-> a.EIR_REF as EIR,
->
-> (SELECT NAMA FROM tb_customer_master c
-> WHERE a.COSTUMER_ID = c.COSTUMER_ID ) as "Name Of Customer",
->
-> (SELECT ATTENTION FROM tb_customer_master c
-> WHERE a.COSTUMER_ID = c.COSTUMER_ID ) as "ATTENTION"
->
-> FROM tb_master_repair_estimate a
->
-> WHERE a.EIR_REF = "1545052";
+------------+----------------------+-----------+
| EIR | Name Of Customer | ATTENTION |
+------------+----------------------+-----------+
| 1545052 | APL | Test |
+------------+----------------------+-----------+
1 row in set (0.00 sec)
My question is, I want to make my last query to be simply. How can I make With a one column eir, name of customer, attention, in_date, in_ref and so on to be simply, not as select one by one in subquery. It is so long command if determine it one by one.
Any suggestion so appreciated
UPDATE,
Thanks for the quickly response. Ther reason why I am using subquery is because my table of report master have many foreign key.
This is the complete tbl_report
mysql> select EIR_REF, NO_TANK, COSTUMER_ID, TANK_ID, TOTAL from tb_master_repair_estimate where EIR_REF = "1545052";
+------------+---------+-------------+---------+-------+
| EIR_REF | NO_TANK | COSTUMER_ID | TANK_ID | TOTAL |
+------------+---------+-------------+---------+-------+
| 1545052 | 7 | 5 | 1 | NULL |
+------------+---------+-------------+---------+-------+
And another table again named tb_tank_type
mysql> select * from tb_tank_type;
+---------+-----------+------------+
| TANK_ID | NAMA_TYPE | KETERANGAN |
+---------+-----------+------------+
| 1 | IM04 | NULL |
| 2 | XXXX | NULL |
+---------+-----------+------------+
2 rows in set (0.00 sec)
I am try to make them on one unite using my code like this:
mysql> SELECT
-> a.EIR_REF as EIR,
->
-> (SELECT NAMA_TYPE FROM tb_tank_type b
-> WHERE b.TANK_ID = a.TANK_ID) as "type tank",
->
-> (SELECT NAMA FROM tb_customer_master c
-> WHERE a.COSTUMER_ID = c.COSTUMER_ID ) as "Name Of Customer",
->
-> (SELECT ATTENTION FROM tb_customer_master c
-> WHERE a.COSTUMER_ID = c.COSTUMER_ID ) as "ATTENTION",
->
-> (SELECT PREFIX FROM tb_iso_tanks d
-> WHERE a.NO_TANK = d.ID_TANK) as "PREFIX",
->
-> (SELECT SERIAL_NUMBER FROM tb_iso_tanks d
-> WHERE a.NO_TANK = d.ID_TANK) as "SERIAL_NUMBER"
->
-> FROM tb_master_repair_estimate a
->
-> WHERE a.EIR_REF = "1545052";
+------------+-----------+----------------------+-----------+--------+---------------+
| EIR | type tank | Name Of Customer | ATTENTION | PREFIX | SERIAL_NUMBER |
+------------+-----------+----------------------+-----------+--------+---------------+
| 1545052 | IM04 | APL | Test | EOLU | 1234567 |
+------------+-----------+----------------------+-----------+--------+---------------+
1 row in set (0.00 sec)
Btw, thanks again.
So a case like this :
There are another table again.

JOINs are usually much faster, and simpler, than subqueries:
SELECT a.EIR_REF as EIR, c.NAMA AS `Name of Customer`, c.ATTENTION AS ATTENTION
FROM tb_master_repair_estimate a
LEFT JOIN tb_customer_master c USING (COSTUMER_ID)
WHERE a.EIR_REF = "1545052"
;

If I understand it correctly, you might use a JOIN your table tb_customer_master to your table tb_master_repair_estimate to simplify your query.
Example derived from your statement:
SELECT
a.EIR_REF as EIR,
c.NAMA as "Name Of Customer",
c.ATTENTION as "ATTENTION"
FROM tb_master_repair_estimate a
INNER JOIN tb_customer_master c ON a.COSTUMER_ID = b.COSTUMER_ID
WHERE a.EIR_REF = "1545052";
Another benefit: this is way more readable.

Looks like you would need to use a join in this instance:
select a.EIR_REF, c.NAMA, c.ATTENTION
from tb_master_repair_estimate a
join tb_customer_master c
on c.COSTUMER_ID = a.COSTUMER_ID
where a.EIR_REF = '1545052';

Related

Count rows until value in column changes in MySQL

I have same problem like in this question Count rows until value in column changes mysql
But although the issue was resolved I not understand this query. Because I have low reputation points I can't leave comment there and must open new question.
My example:
mysql> select * from example;
+----+-------+------+
| id | name | succ |
+----+-------+------+
| 1 | peter | 1 |
| 2 | bob | 1 |
| 3 | peter | 0 |
| 4 | peter | 0 |
| 5 | nick | 1 |
| 6 | bob | 0 |
| 7 | peter | 1 |
| 8 | bob | 0 |
| 9 | peter | 1 |
| 10 | peter | 1 |
+----+-------+------+
10 rows in set (0.00 sec)
I want to count successive true values for peter (descending id, and results must be 3), I know how to set query like this :
mysql> select count(succ)
from example
where id > (select max(id) from example where succ = 0);
+-------------+
| count(succ) |
+-------------+
| 2 |
+-------------+
1 row in set (0.00 sec)
But how to get results just for peter, and if it is possible to get results grouped by name, like this:
+--------+------+
|name | succ |
+--------+------+
| peter | 3 |
| bob | 0 |
| nick | 1 |
+--------+------+
Use variables to count consecutive successes (re-starting when seeing a failure), and join with a query which selects highest id per name (somewhat similar to McNets's answer)
SELECT a.name, a.count FROM (
SELECT e1.id, e1.name, e1.succ,
#count_success := IF (#prev_name = e1.name AND e1.succ = 1, #count_success + 1, e1.succ) AS `count`,
#prev_name := e1.name AS `prev_name`
FROM `example` e1, (SELECT #count_success :=0, #prev_name := NULL) init
ORDER BY e1.name, e1.id ) `a`
JOIN (SELECT MAX(id) AS `max` FROM `example` GROUP BY `name`) `b` ON a.id = b.max
One way to solve this is with a self join. Using an outer join you exclude the rows that have a matching row with a higher id value and succ = 0, and then count the rows with succ = 1 using SUM() and CASE.
Here's the query for your example:
select e1.name,
sum(case when e1.succ = 1 then 1 else 0 end) as succ
from example e1
left outer join example e2 on e2.id > e1.id
and e2.name = e1.name
and e2.succ = 0
where e2.id is null
group by e1.name
if you need the count of records
select name, count(succ) from example group by name
or if you need the sum of the succ of every person you can use
select name, sum(succ) from example group by name

MYSQL: How to fill null values in column with the previous entry?

I've got a program at work that exports to CSV but leaves blanks in the most irritable places. I want to view the carrier and destination on the same row and currently the carrier is 1 row above the destination like below:
I have a database that is like the following:
|Key|Carrier ||Destination|
|-------------------------|
| 1 | HULL2 || |
| 2 | || C14A102 |
| 3 | DONC1 || |
| 4 | || D15A012 |
What I want:
|Key|Carrier ||Destination|
|-------------------------|
| 1 | HULL2 || |
| 2 | HULL2 || C14A102 |
| 3 | DONC1 || |
| 4 | DONC1 || D15A012 |
Either that or insert a new column with the information from carrier column.
Sorry if this is confusing its confusing me to explain it!
James
Here is a solution, by cloning another table and then deleting it:
CREATE TABLE t1(Key_id INT PRIMARY KEY, Carrier CHAR(20), Destination CHAR(20));
INSERT INTO t1 VALUES(1, 'HULL2', ''),(2,'','C14A102'),(3,'DONC1',''),(4,'','D15A012');
CREATE TABLE t2 LIKE t1;
INSERT INTO t2 SELECT * FROM t1;
SELECT * FROM t1;
UPDATE t1 SET Carrier =
(
SELECT t2.Carrier
FROM t2
WHERE t2.Key_id < t1.Key_id AND t2.Carrier != ''
ORDER BY t2.Key_id DESC
LIMIT 1
)
WHERE Carrier = '';
SELECT * FROM t1;
DROP TABLE t2;
Output:
mysql> SELECT * FROM t1;
+--------+---------+-------------+
| Key_id | Carrier | Destination |
+--------+---------+-------------+
| 1 | HULL2 | |
| 2 | | C14A102 |
| 3 | DONC1 | |
| 4 | | D15A012 |
+--------+---------+-------------+
4 rows in set (0.00 sec)
mysql> UPDATE t1 SET Carrier =
-> (
-> SELECT t2.Carrier
-> FROM t2
-> WHERE t2.Key_id < t1.Key_id AND t2.Carrier != ''
-> ORDER BY t2.Key_id DESC
-> LIMIT 1
-> )
-> WHERE Carrier = '';
Query OK, 2 rows affected (0.00 sec)
Rows matched: 2 Changed: 2 Warnings: 0
mysql> SELECT * FROM t1;
+--------+---------+-------------+
| Key_id | Carrier | Destination |
+--------+---------+-------------+
| 1 | HULL2 | |
| 2 | HULL2 | C14A102 |
| 3 | DONC1 | |
| 4 | DONC1 | D15A012 |
+--------+---------+-------------+
4 rows in set (0.00 sec)
Assuming that the column 'key' can be trusted in this way, I would update with a self join where the join uses key = key+1, and then make sure it's only affecting even rows.
UPDATE tablename as even_row JOIN tablename as odd_row
ON even_row.Key = odd_row.Key + 1
SET even_row.Carrier = odd_row.Carrier
WHERE odd_row.Key % 2;

Can't create view with query containing subquery, what can I do instead?

I want to create a view with for a table in my database so I can use it in joins more easily. The table UserSettings look like this:
mysql> desc UserSettings; -- cut the output for readability
+-----------+---------------------
| Field | Type
+-----------+---------------------
| userID | bigint(20) unsigned
| timestamp | timestamp
| settingID | text
| setting | text
+-----------+---------------------
4 rows in set (0.00 sec)
In it we store every update of a users settings. Now I want to create a view for this table with the latest settings for each user. So I want the view to contain the rows marked with *:
mysql> select * from UserSettings;
+--------+---------------------+-----------------------+---------+
| userID | timestamp | settingID | setting |
+--------+---------------------+-----------------------+---------+
| 123 | 2014-04-16 12:08:30 | settings.warnings_off | 1 |
| 123 | 2014-04-16 12:18:56 | settings.warnings_off | 0 |
| 123 | 2014-04-17 06:42:08 | settings.warnings_off | 1 |
| 123 | 2014-04-17 06:49:08 | settings.warnings_off | 0 | *
| 911 | 2014-04-17 06:49:33 | settings.warnings_off | 1 | *
| 123 | 2014-04-17 10:04:12 | settings.test | fooo |
| 123 | 2014-04-17 10:04:22 | settings.test | bar | *
| 911 | 2014-04-17 10:07:40 | settings.test | bar | *
+--------+---------------------+-----------------------+---------+
8 rows in set (0.00 sec)
I can do this with a query containing a subquery:
mysql> SELECT us.*
-> FROM UserSettings us INNER JOIN (
-> SELECT userID, settingID, max(timestamp) as timestamp
-> from UserSettings
-> group by userID, settingID) ts ON ts.userID = us.userID AND
-> ts.settingID = us.settingID AND ts.timestamp = us.timestamp;
+--------+---------------------+-----------------------+---------+
| userID | timestamp | settingID | setting |
+--------+---------------------+-----------------------+---------+
| 123 | 2014-04-17 10:04:22 | settings.test | bar |
| 123 | 2014-04-17 06:49:08 | settings.warnings_off | 0 |
| 911 | 2014-04-17 10:07:40 | settings.test | bar |
| 911 | 2014-04-17 06:49:33 | settings.warnings_off | 1 |
+--------+---------------------+-----------------------+---------+
4 rows in set (0.00 sec)
However I get the error ERROR 1349 (HY000): View's SELECT contains a subquery in the FROM clause when using the above query in CREATE VIEW CurrentUserSettings AS <THE QUERY ABOVE>.
Is there a way to create a view of the UserSettings table that show the same data as the query above? Perhaps using HAVING in some way?
To expand my comment above:-
CREATE VIEW latest_timestamp AS SELECT userID, settingID, max(timestamp) as timestamp
from UserSettings
group by userID, settingID;
CREATE VIEW latest_timestamp AS SELECT us.*
FROM UserSettings us
INNER JOIN latest_timestamp ts
ON ts.userID = us.userID AND
ts.settingID = us.settingID
AND ts.timestamp = us.timestamp;
Another option is to (ab)use the GROUP_CONCAT function. I am not that keen on doing this and there are issues if the fields can contain NULL, plus is entails converting integers to character fields.
Assuming the fields you want are called field1, field2 and field3 then:-
SELECT us.userID,
us.settingID,
SUBSTRING_INDEX(GROUP_CONCAT(us.field1 ORDER BY timestamp DESC SEPARATOR '|~|~'), '|~|~', 1),
SUBSTRING_INDEX(GROUP_CONCAT(us.field2 ORDER BY timestamp DESC SEPARATOR '|~|~'), '|~|~', 1),
SUBSTRING_INDEX(GROUP_CONCAT(us.field3 ORDER BY timestamp DESC SEPARATOR '|~|~'), '|~|~', 1)
FROM UserSettings us
GROUP BY us.userID, us.settingID;
This works by concatenating all the values for a field up into one, ordered by the timestamp descending and with a separator between them (something that will not be in any of the fields), then using SUBSTRING_INDEX to get the field up to the first delimiter.
Like Kickstart advises, do like this
First, create the subquery in a view
CREATE VIEW subqueryview as
SELECT userID, settingID, max(timestamp) as timestamp
from UserSettings group by userID, settingID;
Then you can create the view using the view.
CREATE VIEW finalview AS
SELECT us.*
FROM UserSettings us
-- Here you now use the view created.
INNER JOIN subqueryview ts
ON ts.userID = us.userID
AND ts.settingID = us.settingID
AND ts.timestamp = us.timestamp;

Inexplicably slow query in MySQL

Given this result-set:
mysql> EXPLAIN SELECT c.cust_name, SUM(l.line_subtotal) FROM customer c
-> JOIN slip s ON s.cust_id = c.cust_id
-> JOIN line l ON l.slip_id = s.slip_id
-> JOIN vendor v ON v.vend_id = l.vend_id WHERE v.vend_name = 'blahblah'
-> GROUP BY c.cust_name
-> HAVING SUM(l.line_subtotal) > 49999
-> ORDER BY c.cust_name;
+----+-------------+-------+--------+---------------------------------+---------------+---------+----------------------+------+----------------------------------------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-------+--------+---------------------------------+---------------+---------+----------------------+------+----------------------------------------------+
| 1 | SIMPLE | v | ref | PRIMARY,idx_vend_name | idx_vend_name | 12 | const | 1 | Using where; Using temporary; Using filesort |
| 1 | SIMPLE | l | ref | idx_vend_id | idx_vend_id | 4 | csv_import.v.vend_id | 446 | |
| 1 | SIMPLE | s | eq_ref | PRIMARY,idx_cust_id,idx_slip_id | PRIMARY | 4 | csv_import.l.slip_id | 1 | |
| 1 | SIMPLE | c | eq_ref | PRIMARY,cIndex | PRIMARY | 4 | csv_import.s.cust_id | 1 | |
+----+-------------+-------+--------+---------------------------------+---------------+---------+----------------------+------+----------------------------------------------+
4 rows in set (0.04 sec)
I'm a bit baffled as to why the query referenced by this EXPLAIN statement is still taking about a minute to execute. Isn't it true that this query only has to search through 449 rows? Anyone have any idea as to what could be slowing it down so much?
I think the having sum() is the root of all evil. This forces to mysql to make two joins (from customer to slip and then to line) to get the value of the sum. After this it has to retrieve all the data to properly filter by the sum() value to get a meaningful result.
It might be optimized to the following and probably get better response times:
select c.cust_name,
grouping.line_subtotal
from customer c join
(select c.cust_id,
l.vend_id,
sum(l.line_subtotal) as line_subtotal
from slip s join line l on s.slip_id = l.slip_id
group by c.cust_id, l.vend_id) grouping
on c.cust_id = grouping.cust_id
join vendor v on v.vend_id = grouping.vend_id
where v.vend_name = 'blablah'
and grouping.line_subtotal > 499999
group by c.cust_name
order by c.cust_name;
In other words, create a sub-select that does all the necessary grouping before making the real query.
You can run your select vendor query first, and then join the results with the rest:
SELECT c.cust_name, SUM(l.line_subtotal) FROM customer c
-> JOIN slip s ON s.cust_id = c.cust_id
-> JOIN line l ON l.slip_id = s.slip_id
-> JOIN (SELECT * FROM vendor WHERE vend_name='blahblah') v ON v.vend_id = l.vend_id
-> GROUP BY c.cust_name
-> HAVING SUM(l.line_subtotal) > 49999
-> ORDER BY c.cust_name;
Also, do vend_name and/or cust_name have an index? That might be an issue here.

How to NULL the repeating column data?

Is there a way to NULL the values that are known to be the same in the rest of the dataset?
mysql> SELECT
-> `p1`.`id`,
-> `p1`.`name`,
-> `pp1`.`name` `product_property_name`,
-> `pp1`.`value` `product_property_name`
-> FROM
-> `product` `p1`
-> INNER JOIN
-> `product_property` `pp1`
-> ON
-> `p1`.`id` = `pp1`.`product_id`;
+----+------+-----------------------+-----------------------+
| id | name | product_property_name | product_property_name |
+----+------+-----------------------+-----------------------+
| 1 | Tar | foo | bar |
| 1 | Tar | foo1 | bar1 |
| 1 | Tar | foo2 | bar2 |
| 2 | Qaz | too | doo |
| 2 | Qaz | too1 | doo1 |
+----+------+-----------------------+-----------------------+
5 rows in set (0.00 sec)
In this case product is returned more than once because of INNER JOIN with product_property. I only need the first row of every product to group the results.
Therefore, the desired output:
+----+------+-----------------------+-----------------------+
| id | name | product_property_name | product_property_name |
+----+------+-----------------------+-----------------------+
| 1 | Tar | foo | bar |
| 1 | NULL | foo1 | bar1 |
| 1 | NULL | foo2 | bar2 |
| 2 | Qaz | too | doo |
| 2 | NULL | too1 | doo1 |
+----+------+-----------------------+-----------------------+
This would allow to dramatically cut the memory utilisation, esp. when grouping large datasets.
You can do what you want using variables:
SELECT `p1`.`id`,
(case when name <> #prevname then `p1`.`name` end) as name,
`pp1`.`name` as `product_property_name`,
`pp1`.`value` as `product_property_name`,
#prevname := name
FROM `product` `p1` INNER JOIN
`product_property` `pp1` cross join
(select #prevname := '') const
ON `p1`.`id` = `pp1`.`product_id`;
Although this works in practice, it is not guaranteed to work, because MySQL does not guarantee the ordering of the evaluation of columns in a select statement (so in theory the #prevname := name could happen first).
However, you might consider using group_concat() instead, and dispensing with the multiple rows per property:
SELECT `p1`.`id`,
`p1`.`name`,
group_concat(pp1.name, ', ') as product_property_names,
group_concat(pp1.value, ', ') as product_property_values
FROM `product` `p1` INNER JOIN
`product_property` `pp1`
ON `p1`.`id` = `pp1`.`product_id`
group by p1.id, p1.name;
Or, as a single pair:
SELECT `p1`.`id`,
`p1`.`name`,
group_concat(concat(pp1.name, '=', pp1.value), '; ') as product_property_pairs
FROM `product` `p1` INNER JOIN
`product_property` `pp1`
ON `p1`.`id` = `pp1`.`product_id`
group by p1.id, p1.name;
The latter two should reduce the space more than the first solution.