Retrieve and compute prices in MySQL using conditions - mysql

I want to retrieve/compute the price on a given date for different assets, depending on the side of the transaction. Prior to 2000, I have mid quotes, afterwards I have bid and ask or offer quotes, so I would like the price to be the average of these two quotes. More precisely:
SELECT date, price,
CASE WHEN side='' THEN 'price_mid'
WHEN side='A' THEN 'price_ask'
WHEN side='B' THEN 'price_bid'
WHEN side='O' THEN 'price_offer'
END as prices
FROM table
WHERE asset = 'a';
How can I then compute the price in a new column, having the price_mid (prior to 2000) and (price_bid+price_ask)/2 or (price_bid+price_offer)/2 afterwards?
E.g.: Let's say I have the following situation:
date price prices
1 1 price_mid
2 1.1 price_mid
3 0.9 price_bid
3 1.2 price_ask
4 1.3 price_offer
4 1.1 price_bid
And I would like to have:
date final_price
1 1
2 1.1
3 1.05
4 1.2

I understand you need the average for only some dates. Maybe the following does what you want:
SELECT date, AVG(price) as AvgValue
FROM prices
WHERE date >= 2
AND prices in ('price_ask','price_offer','price_bid')
GROUP BY date
UNION
SELECT date, price as AvgValue
FROM prices
WHERE date < 2
AND prices = 'price_mid'
GROUP BY date
UNION
SELECT date, price as AvgValue
FROM prices p
WHERE date >= 2
AND prices = 'price_mid'
AND NOT EXISTS (SELECT 1 FROM prices p2 where p2.date = p.date AND p2.prices in ('price_ask','price_offer','price_bid'))
GROUP BY date
ORDER BY DATE ASC

Something like this
SELECT `date`, AVG(price) AS final_price FROM tbl GROUP BY 1

Tried something, it did not work, nevertheless maybe that helps..
This is a changed version of the query you already have, but it does not save a kind of state, it only has all the values on the right places.
SELECT
date,
CASE WHEN side = '' THEN price
ELSE NULL
END as price_mid,
CASE WHEN side = 'A' THEN price
ELSE NULL
END as price_ask,
CASE WHEN side = 'B' THEN price
ELSE NULL
END as price_bid,
CASE WHEN side = '' THEN price
ELSE NULL
END as price_offer
FROM
table
WHERE
asset = 'a';
What should give you:
date price_mid price_ask price_bid price_offer
1 1 NULL NULL NULL
2 1.1 NULL NULL NULL
3 NULL NULL 0.9 NULL
3 NULL 1.2 NULL NULL
4 NULL NULL NULL 1,4
4 NULL NULL 1.1 NULL
Now on that table you can check with IS NULL or take the SUM()or the MAX() per date, so you can switch through your cases on one line.

Related

Case Function Returns Null

I have the following input data(A sample only):
ListID
date
Value
0
2022-10-17
0
1
2022-10-17
43.050430504
3
2022-10-17
40.000000000
4
2022-10-17
38.636363636
5
2022-10-17
20.714285714
I am little bit confused about two below query results.
First Query:
SELECT
ListID,
CASE
WHEN date>'2022-07-22'
THEN avg(value)
ELSE NULL
END AS 'Value_Before_Rate_Change'
FROM
TB01 where date like '2022%' and ListID=1;
Output first query:
Value_Before_Rate_Change
NULL
Second Query
select avg(value)
from TB01
where date like '2022%'
and ListID=1
and date>'2022-07-22';
Output second query:
avg(value)
57.773696518595
Can someone show me why I am always getting NULL as an result when I use CASE.
Update:
I used below group by as well. But same result
SELECT
ListID,
CASE
WHEN date>'2022-07-22'
THEN avg(value)
ELSE NULL
END AS 'Value_Before_Rate_Change'
FROM
TB01 where date like '2022%' and ListID=1 group by ListID;
select listID
,avg(case when date > '2022-07-22' then value end) as Value_Before_Rate_Change
from t
group by listID
listID
Value_Before_Rate_Change
0
0.0000000000000000
1
43.0504305040000000
3
40.0000000000000000
4
38.6363636360000000
5
20.7142857140000000
Fiddle

Join 2 tables to produce desired output

I have 2 tables as following:
Products table:
ProductID Name
1 Condensed cheese
2 Milk
Prices table:
ProductID Currency Price
2 EUR 1.50
2 USD 1.74
2 JPY 194.624
1 EUR 0.99
1 USD 1.15
I am learning SQL and wondering what would be SQL statement to join 2 above tables to produce this output:
ProductID Name EUR USD JPY
1 Condensed cheese 0.99 1.15 NULL
2 Milk 1.50 1.74 194.624
you can use max() function with case when
select t1.ProductID ,t1.Name,
max(case when t2.Currenc= 'EUR' then Price end) as EUR,
max(case when t2.Currenc= 'USD' then Price end) as USD,
max(case when t2.Currenc= 'JPY' then Price end) as JPY
from
Products t1 join Prices t2 on t1.ProductID =t2.ProductID
group by t1.ProductID ,t1.Name
It is a Pivot Table problem. You will need to use conditional aggregation with Group By clause.
Do an Inner Join between the two tables using ProductID.
We do a Group By on ProductId and Name, since you want a single row for a productid with all the prices in the same row.
Now, we will use conditional function If() to determine price for a specific currency column. If the currency code matches for that column, we consider that price value, else we consider null. So, for example, in a column aliased EUR, we will have null values for rest of the currencies (except EUR). We will then use Max() function to ensure that we consider the corresponding currency price only.
If there is no price value for a specific currency in the Prices table, it shall come as null value (all currencies will show null and Max(null, null, ...) = null
Eventually we Order By ProductID ASC to get the result sorted in ascending order by ProductID.
Try the following query:
SELECT pdt.ProductID,
pdt.Name,
MAX( IF(prc.Currency = 'EUR', prc.Price, NULL) ) AS EUR,
MAX( IF(prc.Currency = 'USD', prc.Price, NULL) ) AS USD,
MAX( IF(prc.Currency = 'JPY', prc.Price, NULL) ) AS JPY
FROM Products AS pdt
INNER JOIN Prices AS prc ON prc.ProductID = pdt.ProductID
GROUP BY pdt.ProductID, pdt.Name
ORDER BY pdt.ProductID ASC

Complex mysql query with conditional results

I have the next structure in a MySQL database:
boats
id name
-------------
1 name1
2 name2
boat_prices
id boat_id date duration price is_default
---------------------------------------------------------------
1 1 '2018-01-01' 1 100
2 1 '2018-01-01' 2 200
3 1 null null 100 1
4 2 '2018-01-02' 2 400
5 2 '2018-01-02' 4 800
6 2 null null 200 1
7 3 '2018-01-03' 5 1500
8 3 null null 300 1
The boats have a price for a specific date and duration in days.
All boats have a default "from" price that is identified by date = null and duration = null.
But, not all boats have prices for all days.
When I search for boat prices for a specific date and duration, the query should return all rows with a price for that date and duration, and in case a boat hasnĀ“t got a price for that date return its "from" default price.
Example: For the date = '2018-01-01 and duration = 1, the result should be:
boat_prices
id boat_id date duration price is_default
----------------------------------------------------------------
1 1 '2018-01-01' 1 100
6 2 null null 200 1
8 3 null null 300 1
I did this query example just to simplify, but please take into account apart from this, the query has some other joins with other tables.
I need help with the query.
I believe Rick was on the right direction having left join, but you probably need TWO. One to get the boat prices that qualify the date interested in, another explicitly for the default.
select
b.id,
b.name,
DefPrice.price as DefaultPrice,
Specials.price as SpecialsPrice,
COALESCE( Specials.price, DefPrice.price ) as DiscountOrDefaultPrice
from
( select #parmDate = '2018-01-01' ) sqlvars,
boats b
JOIN boat_prices DefPrice
on b.id = DefPrice.boat_id
AND DefPrice.date IS NULL
AND DefPrice.Duration IS NULL
LEFT JOIN boat_prices Specials
on b.id = Specials.boat_id
AND Specials.date <= #parmDate
AND #parmDate <= Date_Add( Specials.Date, INTERVAL (Specials.duration -1 ) DAY )
Now, you could always return only the one price in question by doing a COALESCE() in case there is no Specials price, it gets the default via the DiscountOrDefaultPrice column.
Take your pick version of which column(s) you want to run with. This should get ALL boats, regardless of some special price based on durations. As you change whatever your parameter date in question is -- even if you do a current date, it will work. This is because you are testing the date in question against ALL possible special boat prices and its beginning to beginning + duration end date range. If you have multiple prices that overlap dates, that will just return those multiple rows that overlap.
My Adding of the duration is subtracting 1. For example, if your date is 2018-01-01 and its good for 1 day, does that mean it is only good for that one day? or up to and including 2018-01-02. The -1 forces the qualification to just the one day. So the price on 2018-01-01 good for 1 day is ONLY 2018-01-01.
Your other example for 2018-01-02 has two day duration. To me, indicating 2 days including 01-02 through 01-03. Two actual days.
CONFIRMATION from comment about dates and range
I guess my interpretation was wrong then on your data needs. Your sample of TWO dated boat price records apparently is not enough. You stated you want ALL boats regardless of qualification of a special price record. So you must start with the boat and the join to get all possible "Default" pricing no matter what. It is only the LEFT-JOIN component that needs to be adjusted.
That being said, lets simulate more data. Assume you have the following
Boad ID Date Duration Rate
1 2018-01-01 1 x
1 2018-01-02 4 y
2 2018-01-02 2 z
2 2018-01-04 4 a
3 2018-01-03 5 b
If I provide the date 2018-01-01, what rate records should I see?
If I provide date 2018-01-03, what records?
If I provide date 2018-01-05, what records?
For the particular date "2018-01-01" and duration of 1, i will use an UNION clause like this:
(Note: Edited for add is_default column)
-- Get prices for particular day and duration.
(SELECT
boat_id,
date,
duration,
price,
0 AS is_default
FROM
boat_prices
WHERE
date = "2018-01-01" AND duration = 1)
UNION
-- Add defaults prices for those don't have a price on the particular day and duration
(SELECT
boat_id,
date,
duration,
price,
is_default
FROM
boat_prices
WHERE
date IS NULL
AND
duration IS NULL
AND
boat_id NOT IN (SELECT boat_id
FROM boat_prices
WHERE date ="2018-01-01" AND duration = 1))
EXAMPLE WITH STORED PROCEDURE SOLUTION
DELIMITER //
CREATE PROCEDURE GetPricesByDateAndDuration(IN pDate DATE, IN pDuration INT)
BEGIN
-- Get prices for particular day and duration.
(SELECT
boat_id,
date,
duration,
price,
0 AS is_default
FROM
boat_prices
WHERE
date = pDate AND duration = pDuration)
UNION
-- Add defaults prices for those don't have a price on the particular day and duration
(SELECT
boat_id,
date,
duration,
price,
is_default
FROM
boat_prices
WHERE
date IS NULL
AND
duration IS NULL
AND
boat_id NOT IN (SELECT boat_id
FROM boat_prices
WHERE date = pDate AND duration = pDuration))
END //
DELIMITER ;
Then you can call the procedure like this:
CALL GetPricesByDateAndDuration('2018-01-01', 1);
Instead of that clunky output, consider:
boat_id price default
-----------------------------
1 100
2 300 (default)
Something like this should generate that:
SELECT boat_id,
IF(b.price IS NULL, dflt.price, b.price) AS price,
IF(b.price IS NULL, '(default)', '') AS default
FROM boat_prices AS dflt
LEFT JOIN boat_prices AS b USING(boat_id)
WHERE dflt.date IS NULL
AND dflt.duration IS NULL
AND '2018-01-01' >= b.date
AND '2018-01-01' < b.date + INTERVAL b.duration DAY
GROUP BY boat_id

How can I calculate columns when using grouping sets

I have a query that is giving me results using grouping sets:
Select
store,
product,
FiscalMonth,
FIscalYear,
SUM(Amount),
CASE WHEN FiscalMonth IS NULL THEN SUM(Amount) ELSE NULL END AS Total
From Sales
Group by store, product,
Grouping Sets(
(FiscalYear, FiscalMonth, product),
(FiscalYear, product)
)
Which gives me a nice set of results grouped by FiscalMonth and FiscalYear:
Store Product FiscalMonth FiscalYear Amount Total
1 123 NULL 2007 23.00 452.00
1 123 1/1/2007 2007 55.00 NULL
1 123 1/2/2007 2007 11.00 NULL
1 123 2/1/2007 2007 28.00 NULL
1 123 NULL 2008 28.00 552.00
1 123 3/1/2008 2008 99.00 NULL
1 123 4/1/2008 2008 36.00 NULL
1 123 4/2/2008 2008 55.00 NULL
1 123 4/3/2008 2008 89.00 NULL
The problem I'm having is how can I create a column that compares Fiscal 2007 and Fiscal 2008, i would like an additional column "Diff" that=-100 (452-552)
I've tried creating some different columns with CASE statements, but it seem ultimately, each row only knows about Monthly Sum and Yearly sum of the current date, I can't seem to jump around to get previous years sum.
Any Ideas?
Thanks!
Query below can do what you want, you just have check for datatypes in convert (as i don't know which datatypes you are using so i assumed varchar(20) as year).
I get the previous year total in isNUll() and subtract it from current total and then multplied the result with -100.
Select
store,
product,
FiscalMonth,
FIscalYear,
SUM(Amount),
(-100 * ((ISNULL((SELECT SUM(Amount) FROM sales WHERE FiscalYear = CONVERT(VARCHAR(20),(CONVERT(INT, s.FiscalYear) - 1)) AND FiscalMonth IS NULL Group by FIscalYear),0) -
CASE WHEN FiscalMonth IS NULL THEN SUM(Amount) ELSE NULL END
))) as Diff
From Sales
Group by store, product,
Grouping Sets(
(FiscalYear, FiscalMonth, product),
(FiscalYear, product)
)

Is it possible to group by a few different date periods in mysql?

There is a table likes:
like_user_id | like_post_id | like_date
----------------------------------------
1 | 2 | 1399274149
5 | 2 | 1399271149
....
1 | 3 | 1399270129
I need to make one SELECT query and count records for specific like_post_id by grouping according periods for 1 day, 7 days, 1 month, 1 year.
The result must be like:
period | total
---------------
1_day | 2
7_days | 31
1_month | 87
1 year | 141
Is it possible?
Thank you.
I have a created a query for Oracle syntax please change it according to your db
select '1_Day' as period , count(*) as Total
from likes
where like_date>(sysdate-1)
union
select '7_days' , count(*)
from likes
where like_date>(sysdate-7)
union
select '1_month' , count(*)
from likes
where like_date>(sysdate-30)
union
select '1 year' , count(*)
from likes
where like_date>(sysdate-365)
here idea is to get single sub query for single period and apply the filter in where to match the filter.
This code shows how to build a cross-tab style query that you will likely need. This aggregates by like_post_id and you may want to put restrictions on it. Further, in terms of last month I don't know whether you mean month to date, last 30 days or last calendar month so I've left that to you.
SELECT
like_post_id,
-- cross-tab example, rinse and repeat as required
-- aside of date logic, the SUM(CASE logic is designed to be ANSI compliant but you could use IF instead of CASE
SUM(CASE WHEN FROM_UNIXTIME(like_date)>=DATE_SUB(CURRENT_DATE(), interval 1 day) THEN 1 ELSE 0 END) as 1_day,
...
FROM likes
-- to restrict the number of rows considered
WHERE FROM_UNIXTIME(like_date)>=DATE_SUB(CURRENT_DATE(), interval 1 year)
GROUP BY like_post_id
To be flexible, simply make a table time_intervals which holds from_length and to_length in seconds:
CREATE TABLE time_intervals
( id int(11) not null auto_increment primary key,
name varchar(255),
from_seconds int,
to_seconds int
);
The select is then quite straight:
select like_post_id, ti.name as interval, count(*) as cnt_likes
from time_intervals ti
left /* or inner */ join likes on likes.like_post_id = 175
and likes.like_date between unix_timestamp(now()) - ti.to_seconds and unix_timestamp(now()) + ti.from_seconds
group by ti.id
With left join you get always all intervals (even when holes exist), with inner join only the intervals which exist.
So you change only table time_intervals and can get what you want. The "175" stands for the post you want, and of course you can change to where ... in () if you want.
Here is an alternative using CROSS JOIN. First, the time difference is calculated using the TIMESTAMPDIFF function and the appropriate parameter (DAY/WEEK/MONTH/YEAR). Then, if the counts are equal to 1, then the value is added up. Finally, the CROSS JOIN is made with an inline view containing the names of the periods.
SELECT
periods.period,
CASE periods.period
WHEN '1_day' THEN totals.1_day
WHEN '7_days' THEN totals.7_days
WHEN '1_month' THEN totals.1_month
WHEN '1_year' THEN totals.1_year
END total
FROM
(
SELECT
SUM(CASE days WHEN 2 THEN 1 ELSE 0 END) 1_day,
SUM(CASE weeks WHEN 1 THEN 1 ELSE 0 END) 7_days,
SUM(CASE months WHEN 1 THEN 1 ELSE 0 END) 1_month,
SUM(CASE years WHEN 1 THEN 1 ELSE 0 END) 1_year
FROM
(
SELECT
TIMESTAMPDIFF(YEAR, FROM_UNIXTIME(like_date), NOW()) years,
TIMESTAMPDIFF(MONTH, FROM_UNIXTIME(like_date), NOW()) months,
TIMESTAMPDIFF(WEEK, FROM_UNIXTIME(like_date), NOW()) weeks,
TIMESTAMPDIFF(DAY, FROM_UNIXTIME(like_date), NOW()) days
FROM likes
) counts
) totals
CROSS JOIN
(
SELECT
'1_day' period
UNION ALL
SELECT
'7_days'
UNION ALL
SELECT
'1_month'
UNION ALL
SELECT
'1_year'
) periods