MYSQL error causing calculated result to be written to next row - mysql

Using MYSQL 5.7
This query creates a new table with the columns order_month and SKU plus the calculated columns qty_mth, count_mth and avg_month. The resulting table correctly reflects the first 4 columns but the final column (avg_month), while being correctly calculated is written on the next row.
CREATE TABLE tbl_temp_si_trans4
(temp_si_trans4id INT NOT NULL AUTO_INCREMENT,
PRIMARY KEY temp_trans4idkey (temp_si_trans4id),
INDEX index1 (order_month,SKU))
SELECT order_month, SKU,
#qty_mth := SUM(net_qty_after_refund) AS qty_mth,
#count_mth := COUNT(DISTINCT(order_year)) AS count_mth,
#avg_month := #qty_mth/#count_mth AS avg_month
FROM order_trans4
GROUP BY order_month, SKU
Please see below example of result:
I have tried to following modifications to the avg_month calculation line with the same result.
(#qty_mth/#count_mth) AS avg_month and
#qty_mth/#count_mth AS avg_month

From the documentation:
As a general rule, you should never assign a value to a user variable
and read the value within the same statement. You might get the
results you expect, but this is not guaranteed.
You can use subqueries without using user defined variables to achieve the effect you are looking for however. Something similar to the following
SELECT x.total_sale,
x.f1 / x.total_sale AS f1_percent
FROM (
SELECT s.f1,
s.f1 + s.f2 AS total_sale,
FROM sales s
) x

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.

SQL: Order by a list of foreign_key numbers

A simplified example:
I have a SQL table called things. Things by themselves have an id and a name. Things are part of a tree, e.g. a thing can have a parent; Exactly how to this is stored is not important, important is however that it is possible to obtain a list of thing ids from the root node to the current thing.
I have another table, called properties. A property has a thing_id column, a name column and a value column.
I now want, for the current thing, to obtain all properties, ordered by thing_id, in order of the paths from root thing to current thing.
e.g., if the current thing is nested like this: Root(1) > Vehicle(4) > Car(2) > Hybrid(3), I would want the list of properties be returned with the properties that have a thing_id==1 first, followed by the ones with thing_id == 4, then thing_id==2 and finally thing_id==3.
How can this be done using SQL? (without using N+1 selects)
In SQL this can be achieved with use of recursive query. Here is an example
DECLARE #item as varchar(10)
with CTE (main_part, sub_part, NestingLevel)
as
(
select main_part, sub_part, 1 from tblParts
where main_part = #item
union all
select tblParts.main_part, tblParts.sub_part, (NestingLevel + 1) from tblParts
inner join CTE on tblParts.main_part = CTE.sub_part
)
select * from CTE
In order to address this in MySQL you can try temporary table approach. Here is a good example of it: How to do the Recursive SELECT query in MySQL?

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 sum of custom column

I have a query in which I create custom column names.
At the end of that same table I want a row that will sum all the entries of these custom columns.
For example I have:
SELECT ... IFNULL(bl.amount, '---') AS BudgetAmount,
IFNULL((bl.amount *1) - ( + bal1.amount ), '---') AS Difference
FROM...
I want a column with the sum of BudgetAmount and DIfference columns.
How do I do that?
I think you can use this:
SELECT ... IFNULL(bl.amount, '---') AS BudgetAmount,
IFNULL((bl.amount *1) - ( + bal1.amount ), '---') AS Difference,
IFNULL(bl.amount, 0) + IFNULL((bl.amount *1) - ( + bal1.amount ), 0) AS NewCol
FROM...
Or you could explain more
One easy way to do that is to use your original query as an inline view and write a SELECT statement using the inline view as a rowsource. (In MySQL parlance, the inline view is called a derived table):
SELECT v.BudgetAmount
, v.Difference
, v.BudgetAmount + v.Difference AS Total
FROM (
-- original query here
SELECT ... IFNULL(bl.amount, '---') AS BudgetAmount,
IFNULL((bl.amount *1) - ( + bal1.amount ), '---') AS Difference
FROM...
) v
Unlike other relational database systems, MySQL will actually force the inline view to be materialized (i.e. the query in the inline view is executed and the results are stored as a MyISAM table), and that has performance implications.
But it is a way that you can reference the column aliases for use in other expressions.
(The aliases assigned to the columns can't be referenced in SELECT list where they are assigned... they can only be referenced in the HAVING and ORDER BY clauses of the query.)
To get the values for those columns added into another column in a single query, you can't reference the aliases, you have to to repeat the expressions, like this:
SELECT expr1 AS BudgetAmount
, expr2 AS Difference
, expr1 + expr2 AS Total
FROM ...
To reference the aliases, they need to come from a row source referenced by the query, such as an inline view:
SELECT v.BudgetAmount
, v.Difference
, v.foo
, v.BudgetAmount + v.Difference AS Total
FROM ( SELECT expr1 AS BudgetAmount
, expr2 AS Difference
, foo
FROM ...
) v
I'm not sure that's the answer you wanted to hear, but that's the way it is.
EDIT:
I misunderstood what you were asking. Your question said: "I want a column with the sum of ...". I took that to mean you wanted a query that returned the same number of rows, not that you wanted an additional ROW appended to the result set.
This sounds like a good candidate for app-side summarizing. However, if you really want to do it in MySQL only, there are essentially three options I think.
Write a stored procedure.
Do it in a UNION as others have suggested, which requires putting basically the same subquery twice in one statement.
Try the GROUP BY ... WITH ROLLUP syntax. I'm assuming you have a unique key included in the SELECT, e.g., someUniqueID. You need to group on that ID (so there will be only one row in each group) then use the rollup clause to append the additional row. The ID column will be NULL in the rollup row.
The statement will look something like this, depending on your full query and table structure:
SELECT someUniqueID, ... , IFNULL(SUM(bl.amount), '---') AS BudgetAmount,
IFNULL( SUM( (bl.amount *1) - ( + bal1.amount ) ), '---') AS Difference
FROM ...
GROUP BY someUniqueID
WITH ROLLUP
Sounds like you should do this on the application side. But you can just use UNION ALL at the end to stack the "sum of all entries." Just make sure your columns are aligned.
SELECT amount, balance
FROM table
UNION ALL
SELECT IFNULL(SUM(amount), '---') AS BudgetAmount,
IFNULL(SUM(...)) AS Difference
FROM table

I need some help getting MySql to output some results using a subquery

I'm storing a list of numbers inside a table as a varchar(255) and want to use this list in another query's "IN() clause.
Here's what I mean:
Table Data:
CREATE TABLE IF NOT EXISTS `session_data` (
`visible_portf_ids` varchar(255) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
INSERT INTO `session_data` (`visible_portf_ids`) VALUES
('45,44,658,659,661,45,44,658,659,661')
I want to run a query like this to return a list of portfolio's "QUERY #1":
SELECT portfolio_hierarchy_id, account_id, name, leaf_node_portf_id
FROM portfolio_hierarchy
WHERE account_id = 1
AND leaf_node_portf_id IN
(
(SELECT visible_portf_ids
FROM session_data
WHERE username = 'ronedog')
)
ORDER BY name ASC
The result of the query above returns only 1 row, when there are a total of 3 that should have been returned.
If I run the subquery alone like this:
(SELECT visible_portf_ids
FROM session_data
WHERE username = 'ronedog')
it will return a list like this:
45,44,658,659,661,45,44,658,659,661
But, when I run Query #1 above, only one row of data, which is associated with the "visible_portf_ids" of "45" is returned.
If I replace the subquery with hard coded values like this:
SELECT portfolio_hierarchy_id, account_id, name, leaf_node_portf_id
FROM portfolio_hierarchy
WHERE account_id = 1
AND leaf_node_portf_id IN (45,44,658,659,661,45,44,658,659,661)
ORDER BY name ASC
then I get all 3 rows I'm expecting.
I'm guessing that MySql is returning the list as a string because its stored as a varchar() and so it stops processing after the first "visible_portf_ids" is found, which is "45", but I'm not really sure.
Anyone got any ideas how I can fix this?
Thanks in advance.
You should think about restructuring your tables storing each value in a new row, instead of concatenating them.
Until then, you can use the FIND_IN_SET() function:
AND FIND_IN_SET(leaf_node_portf_id,
(SELECT visible_portf_ids
FROM session_data
WHERE username = 'ronedog'
LIMIT 1)
) > 0
Unfortunately MySQL does not have a function to split a delimited string. Your IN argument is a single string with the result of your subquery. The reason it works when you hard-code it is that MySQL is parsing the values.
I suggest that you redesign your data base to store the visible ports list as separate rows in a separate table. Then you can retrieve them and use them in subqueries like you tried.