mySql - updating by comparing rows in same table - mysql

I want to update a column by comparing each row to all other rows in the table but I cant figure out how to distinguish the column names in the row being updated with the rows being searched through.
Here's a simplified example...
people:
+--------+-----+----------------+
| name | age | nameClosestAge |
+--------+-----+----------------+
| alice | 20 | |
| bob | 30 | |
| clive | 22 | |
| duncan | 24 | |
+--------+-----+----------------+
To fill in the 'nameClosestAge' column with the name of the person that is closest in age to each person, you could do this...
create temporary table peopleTemp like people;
insert into peopleTemp select * from people;
update people set nameClosestAge =
(select name from peopleTemp where people.name != peopleTemp.name
order by abs(people.age - peopleTemp.age) asc limit 1);
Which produces this....
+--------+-----+----------------+
| name | age | nameClosestAge |
+--------+-----+----------------+
| alice | 20 | clive |
| bob | 30 | duncan |
| clive | 22 | alice |
| duncan | 25 | clive |
+--------+-----+----------------+
Surely there is a way to do this without creating a duplicate table.
I'm looking for the most efficient method here as I have a very large table and its taking too long to update.
I'm using mySql with PHP.

You could perform this with just one sub-query and no temp table.
SELECT name, age, (
SELECT name
FROM people
WHERE name != ppl.name
ORDER BY ABS( people.age - ppl.age )
LIMIT 1
) AS nameClosestAge
FROM people AS ppl;
Checked and works :)
EDIT: If you want to be able to work with the calc'ed row, you can use a view;
CREATE VIEW people_close AS
SELECT name, age, (
SELECT name
FROM people
WHERE name != ppl.name
ORDER BY ABS( people.age - ppl.age )
LIMIT 1
) AS nameClosestAge
FROM people AS ppl;
You can't update the calculated field but can query against it easily.

Related

MySQL - How to order duplicate rows in a key value pair table based on multiple columns?

So I have the following key/value pair table, where users submit data through a form and each question on the form is added to the table here as an individual row. Submission_id identifies each form submission.
+----+---------------+--------------+--------+
| id | submission_id | key | value |
+----+---------------+--------------+--------+
| 1 | 10 | manufacturer | Apple |
| 2 | 10 | model | 5s |
| 3 | 10 | firstname | Paul |
| 4 | 15 | manufacturer | Apple |
| 5 | 15 | model | 5s |
| 6 | 15 | firstname | Paul |
| 7 | 20 | manufacturer | Apple |
| 8 | 20 | model | 5s |
| 9 | 20 | firstname | Andrew |
+----+---------------+--------------+--------+
From the data above you can see that the submissions with id of 10 and 15 both have the same values (just different submission id). This is basically because a user has submitted the same form twice and so is a duplicate.
Im trying to find a way to order these table where the any duplicate submissions appear together in order. Given the above table I am trying to build a query that gives me the result as below:
+---------------+
| submission_id |
+---------------+
| 10 |
| 15 |
| 20 |
+---------------+
So I want to check to see if a submission where the manufacturer, model and firstname keys have the same value. If it does then these get the submission id and place them adjacently in the result. In the actual table there are other keys, but I only want to match duplicates based on these 3 keys (manufacturer, model, firstname).
I’ve been going back and forth to the drawing board quite some time now and have tried looking for some possible solutions but cannot get something reliable.
That's not a key value table. It's usually called an Entity-Attribute-Value table/relation/pattern.
Looking at the problem, it would be trivial if the table were laid out in conventional 1st + 2nd Normal form - you just do a join on the values, group by those and take a count....
SELECT manufacturer, model, firstname, COUNT(DISTINCT submission_id)
FROM atable
GROUP BY manufacturer, model, firstname
HAVING COUNT(DISTINCT submission_id)>1;
Or a join....
SELECT a.manufacturer, a.model, a.firstname
, a.submission_id, b.submission_id
FROM atable a
JOIN atable b
ON a.manufacturer=b.manufacturer
AND a.model=b.model
AND a.firstname=b.firstname
WHERE a.submission_id<b.submission_id
;
Or using sorting and comparing adjacent rows....
SELECT *
FROM
(
SELECT #prev.submission_id AS prev_submission_id
, #prev.manufacturer AS prev_manufacturer
, #prev.model AS prev_model
, #prev.firstname AS pref_firstname
, a.submission_id
, a.manufacturer
, a.model
, set #prev.submission_id:=a.submission_id as currsid
, set #prev.manufacturer:=a.manufacturer as currman
, set #prev.model:=a.model as currmodel
, set #prev.firstname=a.forstname as currname
FROM atable
ORDER BY manufacturer, model, firstname, submission_id
)
WHERE prev_manufacturer=manufacturer
AND prev_model=model
AND prev_firstname=firstname
AND prev_submission_id<>submission_id;
So the solution is to simply make your data look like a normal relation....
SELECT ilv.values
, COUNT(ilv.submission_id)
, GROUP_CONCAT(ilv.submission_id)
FROM
(SELECT a.submission_id
, GROUP_CONCAT(CONCAT(a.key, '=',a.value)) AS values
FROM atable a
GROUP BY a.submission_id
) ilv
GROUP BY ilv.values
HAVING COUNT(ilv.submission_id)>1;
Hopefully the join and sequence based solutions should now be obvious.

How to find the range where the given number in mysql

I want to check what range/level the number is in. I have the table of buy and pay. Only I can thinking of is about between. But here, it is different because the column is not only min and max.
pay_level
| id | type | buy1 | pay1 | buy2 | pay2 | buy3 | pay3 |
|----|------|------|------|------|------|------|------|
| 1 | p1 | 10 | 100 | 20 | 80 | 30 | 70 |
|----|------|------|------|------|------|------|------|
| 2 | p2 | 10 | 100 | 20 | 80 | 30 | 70 |
|----|------|------|------|------|------|------|------|
| 3 | p3 | 5 | 500 | 10 | 400 | 30 | 300 |
|----|------|------|------|------|------|------|------|
Ok, according to the table above. My goal is to see how much cost is the incoming order.
For example.
A order p1 for 12 unit. So the price per unit is 100. Because he is buying between buy1 and buy2
B order p1 for 15 units. Then he got 100 per unit as well as A.
C order p1 for 25 units. He got 70 because it's in between pay2 and pay3.
What I can thinking of is to compare 2 columns where the order in between. So my code is:
select * from pay_level where order between buy1 and buy2 and type='p1'
But the problem is occurs when the order is more than 20 (of buy2). I know my English is not good to explain this clear enough. Hope you understand.
First normalise your schema design...
DROP TABLE IF EXISTS wilf;
CREATE TABLE wilf
(id INT AUTO_INCREMENT PRIMARY KEY
,type INT NOT NULL
,x INT NOT NULL
,buy INT NOT NULL
,pay INT NOT NULL
);
INSERT INTO wilf VALUES
(1,1,1,10,100),
(2,2,1,10,100),
(3,3,1, 5,500),
(4,1,2,20, 80),
(5,2,2,20, 80),
(6,3,2,10,400),
(7,1,3,30, 70),
(8,2,3,30, 70),
(9,3,3,30,300);
...and then your queries become trivial...
SELECT pay FROM wilf WHERE type = 1 AND buy < 12 ORDER BY id DESC LIMIT 1;
+-----+
| pay |
+-----+
| 100 |
+-----+
(And C should have got 80)
You'll need a CASE expression to navigate this one since you can't dynamically refer to a database object (table, column, etc) in your sql.
I think something like the following would get you in the ballpark:
SELECT
CASE WHEN order BETWEEN buy1 and buy2 THEN pay1
WHEN order BETWEEN buy2 and buy3 THEN pay2
WHEN order > buy3 THEN pay3 END as cost
FROM pay_level
WHERE type = 'p1'

List Last record of each item in mysql

Each item(item is produced by Serial) in my table has many record and I need to get last record of each item so I run below code:
SELECT ID,Calendar,Serial,MAX(ID)
FROM store
GROUP BY Serial DESC
it means it must show a record for each item which in that record all data of columns be for last record related to each item but the result is like this:
-------------------------------------------------------------+
ID | Calendar | Serial | MAX(ID) |
-------------------------------------------------------------|
7031053 | 2016-05-14 14:05:14 79.5 | N10088 | 7031056 |
7053346 | 2016-05-14 15:17:28 79.8 | N10078 | 7053346 |
7051349 | 2016-05-14 15:21:29 86.1 | J20368 | 7051349 |
7059144 | 2016-05-14 15:50:27 89.6 | J20367 | 7059144 |
7045551 | 2016-05-14 15:15:15 89.2 | J20366 | 7045551 |
7056243 | 2016-05-14 15:25:34 85.2 | J20358 | 7056245 |
7042652 | 2016-05-14 15:18:33 83.9 | J20160 | 7042652 |
7039753 | 2016-05-14 11:48:16 87 | J20158 | 7039753 |
7036854 | 2016-05-14 15:18:35 87.5 | J20128 | 7036854 |
7033955 | 2016-05-14 15:20:45 83.4 | 9662 | 7033955 |
-------------------------------------------------------------+
the problem is why for example in record related to Serial N10088 the ID is "7031053", but MAX(ID) is "7031056"? or also for J20358?
each row must show last record of each item but in my output it is not true!
If you want the row with the max value, then you need a join or some other mechanism.
Here is a simple way using a correlated subquery:
select s.*
from store s
where s.id = (
select max(s2.id)
from store s2
where s2.serial = s.serial
);
You query uses a (mis)feature of SQL Server that generates lots of confusion and is not particularly helpful: you have columns in the select that are not in the group by. What value do these get?
Well, in most databases the answer is simple: the query generates an error as ANSI specifies. MySQL pulls the values for the additional columns from indeterminate matching rows. That is rarely what the writer of the query intends.
For performance, add an index on store(serial, id).
try this one.
SELECT MAX(id), tbl.*
FROM store tbl
GROUP BY Serial
You can try with this also...
SELECT ID,Calendar,Serial
FROM store s0
where ID = (
SELECT MAX(id)
FROM store s1
WHERE s1.serial = s0.serial
);

Only return an ordered subset of the rows from a joined table

Given a structure like this in a MySQL database
#data_table
(id) | user_id | time | (...)
#relations_table
(id) | user_id | user_coach_id | (...)
we can select all data_table rows belonging to a certain user_coach_id (let's say 1) with
SELECT rel.`user_coach_id`, dat.*
FROM `relations_table` rel
LEFT JOIN `data_table` dat ON rel.`uid` = dat.`uid`
WHERE rel.`user_coach_id` = 1
ORDER BY val.`time` DESC
returning something like
| user_coach_id | id | user_id | time | data1 | data2 | ...
| 1 | 9 | 4 | 15 | foo | bar | ...
| 1 | 7 | 3 | 12 | oof | rab | ...
| 1 | 6 | 4 | 11 | ofo | abr | ...
| 1 | 4 | 4 | 5 | foo | bra | ...
(And so on. Of course time are not integers in reality but to keep it simple.)
But now I would like to query (ideally) only up to an arbitrary number of rows from data_table per distinct user_id but still have those ordered (i.e. newest first). Is that even possible?
I know I can use GROUP BY user_id to only return 1 row per user, but then the ordering doesn't work and it seems kind of unpredictable which row will be in the result. I guess it's doable with a subquery, but I haven't figured it out yet.
To limit the number of rows in each GROUP is complicated. It is probably best done with an #variable to count, plus an outer query to throw out the rows beyond the limit.
My blog on Groupwise Max gives some hints of how to do such.

retrieving data from two mysql tables where the second table depends on the first

I have two MySql tables:
users(id_user, name, age, gender ).
ways(#id_user,id_way, start, end, date).
What I want is to retrieve all the ways with their corresponding users details.
So my result would be like this:
id_way | start | end | date | id_user | name | age | gender
---------------------------------------------------------------------------
2 | place1 | place2 | 12/06/2013 | 145 | john | 28 | m
Have you tried JOIN?
SELECT ways.id_way, ways.start, ways.end, ways.date, users.*
FROM ways JOIN users USING (id_user)