The AVG() function calculates column-means but how can I retrieve the mean of several values in the same row like so:
SELECT MEAN(A.a, A.b, ... , A.n) FROM A;
EDIT: sure, as cherouvim suggests I can do:
SELECT MEAN(A.a + A.b + ... + A.n) / n FROM A;
but I'm trying to find out if there's a leaner way.
select (A.a + A.b) / 2 from A;
I stumbled upon a similar situation. This came useful: http://tech-blog.borychowski.com/index.php/2009/02/mysql/average-value-in-a-row/
From the page:
When we do:
SELECT *, (V.rank_0 + V.rank_1 + V.rank_2) / 3
AS row_avg FROM voting V
we only receive correct averages for the rows where all values are not NULL. But when I have e.g. 3, NULL, 4 I’d like to get 3.5 as a return. That’s the moment when function COALESCE() comes handy.
What does COALESCE () do? From MySQL manual we have:
Returns the first non-NULL value in the list, or NULL if there are no non-NULL values.
mysql> SELECT COALESCE(NULL,1);
-> 1
mysql> SELECT COALESCE(NULL,NULL,NULL);
-> NULL
And these information will help us to build another SELECT statement:
SELECT *,
#first part
(COALESCE(V.rank_0, 0)
+ COALESCE(V.rank_1, 0)
+ COALESCE(V.rank_2, 0))
/
#second part
(3 -
(COALESCE(V.rank_0 - V.rank_0, 1)
+ COALESCE(V.rank_1 - V.rank_1, 1)
+ COALESCE(V.rank_2 - V.rank_2, 1))
) AS row_avg FROM voting V
If no one else finds a better solution, you could always just add them together, then divide by the number of columns you added.
Not pretty, but it works.
Not specifically MySql, but the idea should be easy enough to translate over.
CREATE TABLE A (id int identity(1,1), C1 int, C2 int, C3 int)
GO
INSERT INTO A VALUES (1,1,1)
INSERT INTO A VALUES (2,2,2)
INSERT INTO A VALUES (3,3,3)
INSERT INTO A VALUES (1,2,3)
INSERT INTO A VALUES (4,5,6)
GO
CREATE VIEW A_Values
AS
SELECT ID, AVG(Val) AS Average
FROM
(
SELECT ID, C1 AS Val FROM A
UNION ALL
SELECT ID, C2 AS Val FROM A
UNION ALL
SELECT ID, C3 AS Val FROM A
) Q
GROUP BY ID
GO
SELECT * FROM A_Values
GO
I'm not familiar with the MySQL syntax, but what about dumping the row data into a temporary table as multiple rows with a single column, and then using the AVG() function to obtain your result?
Related
There's a hard to understand issue with querying on a json field in MySQL. The data column is of type json.
The following query works perfectly fine
SELECT * FROM `someTable` WHERE data->'$.someData' in ('A')
However the following one returns nothing.
SELECT * FROM `someTable` WHERE data->'$.someData' in ('A','B')
Funnily enough this also works:
SELECT * FROM `someTable` WHERE data->'$.someData'='A' OR data->'$.someData'='B'
I'm clueless as to why this happens. I originally thought that WHERE x IN executed in a json query format might be doing something like && but even if the values are ('A','A') it still returns nothing which essentially shows that more than one value in WHERE x IN wont work.
SAMPLE DATA (any would do really)
id | data (json)
1 | {"someData":"A"}
2 | {"someData":"B"}
Too long for a comment...
This seems to be related to an optimisation MySQL is performing when there is only one value in the IN expression (probably converting it to an a = b expression) and then it ignoring quotes. Strictly speaking,
SELECT *
FROM `someTable`
WHERE data->'$.someData' in ('A')
or
SELECT *
FROM `someTable`
WHERE data->'$.someData' = 'A'
should return no data because
SELECT data->'$.someData'
FROM someTable;
returns
"A"
"B"
which is not the same as A. You need to use JSON_UNQUOTE (or if you have MySQL 5.7.13 or later the ->> operator) to get the actual value of the someData key:
SELECT JSON_UNQUOTE(data->'$.someData') FROm someTable;
SELECT data->>'$.someData' FROm someTable;
which gives
A
B
which then works fine with an IN expression:
SELECT *
FROM `someTable`
WHERE JSON_UNQUOTE(data->'$.someData') in ('A','B')
-- or use WHERE data->>'$.someData' in ('A','B')
Output:
id data
1 {"someData":"A"}
2 {"someData":"B"}
Demo on dbfiddle
You could try using a join on a subquery instead of a IN clause
SELECT *
FROM `someTable` s
INNER JOIN (
select 'A' col
union
select 'B'
) t ON t.col = s.data->'$.someData
Edited to add: not sure why table formatting isn't working, but here is the written example:
I have a simple SQL table (call it SIMPLE) with unique values
**VALUE**
1699
2349
2934
3100
I am looking to create 3 repeats of each value in column 'VALUE' and create a second column that has an index of 1:3 for each value.
Does anyone have a simple way of doing this?
Here is a simple way to do this using unions:
SELECT value, 1 AS idx FROM yourTable UNION ALL
SELECT value, 2 FROM yourTable UNION ALL
SELECT value, 3 FROM yourTable
ORDER BY value, idx;
Demo
The following is more performant than union all:
select t.value, n.n
from t cross join
(select 1 as n union all select 2 union all select 3) n;
Why? The table only needs to be scanned once. This is only an issue if the table is relatively large, but it is worth mentioning the more efficient solution.
Here's an example in SQLite:
CREATE TABLE X(a INT); INSERT INTO X VALUES (1699), (2349), (2934), (3100);
CREATE TABLE Y(b INT); INSERT INTO Y VALUES (1), (2), (3);
SELECT X.a, Y.b FROM X, Y ORDER BY 2, 1;
Let's say I have something as basic as
SELECT IF( (1 + 2) >=0, (1 + 2), 0)
FROM DUAL
which will obviously return 3.
Is there a way to NOT repeat the query I want to return (here 1 + 2)?
To put in context, I want to return the number of tasks remaining to do for the current month (at least 4 must be done), but if more than 4 are already done, there is no need to do more so I want to return 0.
What I have done is
IF((4 - IFNULL((LONG QUERY RETURNING THE NUMBER OF TASKS DONE THIS MONTH FOR A PARTICULAR USER ),0)) >= 0,
(4 - IFNULL((LONG QUERY RETURNING THE NUMBER OF TASKS DONE THIS MONTH FOR A PARTICULAR USER ),
0)
But is there a way to not repeat the query since it is long and I don't want the server to execute the same query twice?
Depending your request, you might want to use user-defined variables:
SELECT 1+2 INTO #tmp;
SELECT IF( #tmp >=0, #tmp, 0)
Or if you like one liners
SELECT IF( (#tmp := (1+2)) >=0, #tmp, 0)
-- ^ ^
-- don't forget those parenthesis !
EDIT: This works for "table select" statements too:
CREATE TABLE tbl AS SELECT 1 as a UNION SELECT 2;
SELECT SUM(a) FROM tbl INTO #tmp;
SELECT IF ( #tmp > 0, #tmp, 0);
If you look at http://sqlfiddle.com/#!2/37c33/1 for the execution plan of the above queries, you will see the second SELECT don't use the table.
BTW, please note that with your actual example, this could have been written:
SELECT MAX( (1+2), 0 )
-- ^^^^^ here your expression
... but I think this is not a property of your real query?
You can do this:
SELECT IF( (#a:=(1 + 2)) >=0, #a, 0);
One statement solution
A way to solve this problem in MySQL is to save the output of the heavy query and reuse multiple times. Take a look here:
SELECT CASE
WHEN #Value - 4 > 0
THEN #Value ELSE 0
END
FROM (SELECT #Value := (SELECT 1)) Query
where «SELECT 1» should be replaced by your query.
OT
Another way to perform your query would require the support of CTE (Common Table Expression).
For what I know this feature is missing in MySQL (How to transform a MSSQL CTE query to MySQL?)
Just to give you a taste of its expressiveness in MSSQL, where CTE is available, you could write something like that:
with Query (value)
as
(
select 1
)
select
case
when (select value from Query) - 4 > 0
then (select value from Query)
else
0
end
Indeed CTE are more powerful especially when dealing with recursive relations.
For example, i have an empty table 'A' with columns: number(INT) and id.
I know that in MySQL null + number returns null. However there is workaround using IFNULL function:
SELECT IFNULL(NULL, 0) + #number
would return #number. But if I try to select from an existing table, results are not the same:
SELECT IFNULL(number, 0) + #number FROM A
gives me a result with no rows selected.
How can i get MySQL to display one row with 'number' being a number, so I could insert minimal #number using REPLACE statement into table A?
Maybe there is another way..?
You can't - you're selecting from an empty table. The number of rows returned is directly related to the number of rows in your table selection.
If you expect just a single row in A at any time, you could do something like this:
select IFNULL((select number from A limit 1), 0) + #number
But, I'd have to ask the broader question of what exactly you're trying to achieve with this query, because it's likely that it can be done in a better way.
If this is indeed some aggregate function then something along these lines will always return 1 row
select MIN(IFNULL(tablesum.mysum, 0))
from
(select MIN(t.score) mysum from table t
union all
select #number as mysum
) tablesum
Reading your comment I think you want
Select min of a table (0 or 1 row), combine that with a row with a fixed number then sum those 1 or 2 rows:
select SUM(IFNULL(d.number, 0))
from
(select MIN(t.number) number from table t
union all
select #number as number
) d
Is there a way to use LIKE and IN together?
I want to achieve something like this.
SELECT * FROM tablename WHERE column IN ('M510%', 'M615%', 'M515%', 'M612%');
So basically I want to be able to match the column with a bunch of different strings. Is there another way to do this with one query or will I have to loop over the array of strings I am looking for?
How about using a substring with IN.
select * from tablename where substring(column,1,4) IN ('M510','M615','M515','M612')
You can do it by in one query by stringing together the individual LIKEs with ORs:
SELECT * FROM tablename
WHERE column LIKE 'M510%'
OR column LIKE 'M615%'
OR column LIKE 'M515%'
OR column LIKE 'M612%';
Just be aware that things like LIKE and per-row functions don't always scale that well. If your table is likely to grow large, you may want to consider adding another column to your table to store the first four characters of the field independently.
This duplicates data but you can guarantee it stays consistent by using insert and update triggers. Then put an index on that new column and your queries become:
SELECT * FROM tablename WHERE newcolumn IN ('M510','M615','M515','M612');
This moves the cost-of-calculation to the point where it's necessary (when the data changes), not every single time you read it. In fact, you could go even further and have your new column as a boolean indicating that it was one of the four special types (if that group of specials will change infrequently). Then the query would be an even faster:
SELECT * FROM tablename WHERE is_special = 1;
This tradeoff of storage requirement for speed is a useful trick for larger databases - generally, disk space is cheap, CPU grunt is precious, and data is read far more often than written. By moving the cost-of-calculation to the write stage, you amortise the cost across all the reads.
You'll need to use multiple LIKE terms, joined by OR.
Use the longer version of IN which is a bunch of OR.
SELECT * FROM tablename
WHERE column LIKE 'M510%'
OR column LIKE 'M615%'
OR column LIKE 'M515%'
OR column LIKE 'M612%';
SELECT * FROM tablename
WHERE column IN
(select column from tablename
where column like 'M510%'
or column like 'M615%'
OR column like 'M515%'
or column like'M612%'
)
substr([column name],
[desired starting position (numeric)],
[# characters to include (numeric)]) in ([complete as usual])
Example
substr([column name],1,4) in ('M510','M615', 'M515', 'M612')
I tried another way
Say the table has values
1 M510
2 M615
3 M515
4 M612
5 M510MM
6 M615NN
7 M515OO
8 M612PP
9 A
10 B
11 C
12 D
Here cols 1 to 8 are valid while the rest of them are invalid
SELECT COL_VAL
FROM SO_LIKE_TABLE SLT
WHERE (SELECT DECODE(SUM(CASE
WHEN INSTR(SLT.COL_VAL, COLUMN_VALUE) > 0 THEN
1
ELSE
0
END),
0,
'FALSE',
'TRUE')
FROM TABLE(SYS.DBMS_DEBUG_VC2COLl('M510', 'M615', 'M515', 'M612'))) =
'TRUE'
What I have done is using the INSTR function, I have tried to find is the value in table matches with any of the values as input. In case it does, it will return it's index, i.e. greater than ZERO. In case the table's value does not match with any of the input, then it will return ZERO. This index I have added up, to indicate successful match.
It seems to be working.
Hope it helps.
You can use a sub-query with wildcards:
SELECT 'Valid Expression'
WHERE 'Source Column' LIKE (SELECT '%Column' --FROM TABLE)
Or you can use a single string:
SELECT 'Valid Expression'
WHERE 'Source Column' LIKE ('%Source%' + '%Column%')
u can even try this
Function
CREATE FUNCTION [dbo].[fn_Split](#text varchar(8000), #delimiter varchar(20))
RETURNS #Strings TABLE
(
position int IDENTITY PRIMARY KEY,
value varchar(8000)
)
AS
BEGIN
DECLARE #index int
SET #index = -1
WHILE (LEN(#text) > 0)
BEGIN
SET #index = CHARINDEX(#delimiter , #text)
IF (#index = 0) AND (LEN(#text) > 0)
BEGIN
INSERT INTO #Strings VALUES (#text)
BREAK
END
IF (#index > 1)
BEGIN
INSERT INTO #Strings VALUES (LEFT(#text, #index - 1))
SET #text = RIGHT(#text, (LEN(#text) - #index))
END
ELSE
SET #text = RIGHT(#text, (LEN(#text) - #index))
END
RETURN
END
Query
select * from my_table inner join (select value from fn_split('M510', 'M615', 'M515', 'M612',','))
as split_table on my_table.column_name like '%'+split_table.value+'%';
For a perfectly dynamic solution, this is achievable by combining a cursor and a temp table. With this solution you do not need to know the starting position nor the length, and it is expandable without having to add any OR's to your SQL query.
For this example, let's say you want to select the ID, Details & creation date from a table where a certain list of text is inside 'Details'.
First create a table FilterTable with the search strings in a column called Search.
As the question starter requested:
insert into [DATABASE].dbo.FilterTable
select 'M510' union
select 'M615' union
select 'M515' union
select 'M612'
Then you can filter your data as following:
DECLARE #DATA NVARCHAR(MAX)
CREATE TABLE #Result (ID uniqueIdentifier, Details nvarchar(MAX), Created datetime)
DECLARE DataCursor CURSOR local forward_only FOR
SELECT '%' + Search + '%'
FROM [DATABASE].dbo.FilterTable
OPEN DataCursor
FETCH NEXT FROM DataCursor INTO #DATA
WHILE ##FETCH_STATUS = 0
BEGIN
insert into #Result
select ID, Details, Created
from [DATABASE].dbo.Table (nolock)
where Details like #DATA
FETCH NEXT FROM DataCursor INTO #DATA
END
CLOSE DataCursor
DEALLOCATE DataCursor
select * from #Result
drop table #Result
Hope this helped
select *
from tablename
where regexp_like (column, '^M510|M615|^M515|^M612')
Note: This works even if say, we want the code M615 to match if it occurs in the middle of the column. The rest of the codes will match only if the column starts with it.