Coalesce whole records in MySQL - mysql

I'm currently coallescing fields individually in MySQL queries, but I would like to coalesce whole records.
Is this possible?
SELECT la.id,
COALESCE(( SELECT name FROM lookup_changed l0,
( SELECT MAX(id) id
FROM lookup_changed
WHERE lookup_id = 26
) l1
WHERE l0.id = l1.id
), la.name) name,
COALESCE(( SELECT msisdn FROM lookup_changed l0,
( SELECT MAX(id) id
FROM lookup_changed
WHERE lookup_id = 26
) l1
WHERE l0.id = l1.id
), la.msisdn) msisdn
FROM lookup_added la
WHERE la.id = 26
#Alma Do - the pseudo-SQL is:
SELECT la.id,
MULTICOALESCE(( SELECT <name, msisdn> FROM lookup_changed l0,
( SELECT MAX(id) id
FROM lookup_changed
WHERE lookup_id = 26
) l1
WHERE l0.id = l1.id
), <la.name, la.msisdn>) <name, msisdn>
FROM lookup_added la
WHERE la.id = 26

Since COALESCE() "return[s] the first non-NULL argument", it sounds like you want to retreive the "first non-NULL result from a set for queries":
-- syntax error
SELECT COALESCE(
SELECT a FROM ta,
SELECT b FROM tb
);
-- roughly equates to
( SELECT a AS val FROM ta WHERE a IS NOT NULL ORDER BY a LIMIT 1 )
UNION
( SELECT b AS val FROM tb WHERE b IS NOT NULL ORDER BY b LIMIT 1 )
ORDER BY val LIMIT 1 ;
Comments:
I added ORDER BY clauses, otherwise "first row" means nothing
the inner LIMIT 1 clauses are optional (but allow early trimming of sub-results)
the parenthesis around the sub queries are mandatory

Related

MYSQL error code: 1054 Unknown column in where clause. Error occurring in Nested SubQueries

I am trying to get through a problem where there are multiple accounts of same scheme on same customer id. On a given txn date I want to retrieve the total Sanctioned Limit and total utilized amount from these accounts. Below is the SQL query I have constructed.
SELECT
cust_id,
tran_date,
rollover_date,
next_rollover,
(
SELECT
acc_num as kcc_ac
FROM
dbzsubvention.acc_disb_amt a
WHERE
(a.tran_date <= AB.tran_date)
AND a.sch_code = 'xxx'
AND a.cust_id = AB.cust_id
ORDER BY
a.tran_date desc
LIMIT
1
) KCC_ACC,
(
SELECT
SUM(kcc_prod)
FROM
(
SELECT
prod_limit as kcc_prod,
acc_num,
s.acc_status
FROM
dbzsubvention.acc_disb_amt a
inner join dbzsubvention.acc_rollover_all_sub_status s using (acc_num)
left join dbzsubvention.acc_close_date c using (acc_num)
WHERE
a.cust_id = AB.cust_id
AND a.tran_date <= AB.tran_date
AND (
ac_close > AB.tran_date || ac_close is null
)
AND a.sch_code = 'xxx'
AND s.acc_status = 'R'
AND s.rollover_date <= AB.tran_date
AND (
AB.tran_date < s.next_rollover || s.next_rollover is null
)
GROUP BY
acc_num
order by
a.tran_date
) t
) kcc_prod,
(
SELECT
sum(disb_amt)
FROM
(
SELECT
disb_amt,
acc_num,
tran_date
FROM
(
SELECT
disb_amt,
a.acc_num,
a.tran_date
FROM
dbzsubvention.acc_disb_amt a
inner join dbzsubvention.acc_rollover_all_sub_status s using (acc_num)
left join dbzsubvention.acc_close_date c using (acc_num)
WHERE
a.tran_date <= AB.tran_date
AND (
c.ac_close > AB.tran_date || c.ac_close is null
)
AND a.sch_code = 'xxx'
AND a.cust_id = AB.cust_id
AND s.acc_status = 'R'
AND s.rollover_date <= AB.tran_date
AND (
AB.tran_date < s.next_rollover || s.next_rollover is null
)
GROUP BY
acc_num,
a.tran_date
order by
a.tran_date desc
) t
GROUP BY
acc_num
) tt
) kcc_disb
FROM
dbzsubvention.acc_disb_amt AB
WHERE
AB.cust_id = 'abcdef'
group by
cust_id,
tran_date
order by
tran_date asc;
This query isn't working. Upon research I have found that correlated subquery works only till 1 level down. However I couldn't get a workaround to this problem.
I have tried searching the solution around this problem but couldn't find the desired one. Using the SUM function at the inner query will not give desired results as
In the second subquery that will sum all the values in column before applying the group by clause.
In third subquery the sorting has to be done first then the grouping and finally the sum.
Therefore I am reaching out to the community for help to suggest a workaround to the issue.
You're correct - external column cannot be transferred through the nesting level immediately.
Try this workaround:
SELECT ... -- outer query
( -- correlated subquery nesting level 1
SELECT ...
( -- correlated subquery nesting level 2
SELECT ...
...
WHERE table0_level1.column0_1 ... -- moved value
)
FROM table1
-- move through nesting level making it a source of current level
CROSS JOIN ( SELECT table0.column0 AS column0_1 ) AS table0_level1
) AS ...,
...
FROM table0
...

How to select last and last but one records

I have a table with 3 columns id, type, value like in image below.
What I'm trying to do is to make a query to get the data in this format:
type previous current
month-1 666 999
month-2 200 15
month-3 0 12
I made this query but it gets just the last value
select *
from statistics
where id in (select max(id) from statistics group by type)
order
by type
EDIT: Live example http://sqlfiddle.com/#!9/af81da/1
Thanks!
I would write this as:
select s.*,
(select s2.value
from statistics s2
where s2.type = s.type
order by id desc
limit 1, 1
) value_prev
from statistics s
where id in (select max(id) from statistics s group by type) order by type;
This should be relatively efficient with an index on statistics(type, id).
select
type,
ifnull(max(case when seq = 2 then value end),0 ) previous,
max( case when seq = 1 then value end ) current
from
(
select *, (select count(*)
from statistics s
where s.type = statistics.type
and s.id >= statistics.id) seq
from statistics ) t
where seq <= 2
group by type

MySql - Selecting MAX & MIN and returning the corresponding rows

I trying to get the last 6 months of the min and max of prices in my table and display them as a group by months. My query is not returning the corresponding rows values, such as the date time for when the max price was or min..
I want to select the min & max prices and the date time they both occurred and the rest of the data for that row...
(the reason why i have concat for report_term, as i need to print this with the dataset when displaying results. e.g. February 2018 -> ...., January 2018 -> ...)
SELECT metal_price_id, CONCAT(MONTHNAME(metal_price_datetime), ' ', YEAR(metal_price_datetime)) AS report_term, max(metal_price) as highest_gold_price, metal_price_datetime FROM metal_prices_v2
WHERE metal_id = 1
AND DATEDIFF(NOW(), metal_price_datetime) BETWEEN 0 AND 180
GROUP BY report_term
ORDER BY metal_price_datetime DESC
I have made an example, extract from my DB:
http://sqlfiddle.com/#!9/617bcb2/4/0
My desired result would be to see the min and max prices grouped by month, date of min, date of max.. and all in the last 6 months.
thanks
UPDATE.
The below code works, but it returns back rows from beyond the 180 days specified. I have just checked, and it is because it joining by the price which may be duplicated a number of times during the years.... see: http://sqlfiddle.com/#!9/5f501b/1
You could use twice inner join on the subselect for min and max
select a.metal_price_datetime
, t1.highest_gold_price
, t1.report_term
, t2.lowest_gold_price
,t2.metal_price_datetime
from metal_prices_v2 a
inner join (
SELECT CONCAT(MONTHNAME(metal_price_datetime), ' ', YEAR(metal_price_datetime)) AS report_term
, max(metal_price) as highest_gold_price
from metal_prices_v2
WHERE metal_id = 1
AND DATEDIFF(NOW(), metal_price_datetime) BETWEEN 0 AND 180
GROUP BY report_term
) t1 on t1.highest_gold_price = a.metal_price
inner join (
select a.metal_price_datetime
, t.lowest_gold_price
, t.report_term
from metal_prices_v2 a
inner join (
SELECT CONCAT(MONTHNAME(metal_price_datetime), ' ', YEAR(metal_price_datetime)) AS report_term
, min(metal_price) as lowest_gold_price
from metal_prices_v2
WHERE metal_id = 1
AND DATEDIFF(NOW(), metal_price_datetime) BETWEEN 0 AND 180
GROUP BY report_term
) t on t.lowest_gold_price = a.metal_price
) t2 on t2.report_term = t1.report_term
simplified version of what you should do so you can learn the working process.
You need calculate the min() max() of the periods you need. That is your first brick on this building.
you have tableA, you calculate min() lets call it R1
SELECT group_field, min() as min_value
FROM TableA
GROUP BY group_field
same for max() call it R2
SELECT group_field, max() as max_value
FROM TableA
GROUP BY group_field
Now you need to bring all the data from original fields so you join each result with your original table
We call those T1 and T2:
SELECT tableA.group_field, tableA.value, tableA.date
FROM tableA
JOIN ( ... .. ) as R1
ON tableA.group_field = R1.group_field
AND tableA.value = R1.min_value
SELECT tableA.group_field, tableA.value, tableA.date
FROM tableA
JOIN ( ... .. ) as R2
ON tableA.group_field = R2.group_field
AND tableA.value = R2.max_value
Now we join T1 and T2.
SELECT *
FROM ( .... ) as T1
JOIN ( .... ) as T2
ON t1.group_field = t2.group_field
So the idea is if you can do a brick, you do the next one. Then you also can add filters like last 6 months or something else you need.
In this case the group_field is the CONCAT() value

Sql query with three conditions

I have a database with a table having content as below :
message_number message_type message_chat
0 IN Hi
1 OB Hello
2 IN Help
3 IN Want to find this thing
4 OB Sure
5 OB Please let me know
I have written 5 rows since i want to incorporate all possible cases that i want in my query in the example table that i showed.
Now in my query output, i want something like :
message_in message_out
Hi Hello
Help NULL
Want to find this string Sure
NULL Please let me know
So the cases that i want to consider are :
suppose if message_number=0 and message_number=1 both have message_type value as IN then put message_chat_in as message_chat(at message_number=0) and message_chat out as NULL and the iterate over message_number=1
if message_number =0 have message_type=IN and message_number =1 have message_type=OB, then show message_chat(at message_number=0) as message_chat_in and message_chat(at message_number=1) as message_out and dont iterate over message_number=1;
hope i have clarified the condition though i have included all three condition in the expected output.How should my sqlquery look like?
Edit : I am using mysql version 5.5.8
Try the following query
SELECT
q1.message_number in_num,
q1.message_chat in_chat,
q2.message_number out_num,
q2.message_chat out_chat
FROM
(
SELECT *,#i1:=IFNULL(#i1,0)+1 num
FROM Chat
ORDER BY message_number
) q1
LEFT JOIN
(
SELECT *,#i2:=IFNULL(#i2,0)+1 num
FROM Chat
ORDER BY message_number
) q2
ON q2.num=q1.num+1 AND q2.message_type<>q1.message_type
WHERE q1.message_type='IN'
UNION ALL
SELECT
q1.message_number in_num,
q1.message_chat in_chat,
q2.message_number out_num,
q2.message_chat out_chat
FROM
(
SELECT *,#i3:=IFNULL(#i3,0)+1 num
FROM Chat
ORDER BY message_number
) q1
RIGHT JOIN
(
SELECT *,#i4:=IFNULL(#i4,0)+1 num
FROM Chat
ORDER BY message_number
) q2
ON q2.num=q1.num+1 AND q2.message_type<>q1.message_type
WHERE q2.message_type='OB'
AND q1.message_type IS NULL
ORDER BY IFNULL(in_num,out_num)
SQL Fiddle - http://sqlfiddle.com/#!9/95a515/1
The second variant
SET #i1 = 0;
SET #i2 = 0;
SET #i3 = 0;
SET #i4 = 0;
-- the same query
SQL Fiddle - http://sqlfiddle.com/#!9/95a515/2
Or
SELECT 0,0,0,0 INTO #i1,#i2,#i3,#i4;
-- the same query
SQL Fiddle - http://sqlfiddle.com/#!9/95a515/5
why not using a analytic function here? I would do it with Lead() like this:
with inc as (
--Do the incorporation in this block. could be subquery too
--but its easier to read this way.
select
case when message_type = 'IN'
then message_chat
end as message_in
,case when LEAD(message_type) OVER (Order by message_number) = 'OB' --get the next message by number if it is type OB
then LEAD(message_chat) OVER (order by message_number)
end as message_out
from input
)
select *
from inc
where coalesce(message_in, message_out) is not null --filter out rows where with in & out is null
Ok, since there is no analytical functions in MySQL less than 8 the code may not be easy to follow:
with data_rn as
(
-- this isolate consecutive rows with the same message_type
select d1.*, count(d2.message_number) rn
from data d1
left join data d2 on d1.message_number > d2.message_number and d1.message_type != d2.message_type
group by d1.message_number
),
data_rn2 as
(
-- this marks the rows where new rows has to be added (i.e. when rn2 != 0)
select d1.*, count(d2.message_number) rn2
from data_rn d1
left join data_rn d2 on d1.rn = d2.rn and d1.message_type = d2.message_type and d1.message_number > d2.message_number
group by d1.message_number
),
data_added as
(
-- this add new rows
select message_number, message_type, message_chat
from data_rn2
union all
select message_number - 0.5, 'OB', NULL from data_rn2 where message_type = 'IN' and rn2 != 0
union all
select message_number - 0.5, 'IN', NULL from data_rn2 where message_type = 'OB' and rn2 != 0
order by message_number
), data_added_rn as
(
-- this compute new row numbering
select d1.*, ceil((count(d2.message_number)+1)/2) rn
from data_added d1
left join data_added d2 on d1.message_number > d2.message_number
group by d1.message_number
)
-- this will do the final formating
select max(case when message_type = 'IN' then message_chat end) message_in,
max(case when message_type = 'OB' then message_chat end) message_out
from data_added_rn
group by rn
demo
I have tried to comment each section appropriately.

MySQL Complex Inner Join

Suppose equity has a column called TickerID. I would like to replace the 111's with equity.TickerID. MySQL can't seem to resolve the scope and returns an unknown column when I try that. This SQL statement works but I need to run it for each ticker. Would be nice if I could get a full table.
SELECT Ticker,
IF(tbl_m200.MA200_Count = 200,tbl_m200.MA200,-1) AS MA200,
IF(tbl_m50.MA50_Count = 50,tbl_m50.MA50,-1) AS MA50,
IF(tbl_m20.MA20_Count = 20,tbl_m20.MA20,-1) AS MA20
FROM equity
INNER JOIN
(SELECT TickerID,AVG(Y.Close) AS MA200,COUNT(Y.Close) AS MA200_Count FROM
(
SELECT Close,TickerID FROM equity_pricehistory_daily
WHERE TickerID = 111
ORDER BY Timestamp DESC LIMIT 0,200
) AS Y
) AS tbl_m200
USING(TickerID)
INNER JOIN
(SELECT TickerID,AVG(Y.Close) AS MA50,COUNT(Y.Close) AS MA50_Count FROM
(
SELECT Close,TickerID FROM equity_pricehistory_daily
WHERE TickerID = 111
ORDER BY Timestamp DESC LIMIT 50
) AS Y
) AS tbl_m50
USING(TickerID)
INNER JOIN
(SELECT TickerID,AVG(Y.Close) AS MA20,COUNT(Y.Close) AS MA20_Count FROM
(
SELECT Close,TickerID FROM equity_pricehistory_daily
WHERE TickerID = 111
ORDER BY Timestamp DESC LIMIT 0,20
) AS Y
) AS tbl_m20
USING(TickerID)
This seems to be some bug or "feature" of MySQL. Many persons seems to have the same problem with outer tables being out of scope.
Anyway... You could create functions that retrieve the information you want:
DROP FUNCTION IF EXISTS AveragePriceHistory_20;
CREATE FUNCTION AveragePriceHistory_20(MyTickerID INT)
RETURNS DECIMAL(9,2) DETERMINISTIC
RETURN (
SELECT AVG(Y.Close)
FROM (
SELECT Z.Close
FROM equity_pricehistory_daily Z
WHERE Z.TickerID = MyTickerID
ORDER BY Timestamp DESC
LIMIT 20
) Y
HAVING COUNT(*) = 20
);
SELECT
E.TickerID,
E.Ticker,
AveragePriceHistory_20(E.TickerID) AS MA20
FROM equity E;
You would get NULL instead of -1. If this is undesirable, you could wrap the function-call with IFNULL(...,-1).
Another way of solving this, would be to select for the time-frame, instead of using LIMIT.
SELECT
E.TickerID,
E.Ticker,
(
SELECT AVG(Y.Close)
FROM equity_pricehistory_daily Y
WHERE Y.TickerID = E.TickerID
AND Y.Timestamp > ADDDATE(CURRENT_TIMESTAMP, INTERVAL -20 DAY)
) AS MA20
FROM equity E;