changing the HAVING changes the column values - mysql

So I have a query that, when ran, has, as the values in the last column, 1, 4 and 8. But when I change the HAVING condition those values become 1, 3 and 5. This doesn't make any sense to me.
Here's my SQL:
SELECT memberId, #temp:=total AS total, #runningTotal as runningTotal, #runningTotal:=#temp+#runningTotal AS newRunningTotal
FROM (
SELECT 1 AS memberId, 1 AS total UNION
SELECT 2, 2 UNION
SELECT 3, 2
) AS temp
JOIN (SELECT #temp:=0) AS temp2
JOIN (SELECT #runningTotal:=0) AS temp3
HAVING newRunningTotal <= 40;
Here's the SQL fiddle:
http://sqlfiddle.com/#!2/d41d8/27761/0
If I change newRunningTotal to runningTotal I get different numbers in the runningTotal and newRunningTotal. This doesn't make any sense to me.
Here's the SQL fiddle for the changed query:
http://sqlfiddle.com/#!2/d41d8/27762/0
Any ideas?
Thanks!

MySQL documentation is quite explicit against doing what you are doing in the select:
As a general rule, other than in SET statements, you should never
assign a value to a user variable and read the value within the same
statement. For example, to increment a variable, this is okay:
SET #a = #a + 1;
For other statements, such as SELECT, you might get the results you
expect, but this is not guaranteed. In the following statement, you
might think that MySQL will evaluate #a first and then do an
assignment second:
SELECT #a, #a:=#a+1, ...;
However, the order of evaluation for
expressions involving user variables is undefined.
I think you have found a situation where it makes a difference.

use this sqlFiddle to specify less than 40,
or this sqlFiddle also to specify less than 40.
I think what's happening is HAVING is applied after everything is done, but because you have variables in your select, they get calculated again giving you no control.

Related

DENSE_RANK() OVER and IFNULL()

Let's say I have a table like this -
id
number
1
1
2
1
3
1
I want to return the second largest number, and if there isn't, return NULL instead. In this case, since all the numbers in the table are the same, there isn't the second largest number, so it should return NULL.
These codes work -
SELECT IFNULL((
SELECT number
FROM (SELECT *, DENSE_RANK() OVER(ORDER BY number DESC) AS ranking
FROM test) r
WHERE ranking = 2), NULL) AS SecondHighestNumber;
However, after I changed the order of the query, it doesn't work anymore -
SELECT IFNULL(number, NULL) AS SecondHighestNumber
FROM (SELECT *, DENSE_RANK() OVER(ORDER BY number DESC) AS ranking
FROM test) r
WHERE ranking = 2;
It returns blank instead of NULL. Why?
Explanation
This is something of a byproduct of the way you are using subquery in your SELECT clause, and really without a FROM clause.
It is easy to see with a very simple example. We create an empty table. Then we select from it where id = 1 (no results as expected).
CREATE TABLE #foo (id int)
SELECT * FROM #foo WHERE id = 1; -- Empty results
But now if we take a left turn and turn that into a subquery in the select statement - we get a result!
CREATE TABLE #foo (id int)
SELECT (SELECT * FROM #foo WHERE id = 1) AS wtf; -- one record in results with value NULL
I'm not sure what else we could ask our sql engine to do for us - perhaps cough up an error and say I can't do this? Maybe return no results? We are telling it to select an empty result set as a value in the SELECT clause, in a query that doesn't have any FROM clause (personally I would like SQL to cough up and error and say I can't do this ... but it's not my call).
I hope someone else can explain this better, more accurately or technically - or even just give a name to this behavior. But in a nutshell there it is.
tldr;
So your first query has SELECT clause with an IFNULL function in it that uses a subquery ... and otherwise is a SELECT without a FROM. So this is a little weird but does what you want, as shown above. On the other hand, your second query is "normal" sql that selects from a table, filters the results, and lets you know it found nothing -- which might not be what you want but I think actually makes more sense ;)
Footnote: my "sql" here is T-SQL, but I believe this simple example would work the same in MySQL. And for what it's worth, I believe Oracle (back when I learned it years ago) actually would cough up errors here and say you can't have a SELECT clause with no FROM.

Setting variables in SELECT statement mysql

Hi I'd like to know if it's OK to assume that the SELECT statement selects the fields from left to right to make sure that it is safe to construct a statement as the following (it wouldn't be nice to try to use a variable that has't been set):
SELECT #variable := IFNULL(var, 0), value + #variable, somevalue, value2*#variable, ..., #variable/whatever...
FROM table1 LEFT JOIN table2 LEFT JOIN table3
The reason I'm asking the question is because it is possible that var equals null in some of the tuples due the use of the LEFT JOINS (let's suppose that var comes from table 3), and I need to use the variable several times to make derivative fields as you can see, so I don't want to use IFNULL(var, 0) every time.
From the docs (with my emphasis):
As a general rule, other than in SET statements, you should never
assign a value to a user variable and read the value within the same
statement. For example, to increment a variable, this is okay:
SET #a = #a + 1;
For other statements, such as SELECT, you might get the results you
expect, but this is not guaranteed. In the following statement, you
might think that MySQL will evaluate #a first and then do an
assignment second:
SELECT #a, #a:=#a+1, ...;
However, the order of evaluation for expressions involving user
variables is undefined.
So, if we go by the book, the answer to your question is NO.

Table statistics (aka row count) over time

i'm preparing a presentation about one of our apps and was asking myself the following question: "based on the data stored in our database, how much growth have happend over the last couple of years?"
so i'd like to basically show in one output/graph, how much data we're storing since beginning of the project.
my current query looks like this:
SELECT DATE_FORMAT(created,'%y-%m') AS label, COUNT(id) FROM table GROUP BY label ORDER BY label;
the example output would be:
11-03: 5
11-04: 200
11-05: 300
unfortunately, this query is missing the accumulation. i would like to receive the following result:
11-03: 5
11-04: 205 (200 + 5)
11-05: 505 (200 + 5 + 300)
is there any way to solve this problem in mysql without the need of having to call the query in a php-loop?
Yes, there's a way to do that. One approach uses MySQL user-defined variables (and behavior that is not guaranteed)
SELECT s.label
, s.cnt
, #tot := #tot + s.cnt AS running_subtotal
FROM ( SELECT DATE_FORMAT(t.created,'%y-%m') AS `label`
, COUNT(t.id) AS cnt
FROM articles t
GROUP BY `label`
ORDER BY `label`
) s
CROSS
JOIN ( SELECT #tot := 0 ) i
Let's unpack that a bit.
The inline view aliased as s returns the same resultset as your original query.
The inline view aliased as i returns a single row. We don't really care what it returns (except that we need it to return exactly one row because of the JOIN operation); what we care about is the side effect, a value of zero gets assigned to the #tot user variable.
Since MySQL materializes the inline view as a derived table, before the outer query runs, that variable gets initialized before the outer query runs.
For each row processed by the outer query, the value of cnt is added to #tot.
The return of s.cnt in the SELECT list is entirely optional, it's just there as a demonstration.
N.B. The MySQL reference manual specifically states that this behavior of user-defined variables is not guaranteed.

MySQL SELECT INTO <variable> returns null but query returns a value

For some reason I cannot get this query to return a value when I do a select into.
Given a table t10company, I have a column called CompanyID which is of CHAR(8), it has a value in it of: MYCO0001
If I issue the following query:
SELECT
MAX(CONVERT(RIGHT(CompanyID, 4), UNSIGNED INTEGER))
FROM t10company
WHERE LEFT(CompanyID, 4) = 'MYCO'
GROUP BY LEFT(CompanyID, 4)
ORDER BY RIGHT(CompanyID, 4) LIMIT 1;
I get a return value of 1 which is what I would expect.
If I issue the exact same query except with INTO #myvar and then do a SELECT #myvar it always returns NULL. It does this in the stored proc I'm writing and also does it in a query window in MySQL Workbench. I dont know why?
I use this form to assign a value to a user variable in MySQL
SELECT #myvar := MAX(CONVERT(RIGHT(CompanyID, 4), UNSIGNED INTEGER)) FROM ...
According to the 5.1 documentation, this should also work:
SELECT MAX(CONVERT(RIGHT(CompanyID, 4), UNSIGNED INTEGER)) INTO #myvar FROM ...
I ran a quick test (MySQL 5.1), and the final SELECT is showing #myvar is being set to 1.
CREATE TABLE t10company (CompanyID CHAR(8));
INSERT INTO t10company VALUES ('MYCO0001');
SET #myvar := NULL;
SELECT #myvar;
SELECT
MAX(CONVERT(RIGHT(CompanyID, 4), UNSIGNED INTEGER)) INTO #myvar
FROM t10company
WHERE LEFT(CompanyID, 4) = 'MYCO'
GROUP BY LEFT(CompanyID, 4)
ORDER BY RIGHT(CompanyID, 4) LIMIT 1;
SELECT #myvar;
The expression in the ORDER BY clause is a bit odd.
Why would you be ordering by that? The aggregate is only going to return one row. It's not clear if MySQL is disregarding that though, since MySQL may not be identifying that all rows that satisfy the WHERE clause are going to be grouped together into a single.
For this particular statement, the LIMIT 1 is redundant.
I'd suggest you try it without the ORDER BY, just to see if that makes any difference.
Otherwise, I'm inclined to agree that perhaps the aggregate function and the GROUP BY might be the problem. If it is, them a possible workaround is to wrap your statement in a set of parenthesis as an inline view (give it an alias), and then selecting from the inline view.
You don't need the "group by", "order by", or "limit" in your query for a simple max(). Eliminate those, and if that doesn't work, try wrapping another query around it.
select
value into #myvar
from (
SELECT
MAX(CONVERT(RIGHT(CompanyID, 4), UNSIGNED INTEGER)) as value
FROM
t10company
WHERE
LEFT(CompanyID, 4) = 'MYCO'
)
You need to add LEFT(CompanyID, 4) to your SELECT statement (before FROM) if your are going to GROUP BY LEFT(CompanyID, 4)
This is pretty weird.
If you enclose the query into another, it seems to work.
SELECT value FROM (
SELECT MAX(CONVERT(RIGHT(CompanyID, 4), UNSIGNED INTEGER))
AS value FROM t10company
WHERE LEFT(CompanyID, 4) = 'MYCO'
GROUP BY LEFT(CompanyID, 4)
ORDER BY RIGHT(CompanyID, 4) LIMIT 1
) AS weird INTO #myval;
Judging by the EXPLAIN, the query extra cost should be negligible.

In mysql, can I count how many rows satisfy some condition, if not then exit the count?

Basically I store data in MySql 5.5. I use qt to connect to mysql. I want to compare two columns, if col1 is greater than col2, the count continues, but when col1 is less than col2, count finishes and exits. So this is to count how many rows under some condition at the beginning of column. Is it possible in mysql?
An example:
Col1 Col2
2 1
2 3
2 1
The count I need should return 1, because the first row meets the condition of Col1 > Col2, but the second row doesn't. Whenever the condition is not meet, counting exits no matter if following rows meet the condition or not.
SELECT COUNT(*)
FROM table
WHERE col1 > col2
It's a little difficult to understand what you're after, but COUNT(*) will return the number of rows matched by your condition, if that's your desire. If it's not, can you maybe be more specific or show example(s) of what you're going for? I will do my best to correct my answer depending on additional details.
You should not be using SQL for this; any answer you get will be chock full of comprimise and if (for example) the result set from your intial query comes back in a different order (due to an index being created or changed), then they will fail.
SQL is designed for "set based" logic - and you really are after procedural logic. If you have to do this, then
1) Use a cursor
2) Use an order by statement
3) Cross fingers
This is a bit ugly, but will do the job. It'll need adjusting depending on any ORDER etc you would like to apply to someTable but the principle is sound.
SELECT COUNT(*)
FROM (
SELECT
#multiplier:=#multiplier*IF(t.`col1`<t.`col2`,0,1) AS counter
FROM `someTable` t, (SELECT #multiplier := 1) v
HAVING counter = 1
) scanQuery
The #multiplier variable will keep multiplying itself by 1. When it encounters a row where col1 < col2 it multiplies by 0. It will then continue multiplying 0 x 1. The outer query then just sums them up.
It's not ideal, but would suffice. This could be expanded to allow you to get those rows before the break by doing the following
SELECT
`someTable`.*
FROM `someTable`
INNER JOIN (
SELECT
`someTable`.`PrimaryKeyField`
#multiplier:=#multiplier*IF(`col1`<`col2`,0,1) AS counter
FROM `someTable` t, (SELECT #multiplier := 1) v
HAVING counter = 1
) t
ON scanQuery.`PrimaryKeyField` = `someTable`.`PrimaryKeyField`
Or possibly simply
SELECT
`someTable`.*
#multiplier:=#multiplier*IF(`col1`<`col2`,0,1) AS counter
FROM `someTable` t, (SELECT #multiplier := 1) v
HAVING counter = 1