So i have this table:
req_num
number
status
order
1254
5
7
1
1254
4
7
2
1254
6
7
3
1246
7
8
1
1246
5
8
2
1246
3
8
3
1253
1
9
1
1253
4
7
2
1253
7
4
3
1321
7
4
1
1321
8
4
2
1321
7
4
3
1321
8
4
4
and i need to know if theres a way to generate a query that get a column like this based on the highest value in the "order" column.
req_num
number
status
order
last_req
1254
5
7
1
not_last
1254
4
7
2
not_last
1254
6
7
3
last
1246
7
8
1
not_last
1246
5
8
2
not_last
1246
3
8
3
last
1253
1
9
1
not_last
1253
4
7
2
not_last
1253
7
4
3
last
1321
7
4
1
not_last
1321
8
4
2
not_last
1321
7
4
3
not_last
1321
8
4
4
last
this table doesnt have any ids and i can't do inserts on it, any ideas?
You can use window functions to accomplish this - you can partition your data by req_num and then take the item in each partition with the highest order.
The docs: https://dev.mysql.com/doc/refman/8.0/en/window-functions-frames.html
The result will be something like (untested):
SELECT rec_num, number, status, LAST_VAL(order) OVER w AS 'last',
FROM my_table
WINDOW w AS (PARTITION BY req_num ORDER BY order);
That won't give you exactly what you want as all the rows will now include the highest order num for that rec_num. But you can then easily compare the order and last columns to create your last and not_last identifiers.
SELECT rec_num, number, status, LAST_VAL(order) OVER w AS 'last',
IF(`order` = `last`, 'last', 'not_last') AS last_req
FROM my_table
WINDOW w AS (PARTITION BY req_num ORDER BY order);
The window functions are remarkably powerful for situations like this.
In order to solve this problem, you need to look for the maximum value in the order field, grouped by req_num.
If your version is MySQL 8.0, you can use the ROW_NUMBER window function to assign an ordinal value to that column in a descendent way, so that your max value has ranking = 1. Afterwards you check with an IF statement if your column has the value 1 or not, and replace with 'last' and 'not_last' accordingly.
SELECT *,
IF(ROW_NUMBER() OVER(PARTITION BY req_num_ ORDER BY order_ DESC) = 1,
'last',
'not_last') AS last_req
FROM tab
Demo here.
If your version is MySQL 5.X, you can first use the MAX aggregation function in a subquery containing only req_num and order, by grouping on the former field and aggregating on the latter one. Then you can LEFT JOIN the original table with this result set, and the order values who will have assigned NULL will be the ones that are 'not_last'.
SELECT tab.req_num,
tab.status,
tab.order,
IF(max_values.max_order, 'last', 'not_last') AS last_req
FROM tab
LEFT JOIN (SELECT req_num_,
MAX(order) AS max_order
FROM tab
GROUP BY req_num ) max_values
ON tab.req_num = max_values.req_num
AND tab.order = max_values.max_order
Demo here.
Related
Imagine I have the following tables:
Numbers PK
1
2
3
4
5
6
7
8
9
10
Numbers FK 1
Numbers FK 2
1
1
2
1
3
1
4
1
5
1
6
1
7
1
8
1
9
1
10
1
8
8
10
4
7
3
4
9
1
6
3
9
4
6
5
6
I have the following tables: "Numbers PK" as Primary key and another 2 tables that are related one with each other that are foreign keys of Numbers.
I am trying to make a query to select the number(s) from the table "NumbersFK2" that are related with all the numbers of "Numbers PK".
As you can see in this example the solution would be 1 as 1 is related with 1-10 in the tables "Numbers FK1" and "Numbers FK2"
I have tried to solve and after some days I need some help as I don't know how could I do it. I appreciate the help. Thanks
We use dense_rank() to count the Numbers_PK in case they're not consecutive. Then we left join, group by and count(distinct Numbers_PK).
with t3 as (
select Numbers_PK
,dense_rank() over(order by Numbers_PK) as dns_rnk
from t
)
select Numbers_FK_2
from t3 left join t2 on t2.Numbers_FK_1 = t3.Numbers_PK
group by Numbers_FK_2
having count(distinct Numbers_PK) = max(dns_rnk)
Numbers_FK_2
1
Fiddle
This question already has an answer here:
How to write these complex MySQL queries?
(1 answer)
Closed 1 year ago.
I am a beginner at databases. I need to write some SQL queries.
The tables are:
Expedition(id, number, id_captain, id_ship, id_heros)
City(id, name)
Heros(id, family_name, first_name)
Step(id, index, id_expedition, id_city)
sample data :
'Table expedition'
id
number
id_captain
id_ship
id_hero
1
1
1
10
8
2
2
2
1
5
3
3
1
8
3
4
4
10
9
6
5
5
5
7
4
6
6
6
5
4
7
7
7
3
7
8
8
8
2
8
9
9
9
1
3
10
10
1
4
2
11
11
6
3
1
12
12
8
6
1
13
13
5
8
6
14
14
4
9
9
15
15
3
10
4
16
16
10
2
2
17
17
9
3
3
18
18
8
7
7
19
19
9
8
10
20
20
7
2
2
table 'heros'
id
family_name
first_name
1
familyname1
firstname1
2
familyname2
firstname2
3
familyname3
firstname3
4
familyname4
firstname4
5
familyname5
firstname5
6
familyname6
firstname6
7
familyname7
firstname7
8
familyname8
firstname8
9
familyname9
firstname9
10
familyname10
firstname10
query1: The family (based on the family name) with the least travelling (the fewest cities different crossings).
i have done this for the first query:
select expedition.id, id_hero, heros.family_name as Famille_expedition, count(distinct id_city) as city_count
from expedition, step, heros
where expedition.id=step.id and expedition.id_hero=heros.id
group by id_hero
having city_count =
(select count(distinct id_city) as min_city_count
from expedition, step
where expedition.id=step.id
group by id_hero
order by min_city_count asc
limit 1);
query2: The average of cities crossed by an expedition
I have no idea how to answer the second one.
Well, first ask yourself what information do you need to answer your question?
From your question, I'd say the average number of crossings is just the sum of all entries in the steps table, divided by the number of expeditions, since in each step, one city is visited and the average of all visits is what you are looking for:
SELECT (
(SELECT COUNT(s.id_city)
FROM step AS s) /
(SELECT COUNT(e.id)
FROM expedition AS e) ) AS total_average__cities
That being said, it depends on how exactly you define number of cities and crossing. Imagine the following example data for the table step:
id
idx
id_expedition
id_city
1
1
1
1
2
2
1
5
3
3
1
3
4
1
2
5
5
2
2
9
6
1
3
8
7
2
3
5
8
3
3
9
9
4
3
5
10
5
3
8
The table lists the steps for three expeditions. Expedition 1 goes from one city via another to a third. Expedition 2 goes directly from one city to another. And expedition 3 goes through several cities and visits one city twice along the way and also returns to the city that it started in.
The average number of cities over all these steps is (3 + 2 + 5 [cities in all steps]) / 3 [expeditions] = 3.3333. That is the result of the above query.
Now, if you define number of cities as meaning unique cities for each expedition, expedition 3 only visits 3 cities instead of 5. Then your average calculates as (3 + 2 + 3 [unique cities/expedition in all steps]) / 3 [expeditions] = 2.6666. The according query needs to count the distinct cities within each expedition before building the average:
SELECT (
(SELECT SUM(cnt) FROM (SELECT COUNT(DISTINCT s.id_city) AS cnt
FROM step AS s
GROUP BY s.id_expedition) t) /
(SELECT COUNT(e.id)
FROM expedition AS e) ) AS total_average__cities
Now, if you define crossing as only covering cities along the way, expedition 1 only crosses 1 city and expedition 2 crosses none at all.
Then your query also needs to look differently. You need to filter the all cities to exclude the first and the last for each expedition. The subquery could look like this:
SELECT s.* FROM step s
JOIN ( SELECT id_expedition,
MAX(idx) AS max_idx,
MIN(idx) AS min_idx
FROM step s
GROUP BY id_expedition) minmax
ON s.id_expedition = minmax.id_expedition
AND s.idx > minmax.min_idx
AND s.idx < minmax.max_idx
So for the case that you want the number of cities crossed excluding start and stop, your average would be computed as (1 + 0 + 3 [intermediate cities in all steps]) / 3 [expeditions] = 1.3333. The according query would be
SELECT (
(SELECT COUNT(s.id_city)
FROM step s
JOIN ( SELECT id_expedition,
MAX(idx) as max_idx,
MIN(idx) as min_idx
FROM step s
GROUP BY id_expedition) minmax
ON s.id_expedition = minmax.id_expedition
AND s.idx > minmax.min_idx
AND s.idx < minmax.max_idx) /
(SELECT COUNT(e.id)
FROM expedition AS e) ) AS total_average__cities
Finally, in case you want to both exclude start and stop and only want to count unique cities, your average would be computed as (1 + 0 + 2 [unique intermediate cities in all steps]) / 3 [expeditions] = 1. The following query combines the two approaches from above:
SELECT (
(SELECT SUM(cnt) FROM (SELECT COUNT(DISTINCT id_city) AS cnt
FROM step s
JOIN ( SELECT id_expedition,
MAX(idx) AS max_idx,
MIN(idx) AS min_idx
FROM step s
GROUP BY id_expedition) minmax
ON s.id_expedition = minmax.id_expedition
AND s.idx > minmax.min_idx
AND s.idx < minmax.max_idx
GROUP BY s.id_expedition) t) /
(SELECT COUNT(e.id)
FROM expedition AS e) ) AS total_average_cities
You can test all these queries in this db<>fiddle.
I have a database with a table called BOOKINGS containing the following values
main-id place-id start-date end-date
1 1 2018-8-1 2018-8-8
2 2 2018-6-6 2018-6-9
3 3 2018-5-5 2018-5-8
4 4 2018-4-4 2018-4-5
5 5 2018-3-3 2018-3-10
5 1 2018-1-1 2018-1-6
4 2 2018-2-1 2018-2-10
3 3 2018-3-1 2018-3-28
2 4 2018-4-1 2018-4-6
1 5 2018-5-1 2018-5-15
1 3 2018-6-1 2018-8-8
1 4 2018-7-1 2018-7-6
1 1 2018-8-1 2018-8-18
1 2 2018-9-1 2018-9-3
1 5 2018-10-1 2018-10-6
2 5 2018-11-1 2018-11-5
2 3 2018-12-1 2018-12-25
2 2 2018-2-2 2018-2-19
2 4 2018-4-4 2018-4-9
2 1 2018-5-5 2018-5-23
What I need to do is for each main-id I need to find the largest total number of days for every place-id. Basically, I need to determine where each main-id has spend the most time.
This information must then be put into a view, so unfortunately I can't use temporary tables.
The query that gets me the closest is
CREATE VIEW `MOSTTIME` (`main-id`,`place-id`,`total`) AS
SELECT `BOOKINGS`.`main-id`, `BOOKINGS`.`place-id`, SUM(DATEDIFF(`end-date`, `begin-date`)) AS `total`
FROM `BOOKINGS`
GROUP BY `BOOKINGS`.`main-id`,`RESERVATION`.`place-id`
Which yields:
main-id place-id total
1 1 24
1 2 18
1 5 5
2 1 2
2 2 20
2 4 9
3 1 68
3 2 24
3 3 30
4 1 5
4 2 10
4 4 1
5 1 19
5 2 4
5 5 7
What I need is then the max total for each distinct main-id:
main-id place-id total
1 1 24
2 2 20
3 1 68
4 2 10
5 1 19
I've dug through a large amount of similar posts that recommend things like self joins; however, due to the fact that I have to create the new field total using an aggregate function (SUM) and another function (DATEDIFF) rather than just querying an existing field, my attempts at implementing those solutions have been unsuccessful.
I am hoping that my query that got me close will only require a small modification to get the correct solution.
Having hyphen character - in column name (which is also minus operator) is a really bad idea. Do consider replacing it with underscore character _.
One possible way is to use Derived Tables. One Derived Table is used to determine the total on a group of main id and place id. Another Derived Table is used to get maximum value out of them based on main id. We can then join back to get only the row corresponding to the maximum value.
CREATE VIEW `MOSTTIME` (`main-id`,`place-id`,`total`) AS
SELECT b1.main_id, b1.place_id, b1.total
FROM
(
SELECT `main-id` AS main_id,
`place-id` AS place_id,
SUM(DATEDIFF(`end-date`, `begin-date`)) AS total
FROM BOOKINGS
GROUP BY main_id, place_id
) AS b1
JOIN
(
SELECT dt.main_id, MAX(dt.total) AS max_total
FROM
(
SELECT `main-id` AS main_id,
`place-id` AS place_id,
SUM(DATEDIFF(`end-date`, `begin-date`)) AS total
FROM BOOKINGS
GROUP BY main_id, place_id
) AS dt
GROUP BY dt.main_id
) AS b2
ON b1.main_id = b2.main_id AND
b1.total = b2.max_total
MySQL 8+ solution would be utilizing the Row_Number() functionality:
CREATE VIEW `MOSTTIME` (`main-id`,`place-id`,`total`) AS
SELECT b.main_id, b.place_id, b.total
FROM
(
SELECT dt.main_id,
dt.place_id,
dt.total
ROW_NUMBER() OVER (PARTITION BY dt.main_id
ORDER BY dt.total DESC) AS row_num
FROM
(
SELECT `main-id` AS main_id,
`place-id` AS place_id,
SUM(DATEDIFF(`end-date`, `begin-date`)) AS total
FROM BOOKINGS
GROUP BY main_id, place_id
) AS dt
GROUP BY dt.main_id
) AS b
WHERE b.row_num = 1
Currently I am honestly at loss what I am doing wrong. It is a rather simple query I think.
Tables:
operations:
id processedon clientid
1 2018-01-01 9
2 2018-03-16 9
3 2018-04-21 9
4 2018-04-20 9
5 2018-05-09 9
items:
id operation_id quantity unitprice
1 1 10 2
2 1 5 3
3 2 20 4
4 3 10 2
5 4 8 4
6 4 10 4
7 5 2 2
The expected result of the operation/query is:
month total_value
1 35
3 80
4 92
5 4
That is quantity * unitprice based. For some reason, it only returns month=4
SELECT
month(`operations`.`processedon`) AS `month`,
SUM((`items`.`quantity` * `items`.`unitprice`)) AS `total_value`
FROM `items`
INNER JOIN `operations` ON (`items`.`operation_id` = `operations`.`id`)
GROUP BY 'month'
ORDER BY 'month'
According to the info provided the join should be
INNER JOIN operations ON items.operation_id = operations.id
Eg
SELECT
month(`operations`.`processedon`) AS `month`,
SUM((`items`.`quantity` * `items`.`unitprice`)) AS `total_value`
FROM `items`
INNER JOIN `operations` ON `items`.`operation_id` = `operations`.`id`
GROUP BY month(`operations`.`processedon`)
ORDER BY `month`
There is no efficiency gain by using a column alias in the group by clause, I prefer to avoid using them except perhaps in the order by clause.
The following query will give you the required answer
SELECT
month(`operations`.`processedon`) AS `month`,
SUM((`items`.`quantity` * `items`.`unitprice`)) AS `total_value`
FROM items
INNER JOIN operations ON (items.operation_id = operations.id)
GROUP BY month(operations.processedon)
ORDER BY month(operations.processedon)
You need to specify month correctly since it is not an existing column.
You'll get the following result
month total_value
1 35
3 80
4 92
5 4
I'm wondering if there is a way to do a numeric sort when the number is a string. Take a look at example:
The query:
SELECT * FROM t_tables t order by id_string asc
This is the list from DB:
13
4
6
8
10
1
3
2
5
12
7
9
11
I need to extract it in this way:
1
2
3
4
5
6
7
8
9
10
11
12
13
But as result I get this:
1
10
11
12
13
2
3
4
5
6
7
8
9
If you know that you always have numeric values in the column you can use the CAST() operator. Also have a look at this question.
SELECT * FROM t_tables t ORDER BY CAST(id_string AS UNSIGNED) ASC
SELECT * FROM t_tables t order by cast(id_string as signed integer) asc;
Hope it helps:
SELECT * FROM t_tables t order by cast(id_string as unsigned)