Median value from table with number:count format - mysql

Given a table
+------------+-----------+
| Number | Count |
+------------+-----------+
| 0 | 7 |
+------------+-----------+
| 1 | 1 |
+------------+-----------+
| 2 | 3 |
+------------+-----------+
| 4 | 1 |
+------------+-----------+
Which is representing such a number sequence: 0, 0, 0, 0, 0, 0, 0, 1, 2, 2, 2, 4
find the median number, in this case it's 0, with sql. You will need to run this query in hive (qubole)
Thoughts?

There is a fairly straightforward solution in Hive. You'll need this UDF here. Essentially, you want to un-aggregate your count data and then percentile it.
Query:
add jar /path/to/jar/brickhouse-0.7.1.jar;
create temporary function numeric_range as 'brickhouse.udf.collect.NumericRange';
select percentile(number, 0.50) median
from (
select number
from db.table
lateral view numeric_range(count) n1 as n) x
The inner query will produce
0
0
0
0
0
0
0
1
2
2
2
4
Then you can just use the percentile() function on this column
Output:
median
------
0.0

Related

MySQL: Add Default Value to Joined Table when Row not Found

System info:
$ uname -srvm
Linux 5.15.0-56-generic #62-Ubuntu SMP Tue Nov 22 19:54:14 UTC 2022 x86_64
$ mysql --version
mysql Ver 8.0.31-0ubuntu0.22.04.1 for Linux on x86_64 ((Ubuntu))
I am very inexperienced with MySQL & have been looking for an answer to this for about half a week. I am working with two tables named character_stats & halloffame that I want to join in a query. They look like this:
mysql> SELECT name, level FROM character_stats;
+-----------+-------+
| name | level |
+-----------+-------+
| foo | 0 |
| bar | 0 |
| baz | 3 |
| tester | 4 |
| testertoo | 2 |
+-----------+-------+
mysql> SELECT * from halloffame;
+----+-----------+----------+--------+
| id | charname | fametype | points |
+----+-----------+----------+--------+
| 1 | bar | T | 0 |
| 2 | foo | T | 0 |
| 3 | baz | T | 0 |
| 4 | tester | T | 0 |
| 5 | testertoo | T | 0 |
| 6 | tester | D | 40 |
| 7 | tester | M | 92 |
| 8 | bar | M | 63 |
+----+-----------+----------+--------+
In my query, I want to display all the rows from character_stats & I want to join the points column from halloffame for fametype='M'. If there is no row for fametype='M', I want to set points to 0 for that character name, instead of omitting the entire row as is done in the following:
mysql> SELECT name, level, points FROM character_stats JOIN
-> (SELECT charname, points FROM halloffame WHERE fametype='M')
-> AS hof ON (hof.charname=name);
+--------+-------+--------+
| name | level | points |
+--------+-------+--------+
| tester | 4 | 92 |
| bar | 0 | 63 |
+--------+-------+--------+
So I want it to output this:
+-----------+-------+--------+
| name | level | points |
+-----------+-------+--------+
| foo | 0 | 0 |
| bar | 0 | 63 |
| baz | 3 | 0 |
| tester | 4 | 92 |
| testertoo | 2 | 0 |
+-----------+-------+--------+
I have tried to learn how to use IFNULL, IF-THEN-ELSE, CASE, COALESCE, & COUNT statements from what I have found in documentation & answers on stackoverflow.com. But as I said, I am very inexperienced & don't know how to implement them.
The following works on its own:
SELECT IFNULL((SELECT points FROM halloffame WHERE fametype='M'
AND charname='foo' LIMIT 1), 0) as points;
But I don't know how to join it to the character_stats table. The following would work if I knew how to get the value of character_stats.name before COALESCE is called:
SELECT name, level, 'M' AS fametype, points FROM character_stats
JOIN (SELECT COALESCE((SELECT points FROM halloffame WHERE
fametype='M' AND charname=name LIMIT 1), 0) AS points) AS hof;
According to Adding Default Values on Joining Tables I should be able to use CROSS JOIN, but I am doing something wrong as it still results in Unknown column 'cc.name' in 'where clause':
SELECT name, level, points FROM character_stats
CROSS JOIN (SELECT DISTINCT name FROM character_stats) AS cc
JOIN (SELECT COALESCE((SELECT points FROM halloffame WHERE
fametype='M' AND charname=cc.name LIMIT 1), 0) AS points) AS hof;
Some references I have looked at:
Returning a value even if no result
Usage of MySQL's "IF EXISTS"
Return Default value if no row found
MySQL.. Return '1' if a COUNT returns anything greater than 0
How do write IF ELSE statement in a MySQL query
Simple check for SELECT query empty result
Is there a function equivalent to the Oracle's NVL in MySQL?
MySQL: COALESCE within JOIN
Unknown Column In Where Clause With Join
Adding Default Values on Joining Tables
https://www.tutorialspoint.com/returning-a-value-even-if-there-is-no-result-in-a-mysql-query
I found that I can do the following:
SELECT name, level, COALESCE((SELECT points FROM
halloffame WHERE fametype='M' AND charname=name
LIMIT 1), 0) AS points FROM character_stats;
Though I would still like to know how to do it within a JOIN statement.

Using SQL's CASE for a large number of conditions?

What I want to do is this:
case_1 case_2 case_3 Final
0 0 0 0
0 0 1 3
0 1 0 2
1 0 0 1
1 1 0 2
1 0 1 3
0 1 1 3
1 1 1 3
That means when case_1 is 0, case_2 is 0 and case_3 is 0, the final col has value 0.
Similarly, when case_1 is 1, case_2 is 1 and case_3 is 1, the final cols will be 3.
And so forth.
And what I ended up typing in SQL which is awkward:
Select *,
case when case_1>0 and case_2>0 and case_3>0 then 3 else 0,
case when case_1>0 and case_2>0 and case_3=0 then 2 else 0,
case when case_1>0 and case_2=0 and case_3=0 then 1 else 0,
....
....
....
from mytable;
Now this is seriously bad, I know that. Can there be better way of such coding?
From the example, it looks like the priority is case 3 -> case 2 -> case 1. In which case, you can do something like this:
SELECT *,
CASE WHEN case_3 > 0 THEN 3
WHEN case_2 > 0 THEN 2
WHEN case_1 > 0 THEN 1
ELSE 0 END AS `Final`
FROM table;
Looks like you want the rightmost position of nonzero column, if any
select *,
case when case_3>0 then 3 else
case when case_2>0 then 2 else
case when case_1>0 then 1 else 0 end
end
end final
from tbl
For what it's worth, electrical engineering knows this problem as "generating a Boolean expression from a truth table."
I'm going a different direction from the other answerers.
Create yourself a tiny lookup table with eight rows and four columns, like this
SELECT * FROM final
| case_1 | case_2 | case_3 | Final |
|--------|--------|--------|-------|
| 0 | 0 | 0 | 0 |
| 0 | 0 | 1 | 3 |
| 0 | 1 | 0 | 2 |
| 1 | 0 | 0 | 1 |
| 1 | 1 | 0 | 2 |
| 1 | 0 | 1 | 3 |
| 0 | 1 | 1 | 3 |
| 1 | 1 | 1 | 3 |
Then join to it to your main data table to do your lookup of the final value, like this (http://sqlfiddle.com/#!9/4de009/1/0).
SELECT a.Name, b.Final
FROM test a
JOIN final b ON a.case_1 = b.case_1
AND a.case_2 = b.case_2
AND a.case_3 = b.case_3
Performance? Not a problem on an eight-row lookup table. SQL is made for this.
Flexibility? If your rules for computing Final change all you have to do is update the table. You don't have to do the Boolean expression simplification again.
Complexity? Well, yes, it's more complex than a nested bunch of CASE or IF statements. But it's easier to read.

MySQL cumulative sum gives wrong result

Here is the sample of my table with some sample data-
The strange things happens while making cumulative sum of difference between columns gorivo.PovratKM and gorivo.PolazakKM and same for gorivo.UkupnoGorivo.
The cumulative sums are in column SumUkKM for difference between gorivo.PovratKM and gorivo.PolazakKM and for cumulative sum for gorivo.UkupnoGorivo is column SumGorivo.
The output should be something like:
+-------------+------------+-------------+------------+
| Polazak KM | Povratal KM| Prijedeno KM| SumUkKM |
+-------------+------------+-------------+------------+
| 814990 | 816220 | 1230 | 1230 |
+-------------+------------+-------------+------------+
| 816220 | 817096 | 876 | 2106 |
+-------------+------------+-------------+------------+
| 817096 | 817124 | 28 | 2134 |
+-------------+------------+-------------+------------+
| 817124 | 818426 | 1302 | 3436 |
+-------------+------------+-------------+------------+
What I'm doing wrong in my query?
MySql allows to declare variables in the sql sentence, (select #SumUkGorivo := 0, #SumUkKM := 0) x the CROSS JOIN allows to calculate its value for each row of the other table.
Using variables, you can, for example set reset points or partitions on the same way than SUM() OVER (PARTITION BY is used by other dmbs like SQL or Postgres.
SELECT
y.`PolazakKM`, y.`PovratakKM`,
#SumUkGorivo := #SumUkGorivo + `UkupnoGorivo` as SumUkGorivo,
#SumUkKM := #SumUkKM + (y.`PovratakKM` - y.`PolazakKM`) as SumUkKM
FROM
(select #SumUkGorivo := 0, #SumUkKM := 0) x,
(select gorivo.`PolazakKM`, gorivo.`PovratakKM`, gorivo.`UkupnoGorivo`
from gorivo WHERE gorivo.`IDVozilo` = 131
order by `DatumT`) y
;

mysql (innodb) increment a value to create a hole

say I have a simple tree type table
id(key) | parent | order
============================
1 | 0 | 0
2 | 0 | 1
4 | 2 | 0
5 | 2 | 1
6 | 2 | 2
I want to insert a new node so that it has parent = 2 and order = 1, so it then the table data looks like:
id(key) | parent | order
============================
1 | 0 | 0
2 | 0 | 1
4 | 2 | 0
5 | 2 | 2
6 | 2 | 3
7 | 2 | 1
E.g. existing rows increment their order value.
What's the best way to ensure that existing rows increment their order (non-key field) starting at an existing arbitrary value, to make a hole for my insert statement?
If you have a new row with parent $p and order position $o you can:
UPDATE table SET order = order + 1 WHERE parent = $p AND order >= $o
and then:
INSERT INTO order (id,parent,order) VALUES($id,$p,$o)

Using MIN() in SET statement MySQL

I am using MySQL. Lets call a table that I have as Inventory which looks is below:
+----+--------+--------+-------------+----------+
| ID | Price1 | Price2 | TargetPrice | Quantity |
+----+--------+--------+-------------+----------+
| 1 | 12 | 1 | | 0 |
| 2 | 3 | 3 | 3 | 2 |
| 3 | | 4 | | 0 |
| 4 | 2 | 2 | 2 | 2 |
| 5 | 5 | 45 | 5 | 1 |
+----+--------+--------+-------------+----------+
Now, I need to update the TargetPrice to minimum of Price1 and Price2 for any row whose Quantity is 0
I have tried:
UPDATE Inventory SET
TargetPrice= MIN(Price1,Price2)
WHERE Quantity >0
However, MySQL complains about the usage of MIN() function. I know it is expecting MIN() to work on the data contained inside column, rather than taking MIN() of two columns of a specified row.
Anyway to achieve this other than cursors?
EDIT:
Price1 and Price2 can be null or 0 and in all these cases, it should be treated as infinity so that the other price gets to be minimum when compared against it.
Use LEAST instead of MIN:
UPDATE Inventory
SET TargetPrice = LEAST(Price1,Price2)
WHERE Quantity = 0
MIN is an aggregate function operating on a rowset, whereas LEAST operates on the list of arguments passed.
EDIT:
UPDATE Inventory
SET TargetPrice = LEAST(COALESCE(Price1, Price2), COALESCE(Price2, Price1))
WHERE Quantity = 0
You can use COALESCE to handle NULL values.
EDIT2:
You can use NULLIF to handle 0 values:
UPDATE Inventory
SET TargetPrice = LEAST(COALESCE(NULLIF(Price1,0), Price2),
COALESCE(NULLIF(Price2,0), Price1))
WHERE Quantity = 0