select lower and upper from lower only table - mysql

How can I transform a normalized table with family, name and 'lowerbound' number to a result set with family, name, lower and upper bound, where upper bound is defined as min(lowerbound of family) > current lowerbound and if no number like this exists, use a provided number
for example, if this is the schema and data:
create table records(
family varchar(10),
name varchar(10),
lowbound int(4)
);
insert into records
values
('letters', 'a',1),('letters', 'b',3),('letters', 'c',3),('letters', 'd',3),
('letters', 'e',7),('letters', 'f',7),('numbers', '12',1), ('numbers', '15',1), ('numbers', '18',4);
and the provided number is 9, then the result set should be:
| FAMILY | NAME | LOWER | UPPER |
|---------|------|-------|-------|
| letters | a | 1 | 3 |
| letters | b | 3 | 7 |
| letters | c | 3 | 7 |
| letters | d | 3 | 7 |
| letters | e | 7 | 9 |
| letters | f | 7 | 9 |
| numbers | 12 | 1 | 4 |
| numbers | 15 | 1 | 4 |
| numbers | 18 | 4 | 9 |

Try this out:
SELECT r1.family, r1.name, r1.lowbound lower, coalesce(min(r2.lowbound), 9) upper
FROM records r1
LEFT JOIN records r2 ON r1.family = r2.family AND r1.lowbound < r2.lowbound
GROUP BY r1.family, r1.name, r1.lowbound
Fiddle here

I think the easiest way to express this is with a correlated subquery in the select clause:
select r.*,
coalesce((select r2.lowbound
from records r2
where r2.family = r.family and
r2.lowbound > r.lowbound
order by r2.lowbound
limit 1
), 9) as highbound
from records r;
The coalesce() handles the case where there is no value. In that case, your substitution value of 9 is used.
Here is the SQL Fiddle.

Related

how to remove rows that are related to same values

how do I turn this:
| ID | LETTER
----------------
| 1 | A
| 2 | B
| 3 | B
| 4 | C
| 5 | D
| 6 | D
| 7 | F
| 8 | A
On this:
| ID | LETTER
----------------
| 4 | C
| 7 | F
There are multiple ways to solve this problem, one possible way is -
SELECT
*
FROM
t_system_log;
SELECT
t_table.*
FROM
t_table,
(
SELECT
letter
FROM
t_table
GROUP BY
letter
HAVING
count(letter) = 1) AS t_unique
WHERE
t_table.letter = t_unique.letter
;
My Fiddle
I'm using SQL 5.6,
If the table name is t_name,
Then simplest query to remove rows that are related to same values:
*
SELECT * FROM t_name
Group by LETTER having count(LETTER)=1;

How to select only a specific set that includes some or all of another set in mySQL

I'm trying to extract specific rows from a mySQL table that contains lists of numbers.
I have a single table with 2 columns- id and data. Each row has a sorted, comma separated record of numbers ranging from 1 to 1000. I want to only select records with a partial or full set of specific numbers in it. I've tried using LIKE and IN and also looked at FIND_IN_SET.
t1.id t1.data
1 2,9,569
2 2,9,991,979
3 9,569,763
4 52,57,569,763,892,897
5 763
6 2,9,10,15,151,569,771,801,888,973
If I'm looking for rows with one or more of the values (2,9,569,763), I don't want to have to write:
SELECT t1.id from t1
WHERE t1.data NOT IN (1,3,4,5,6,7,8,10,11,...........,1000);
to return 3 rows, t1.id = 1,3 and 5.
Is there a simpler way? Something like (in mySQL):
SELECT t1.id from t1
WHERE t1.data "only includes one or more of" (2,9,569,763);
Paul Spiegel's answer gives the correct result, but it can't be optimized with indexes, because of the use of FIND_IN_SET(). It will always do a table-scan, which will get more and more expensive the more rows you have.
You should take this as a clue that storing lists of numbers as a comma-separated list in a string column is a bad idea when you actually want to do some searches for discrete members of that list.
What you should do instead is store the list as a child table, with one member per row.
CREATE TABLE mydata (
t1id INT NOT NULL,
member INT NOT NULL,
PRIMARY KEY (t1id, member),
FOREIGN KEY (t1id) REFERENCES t1(id)
);
INSERT INTO mydata VALUES
(1,2),(1,9),(1,569),
(2,2),(2,9),(2,991),(2,979),
(3,9),(3,569),(3,763),
(4,52,(4,57,(4,569,(4,763,(4,892),(4,897),
(5,763),
(6,2),(6,9),(6,10),(6,15),(6,151),(6,569),(6,771),(6,801),(6,888),(6,973);
Now you would join your original table t1 to mydata but exclude the matches to values in your desired list.
mysql> select * from t1 left join mydata on t1.id=mydata.t1id
and mydata.member not in (2,9,569,763);
+----+------+--------+
| id | t1id | member |
+----+------+--------+
| 1 | NULL | NULL |
| 2 | 2 | 979 |
| 2 | 2 | 991 |
| 3 | NULL | NULL |
| 4 | 4 | 52 |
| 4 | 4 | 57 |
| 4 | 4 | 892 |
| 4 | 4 | 897 |
| 5 | NULL | NULL |
| 6 | 6 | 10 |
| 6 | 6 | 15 |
| 6 | 6 | 151 |
| 6 | 6 | 771 |
| 6 | 6 | 801 |
| 6 | 6 | 888 |
| 6 | 6 | 973 |
+----+------+--------+
You see there are NULLs of id 1, 3, 5 because there are no values that are NOT in your specified list. Those are the id's that you want to return.
mysql> select t1.id from t1 left join mydata on t1.id=mydata.t1id
and mydata.member not in (2,9,569,763)
where mydata.member is null;
+----+
| id |
+----+
| 1 |
| 3 |
| 5 |
+----+
Not simple but..
Count single hits and compare it with the number of all values in the data column. They must be equal.
select id, data
from t1
where (find_in_set(2, data) > 0)
+ (find_in_set(9, data) > 0)
+ (find_in_set(569, data) > 0)
+ (find_in_set(763, data) > 0)
= char_length(data) - char_length(replace(data, ',', '')) + 1
Demo: https://www.db-fiddle.com/f/oLcrz4vmXRWXqYQhCnZA5z/0
Try to use the REGEXP function:
SELECT t1.id from t1
WHERE t1.data REGEXP '(2,9,569,763)';

MySQL: SUM function applied to a formula contained in field selected by another query

I'm in the need to perform a select SUM() where that is a formula contained into a field selected by another query.
Example:
table_A (the "formula" field contains, in each cell, an arithmetic expression involving columns from table B):
+------------+--------------+------------+
| Product_id | related_prod | formula |
+------------+--------------+------------+
| U1 | C2 | col2-col1 |
| U2 | C3 | col3-col2 |
| U3 | C4 | col3-col1 |
+------------+--------------+------------+
table_B:
+------------+---------+------------+----------+------+------+------+
| Product_id | year_id | company_id | month_id | col1 | col2 | col3 |
+------------+---------+------------+----------+------+------+------+
| C2 | 2017 | 1 | 2 | 100 | 200 | 300 |
| C3 | 2017 | 1 | 2 | 400 | 500 | 600 |
| C4 | 2017 | 1 | 2 | 700 | 800 | 900 |
+------------+---------+------------+----------+------+------+------+
I do, then, the following query:
SELECT
SUM(totals.relaz) as final_sum,
totals.relaz as 'col',
totals.prod as 'prod',
totals.cons as 'cons',
m.company_id, m.month_id, m.year_id, FROM `table_B` m,
( SELECT formula as relaz,
related_prod as prod,
p.product_id as cons FROM table_A p )
AS totals
WHERE m.product_id=totals.prod
GROUP BY m.company_id, m.year_id, m.month_id, m.product_id, totals.cons
After the select I'd do expect that, considering for example the only product 'U1', the corresponding row would be
+-----------+-----------+------+------+------------+----------+---------+
| final_sum | col | prod | cons | company_id | month_id | year_id |
+-----------+-----------+------+------+------------+----------+---------+
| 100 | col2-col1 | C2 | U1 | 1 | 2 | 2017 |
+-----------+-----------+------+------+------------+----------+---------+
Instead, what I get is
+-----------+-----------+------+------+------------+----------+---------+
| final_sum | col | prod | cons | company_id | month_id | year_id |
+-----------+-----------+------+------+------------+----------+---------+
| 0 | col2-col1 | C2 | U1 | 1 | 2 | 2017 |
+-----------+-----------+------+------+------------+----------+---------+
i.e. the final_sum field is always set to 0, despite the 'col' field contains the correct equation.
What am I doing wrong?
Thank you in advance
Alex
You are trying to get sum from a string column (table_A.formula). This will result 0. MySQL/MariaDB will not try to convert the strings to column references and evaluate the formula in the string.
Another thing is that you should list all columns not in aggregate function in GROUP BY.
To get the result you want, use:
SELECT
SUM(CASE
WHEN a.formula = 'col2-col1' THEN b.col2-b.col1
WHEN a.formula = 'col3-col1' THEN b.col3-b.col1
WHEN a.formula = 'col3-col2' THEN b.col3-b.col2
END
) AS final_sum,
a.formula as 'col',
a.related_prod as 'prod',
a.Product_id as 'cons',
b.company_id,
b.month_id,
b.year_id
FROM table_B b
JOIN table_A a on a.related_prod=b.Product_id
GROUP BY a.formula, a.related_prod, a.Product_id, b.company_id, b.month_id, b.year_id
It may possible to build a Stored routine that fetches the string col2-col1 and inserts it (using CONCAT) into a string, then PREPAREs and EXECUTEs the SQL string.
That is, dynamically build the SQL, perhaps like in #slaakso's Answer.
It would be messy.
I have needed something like this; I chose to do eval() in PHP, which was the client language. I use it for evaluating VARIABLES and GLOBAL STATUS. Example: Table_open_cache_misses / Uptime gives the "misses per second", which, if high, indicates the need for increasing the setting table_open_cache.

SQL order by match to specific row

I have a example table below. I am trying to create a SQL query that gets all user_ids besides user_id of the current user and then orders by number of matches to the row with the current user_id
For example, if the user has a user_id of '1', I want to get all of the user_ids corresponding with the rows of id 2-8, and then order the user_ids from most matches to the row of the current user to least matches with the row of the current user
Let's say var current_user = 1
Something like this:
SELECT user_id
FROM assets
WHERE user_id <> `current_user` and
ORDER BY most matches to `current_user`"
The output should get 7,8,3,9,2
I would appreciate anyone's input on how I can effectively achieve this.
Table assets
+----------+---------+-------+--------+-------+
| id | user_id | cars | houses | boats |
+----------+---------+-------+--------+-------+
| 1 | 1 | 3 | 2 | 3 |
| 2 | 8 | 3 | 2 | 5 |
| 3 | 3 | 3 | 2 | 2 |
| 4 | 2 | 5 | 1 | 5 |
| 5 | 9 | 5 | 7 | 3 |
| 8 | 7 | 3 | 2 | 3 |
+----------+---------+-------+--------+-------+
I think you can just do this:
select a.*
from assets a cross join
assets a1
where a1.user_id = 1 and a.user_id <> a1.user_id
order by ( (a.cars = a1.cars) + (a.houses = a1.houses) + (a.boats = a1.boats) ) desc;
In MySQL, a boolean expression is treated as an integer in a numeric context, with 1 for true and 0 for false.
If you want to be fancier, you could order by the total difference:
order by ( abs(a.cars - a1.cars) + abs(a.houses - a1.houses) + abs(a.boats - a1.boats) );
This is called Manhattan distance, and you would be implementing a version of a nearest neighbor model.

COUNT() with a nested subquery

Can I count a column with the number of instances of a character in a particular column?
+---+---+
| i | p |
+---+---+
| A | 3 |
| B | 3 |
| C | 0 |
| A | 1 |
| B | 1 |
| C | 3 |
| A | 1 |
| B | 0 |
| C | 0 |
+---+---+
Query:
SELECT i, SUM(p) AS Sp, COUNT(p) AS Cp FROM table
GROUP BY i
Id like to get this:
+---+----+----+-----+-----+-----+
| i | Sp | Cp | x3x | x1x | x0x |
+---+----+----+-----+-----+-----+
| A | 5 | 3 | 1 | 2 | 0 |
| B | 4 | 3 | 1 | 1 | 0 |
| C | 4 | 3 | 1 | 0 | 2 |
+---+----+----+-----+-----+-----+
Essentially I want to COUNT the instances of 3, 0 or 1 in a column where the column is grouped by the id 'i'
I tried this as well as a number of variations, but I can't seem to get it going.
COUNT(P WHERE p='3'), COUNT(P WHERE p='1'), COUNT(P WHERE p='0'),
Is there a means by which I can place a subquery within a COUNT() that I've missed in my research?
I also tried
COUNT(Points='3'), COUNT(='1'), COUNT(Points='0'),
You are close:
select i, sum(points), count(*),
sum(Points = 3), sum(points = 1), sum(Points = 0)
from t
group by i;
One minor difference in this case is that a removed the single quotes around the values. When comparing to a number, don't use single quotes. Only use single quotes for string and date constants.
The more important change is from count() to sum(). count() counts the number of non-NULL values. Well, the boolean expression is true or false -- but not really NULL (unless points is NULL, which is not the case with your data).
MySQL treats boolean values as integers in a numeric context, with 0 for false and 1 for true. So, adding them up counts the number of times that something is true.