Odd behavior with user defined variables in MySQL - mysql

I am building a report and using user defined variables to keep the query as compact as possible. What I am noticing in MySQL Workbench and in PHP is that they don't always work properly.
For example:
SELECT
#NewSales := SUM(CASE WHEN `v`.`new_used`='N' THEN 1 ELSE 0 END) AS `NewSales`,
#UsedSales := SUM(CASE WHEN `v`.`new_used`='U' THEN 1 ELSE 0 END) AS `UsedSales`,
#UsedSales + #NewSales AS `TotalSales`
FROM `fi_sales` `s`
LEFT JOIN `vehicles` `v` ON `v`.`stock_number`=`s`.`stock_number`
If I run the above query in Workbench, the first run outputs TotalSales=NULL:
NewSales, UsedSales, TotalSales
3418, 2889, NULL
If I refresh the query, the output produces expected result for TotalSales:
NewSales, UsedSales, TotalSales
3418, 2889, 6307.000000000000000000000000000000
A bit strange; almost as if the variable is not usable in the same query that sets it. I usually work around it by reproducing the calculation without using variables.
My next problem is that if I copy the same query from Workbench into my application (PHP) the TotalSales output will produce "0" zero.
I am sure there is a perfectly good explanation what is going on here but I am having trouble finding it. Any answers are greatly appreciated.

You're in non-deterministic territory using user-defined variables in a query that changes them, and the explanation is straightforward: the answer you get is actually from the previous run of the same query.
UDVs are scoped to your individual database connection. Their values persist between queries, but not across connections. This query is giving you the value of #UsedSales + #NewSales from before the query is run, not after. (Why? Because it just is. There is not a reason... it could go either way. See below.)
SET #UsedSales = 1, #NewSales = 2; and run your query again. Total will be 3 in the next run, clearly the wrong answer (compared to what you expected) but not wrong in the sense that the server is free to resolve these in any order it likes, because they look like constants.
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 other statements, such as SELECT, you might get the results you expect, but this is not guaranteed.
...
The order of evaluation for expressions involving user variables is undefined.
https://dev.mysql.com/doc/refman/5.7/en/user-variables.html
You're trying to solve a problem that isn't really a problem.
Instead, either do this:
SELECT
SUM(CASE WHEN `v`.`new_used`='N' THEN 1 ELSE 0 END) AS `NewSales`,
SUM(CASE WHEN `v`.`new_used`='U' THEN 1 ELSE 0 END) AS `UsedSales`,
SUM(CASE WHEN `v`.`new_used` IN ('N','U') THEN 1 ELSE 0 END) AS `TotalSales`
FROM `fi_sales` `s`
LEFT JOIN `vehicles` `v` ON `v`.`stock_number`=`s`.`stock_number`;
Or if you insist, make a derived table (here, named x) and do the extra addition of the resulting columns.
SELECT
x.NewSales,
x.UsedSales,
x.NewSales + x.UsedSales AS TotalSales
FROM (
SELECT
SUM(CASE WHEN `v`.`new_used`='N' THEN 1 ELSE 0 END) AS `NewSales`,
SUM(CASE WHEN `v`.`new_used`='U' THEN 1 ELSE 0 END) AS `UsedSales`
FROM `fi_sales` `s`
LEFT JOIN `vehicles` `v` ON `v`.`stock_number`=`s`.`stock_number`
) x;
This materializes the inner result into a temporary table that is discarded as soon as the query finishes executing.
Or, if you really want clever and short, go with this:
SELECT
COUNT(`v`.`new_used`='N' OR NULL) AS `NewSales`,
COUNT(`v`.`new_used`='U' OR NULL) AS `UsedSales`,
COUNT(`v`.`new_used` IN ('N','U') OR NULL) AS `TotalSales`
FROM `fi_sales` `s`
LEFT JOIN `vehicles` `v` ON `v`.`stock_number`=`s`.`stock_number`;
This works, because COUNT() counts only rows with non-null arguments, and any expression expr OR NULL coerces expr to be evaluated as a boolean expression, and thus is logically equivalent to CASE WHEN expr [IS TRUE] THEN 1 ELSE NULL END and thus can only evaluate to 1 (if expr is truthy)... or NULL (if expr is either false or null)... which an exact match for how COUNT() works.

Related

SQL if query functionality

I want to write a query to determine the success rate for every day for each different mode. I did write a query that'll just group by date and mode which serves my purpose but one of my seniors wrote this following query which also works but I am just unable to understand how the if clause is working. I'll add a bit of the query here -
SELECT
dt,
sum(if(mode='A',success,0))AS a_s,
sum(if(mode='A',total,0))AS a_t,
sum(if(mode='B',success,0))AS b_s,
sum(if(mode='B',total,0))AS b_t,
sum(if(mode='C',success,0))AS c_s,
sum(if(mode='C',total,0))AS c_t,
sum(if(mode='D',success,0))AS d_s,
sum(if(mode='D',total,0))AS d_t,
sum(if(mode NOT in('A','B','C','D'),success,0))AS other_s,
sum(if(mode NOT in('A','B','C','D'),total,0))AS other_t
FROM
(SELECT
mode,
date(addedon)AS dt,
sum(if(status in('success','partial'),1,0))AS success,
count(*)AS total
FROM `a_huge_ass_table`
WHERE `studentid`=159633 AND addedon>'2021-01-15'
GROUP BY mode,date(addedon)
)AS t
Here I am unable to understand how sum(if(mode='A',success,0))AS a_s, - this if clause is working. If the condition is true then the clause is returning success? how does that work does adding success also somehow verify that its status is a success case? I cant find this on google.
First, if() is not standard SQL. I recommend rewrite this using case:
sum(case when mode = 'A' then success else 0 end) as a_s,
sum(case when mode = 'A' then total else 0 end) as a_t,
and so on.
Second, this query is missing the final group by dt. Otherwise it produces one row, rather than a separate row for each dt value.
This is called conditional aggregation. Every row in the final result set represents a group of rows from the subquery. Within this group, some have mode = 'A' and some do not. For the ones with mode = 'A' the above sums the value of success and total.
There is no need for a subquery by the way. That just slows down the query. I would recommend writing the query as:
SELECT date(addedon) as dt
SUM( mode = 'A' AND status IN ('success', 'partial') ) as a_success,
SUM( mode = 'A' ) as a_total,
. . .
FROM `a_huge_ass_table`
WHERE studentid = 159633 AND addedon >= '2021-01-15'
GROUP BY date(addedon);
Note that this uses a MySQL extension where boolean expressions are treated as integers, with "1" for true and "0" for false.

SQL: Something wrong with inheriting variables for NULL next-row values

I'm trying to inherit value from previous row (based on correct subscription_id + checking for IS NULL subscription_status), but something goes wrong and I get incorrect value.
Take a look at screenshot.
If I'm not mistaken it also called last non-null puzzle, but examples of possible solution for other DB provide window function with IGNORE NULLS.
But, I'm using MySQL 8.x and it doesn't support this function.
I'm sorry, but SQL fiddle doesn't provide correct text-value for variables in my code :(
https://www.db-fiddle.com/f/wHanqoSCHKJHus5u6BU4DB/4
Or, you can see mistakes here:
SET #history_subscription_status = NULL;
SET #history_subscription_id = 0;
SELECT
c.date,
c.user_id,
c.subscription_id,
sd.subscription_status,
(#history_subscription_id := c.subscription_id) as 'historical_sub_id',
(#history_subscription_status := CASE
WHEN #history_subscription_id = c.subscription_id AND sd.subscription_status IS NULL
THEN #history_subscription_status
ELSE
sd.subscription_status
END
) as 'historical'
FROM
calendar c
LEFT JOIN
subscription_data sd ON sd.date = c.date AND sd.user_id = c.user_id AND sd.subscription_id = c.subscription_id
ORDER BY
c.user_id,
c.subscription_id,
c.date
I expect to get results for this query in this way:
IMPORTANT: I'm going to use this code for a lot of data (about 1 mln rows), so it very important for me to avoid additional select or subquery that can slow down the execution of the query.

How to hide the Repeated record in SQL

Using this query:
SELECT
linerbltbl.billofladingid,
linerbltbl.grossweighttotal,
linerb‌​ltbl.netweighttotal,
linerblwisecontainerdetailstbl.containertype,
linerblwisecont‌​ainerdetailstbl.cont‌​ainernumber,
linerblwisecontainerdetailstbl.sealnumber,
linerblwisecontain‌​erdetailstbl.princip‌​lecharge
FROM linerbltbl,
linerblwisecontainerdetailstbl
WHERE linerbltbl.shippername IS NOT NULL
AND linerbltbl.billofladingid =
linerblwisecontainerdetailstbl.bil‌​lofladingid
See this image. It shows what my output IS and what I would like it to be.
You will note that the repeated data is blanked out. Is there a way to do this in my query?
First of all, I think you are going about this wrong. While you CAN do this from within the query itself (see this page) you are probably better off doing it in whatever is consuming the output. For example, if you are taking this data and pasting it into MS Excel, you could check out this post which does is via conditional formatting. If you are consuming it from inside code, you can add logic to handle it there (since I don't know what you are going to do with it, it's hard to add any examples).
But, if you insist on doing it from within SQL it's going to be something to this effect (borrowing from the linked example):
;with t as
(SELECT
linerbltbl.billofladingid,
linerbltbl.grossweighttotal,
linerb‌​ltbl.netweighttotal,
linerblwisecontainerdetailstbl.containertype,
linerblwisecont‌​ainerdetailstbl.cont‌​ainernumber,
linerblwisecontainerdetailstbl.sealnumber,
linerblwisecontain‌​erdetailstbl.princip‌​lecharge
FROM linerbltbl,
linerblwisecontainerdetailstbl
WHERE linerbltbl.shippername IS NOT NULL
AND linerbltbl.billofladingid =
linerblwisecontainerdetailstbl.bil‌​lofladingid
)
SELECT
CASE WHEN RR = 1 THEN billofladingid ELSE '' END BillOfLadingID,
CASE WHEN RR = 1 THEN grossweighttotal ELSE '' END GrossWeight,
CASE WHEN RR = 1 THEN netweighttotal ELSE '' END NetWeight,
containertype,
cont‌​ainernumber,
sealnumber,
princip‌​lecharge
FROM
(SELECT (ROW_NUMBER() OVER(PARTITION BY billofladingid ORDER BY billofladingid, grossweighttotal)) [RR], *
FROM t) SUBQUERY1

Need help in writing Efficient SQL query

I have the following query, written inside perl script:
insert into #temp_table
select distinct bv.port,bv.sip,avg(bv.bv) bv, isnull(avg(bv.book_sum),0) book_sum,
avg(bv.book_tot) book_tot,
check_null = case when bv.book_sum = null then 0 else 1 end
from table_bv bv, table_group pge, table_master sm
where pge.a_p_g = '$val'
and pge.p_c = bv.port
and bv.r = '$r'
and bv.effective_date = '$date'
and sm.sip = bv.sip
query continued -- need help below (can some one help me make this efficient, or rewriting, I am thinking its wrong)
and ((sm.s_g = 'FE')OR(sm.s_g='CH')OR(sm.s_g='FX')
OR(sm.s_g='SH')OR(sm.s_g='FD')OR(sm.s_g='EY')
OR ((sm.s_t = 'TA' OR sm.s_t='ON')))
query continued below
group by bv.port,bv.sip
query ends
explanation: some $val that contain sip with
s_g ('FE','CH','FX','SH','FD','EY') and
s_t ('TA','ON') have book_sum as null. The temp_table does not take null values,
hence I am inserting them as zero ( isnull(avg(bv.book_sum),0) ) where ever it encounters a null for the following s_g and s_m ONLY.
I have tried making the query as follows but it made my script to stop wroking:
and sm.s_g in ('FE', 'CH','FX','SH','FD','EY')
or sm.s_t in ('TA','ON')`
I know this should be a comment, but I don't have the rep. To me, it looks like it's hanging because you lost your grouping at the end. I think it should be:
and (
sm.s_g in ('FE', 'CH','FX','SH','FD','EY')
or
sm.s_t in ('TA','ON')
)
Note the parentheses. Otherwise, you're asking for all of the earlier conditions, OR that sm.s_t is one of TA or ON, which is a much larger set than you're anticipating, which may cause it to spin.

mysql create view [Err] 1349 - View's SELECT contains a subquery in the FROM clause

I'm receieving the following error:
[Err] 1349 - View's SELECT contains a subquery in the FROM clause
When creating this view:
create view v_rescume_info as
select((case when goods.goods_fee is null then 0 else goods.goods_fee end)
+(case when rent.rent_fee is null then 0 else rent.rent_fee end)
+(case when baoxiao.baoxiao_fee is null then 0 else baoxiao.baoxiao_fee end))
as rescume_fee,goods.member_id from (select sum(product_price * product_quantity) as goods_fee,member_id from product_general_info group by member_id) goods
left join (select sum(house_fee + (case when water_fee is null then 0 else water_fee end)*water_price
+(case when electric_fee is null then 0 else electric_fee end)*electric_price) rent_fee,member_id from member_rent_info group by member_id) rent
on goods.member_id = rent.member_id
left join (select sum(finishedoff_price) as baoxiao_fee ,member_id from member_finishedoff_info group by member_id) baoxiao
Well that's a mess.
In order to help you, you can replace those "cases when... then" with coalesce
create view v_rescume_info as
select(
Coalesce(goods.goods_fee,0) + Coalesce(rent.rent_fee, 0) +
Coalesce(baoxiao.baoxiao_fee,0) as rescume_fee,
goods.member_id from v_newView1
Why I put the v_newView here? Well.. you are really trying to select from another select. You can cross tables but it never worked for me this way. This looks like Transact-SQL rather than MySQL sintax.
Before you create views, please test those views outside. You only should put "Create View foo as " once you got the results working.
For what I can see, you need to create another view, "my" v_newView1 in order to get the results from a sub-query. I recommend you to use the join functions (left, right, inner) to achieve what you intend.
Without the database it's hard to figure where is your problem. Also use multiple lines while debugging. It really helps to see where witch line is producing your error.