Average a column based upon the value in another column SQL - mysql

Suppose I have the following data
SqlUnixTime BID ASK VALID ASSET_ID
1504900871 101.50 101.6 Y XY1
1504900870 0 101.6 Y XY1
1504900871 101.50 20 N XY1
...
In the BID & ASK columns I can have a valid price, a 0 (meaning no data) or an invalid price (see the final row).
I'd like to compute a 30 day average. I have managed to handle the 0 case using the following query:
Select ASSET_ID, AVG(NULLIF(BID,0)) as AVG_BID_30D, AVG(NULLIF(ASK,0)) as AVG_ASK_30D FROM myDB.myTable where SqlUnixTime > 1504900870 GROUP BY ASSET_ID;
However, how would I only average those values where VALID = "Y". I thought about putting a where clause in the end but then it might not select asset_id that are invalid? I just want it to have a null?

UPDATED
group it by (ASSET_ID, VALID='Y') and then the resultant again group by VALID='Y'
I think it will work.
select A.ASSET_ID, A.AVG_BID_30D, A.AVG_ASK_30D
from (Select ASSET_ID, AVG(NULLIF(BID,0)) as AVG_BID_30D, AVG(NULLIF (ASK,0)) as AVG_ASK_30D, VALID
FROM myDB.myTable where SqlUnixTime > 1504900870
GROUP BY ASSET_ID, VALID='Y') as A
group by ASSET_ID='Y';
.

Related

Is there a way to use aggregate COUNT() values within CASE?

I need to retrieve unique yet truncated part numbers, with their description values being conditionally determined.
DATA:
Here's some simplified sample data:
(the real table has half a million rows)
create table inventory(
partnumber VARCHAR(10),
description VARCHAR(10)
);
INSERT INTO inventory (partnumber,description) VALUES
('12345','ABCDE'),
('123456','ABCDEF'),
('1234567','ABCDEFG'),
('98765','ZYXWV'),
('987654','ZYXWVU'),
('9876543','ZYXWVUT'),
('abcde',''),
('abcdef','123'),
('abcdefg','321'),
('zyxwv',NULL),
('zyxwvu','987'),
('zyxwvut','789');
TRIED:
I've tried too many things to list here.
I've finally found a way to get past all the 'unknown field' errors and at least get SOME results, but:
it's SUPER kludgy!
my results are not limited to unique prods.
Here's my current query:
SELECT
LEFT(i.partnumber, 6) AS prod,
CASE
WHEN agg.cnt > 1
OR i.description IS NULL
OR i.description = ''
THEN LEFT(i.partnumber, 6)
ELSE i.description
END AS `descrip`
FROM inventory i
INNER JOIN (SELECT LEFT(ii.partnumber, 6) t, COUNT(*) cnt
FROM inventory ii GROUP BY ii.partnumber) AS agg
ON LEFT(i.partnumber, 6) = agg.t;
GOAL:
My goal is to retrieve:
prod
descrip
12345
ABCDE
123456
123456
98765
ZYXWV
987654
987654
abcde
abcde
abcdef
abcdef
zyxwv
zyxwv
zyxwvu
zyxwvu
QUESTION:
What are some cleaner ways to use the COUNT() aggregate data with a CASE type conditional?
How can I limit my results so that all prods are UNIQUE?
You can check if a left(partnumber, 6) is not unique in the result by checking if count(*) > 1. In such a case let descrip be left(partnumber, 6). Otherwise you can use max(description) (or min(description)) to get the single description but satisfy the needs to use an aggregation function on columns not in the GROUP BY. To replace empty or NULL descriptions, nullif() and coalesce() can be used.
That would lead to the following using just one level of aggregation and no joins:
SELECT left(partnumber, 6) AS prod,
CASE
WHEN count(*) > 1 THEN
left(partnumber, 6)
ELSE
coalesce(nullif(max(description), ''), left(partnumber, 6))
END AS descrip
FROM inventory
GROUP BY left(partnumber, 6)
ORDER BY left(partnumber, 6);
But there seems to be a bug in MySQL and this query fails. The engine doesn't "see" that, in the list after SELECT partnumber is only used in the expression left(partnumber, 6), which is also in the GROUP BY. Instead the engine falsely complains about partnumber not being in the GROUP BY and not subject to an aggregation function.
As a workaround, we can use a derived table, that does the shortening of partnumber to its first six characters. We then use use that column of the derived table instead of left(partnumber, 6).
SELECT l6pn AS prod,
CASE
WHEN count(*) > 1 THEN
l6pn
ELSE
coalesce(nullif(max(description), ''), l6pn)
END AS descrip
FROM (SELECT left(partnumber, 6) AS l6pn,
description
FROM inventory) AS x
GROUP BY l6pn
ORDER BY l6pn;
Or we slap some actually pointless max()es around the left(partnumber, 6) other than the first, to work around the bug.
SELECT left(partnumber, 6) AS prod,
CASE
WHEN count(*) > 1 THEN
max(left(partnumber, 6))
ELSE
coalesce(nullif(max(description), ''), max(left(partnumber, 6)))
END AS descrip
FROM inventory
GROUP BY left(partnumber, 6)
ORDER BY left(partnumber, 6);
db<>fiddle (Change the DBMS to some other like Postgres or MariaDB to see that they also accept the first query.)

Select Statements: Select referring previous select query

I have two Queries which work independently; I need to combine these results.
Fetch all fields (including wdate and Empid), from Fromdate to ToDate.
Calculate a value (for efficiency, Efc), for a specific wdate and Empid from First Query.
1st Query
SELECT *
FROM tblProductionEffcyDetails
WHERE wDate BETWEEN '06/26/2019' AND '07/25/2019'
AND worker = 'Techn'
ORDER BY Empid, wDate
2nd Query
SELECT Cast(ROUND(SUM(Tstdmin) / NULLIF(SUM(TAvlblmin), 0) * 100,0) as int) AS [Efc]
FROM tblProductionEffcyDetails
WHERE wDate='07/11/2019'
AND Empid='00021'
GROUP BY wdate, Empid
That is, in this 2nd Query, the values for wDate and Empid should come from the results of the 1st Query.
Notes on the data/ table:
Any particular date (wDate) or person (Empid) can have any number
of entries.
Efficiency (Efc) should be given just once per day
(wDate) i.e. it should not have multiple values for a particular wDate.
Table structure is as below
SL wDate Avlbl_Mins NP_Mins Empid Name Process Model Efc
117571 7/13/2019 0 0 21 MARRY Block removing 900-2930 80
117572 7/13/2019 0 0 21 MARRY Microscope checking 900-2929 Null
116872 6/26/2019 430 75 52 SUGANTHI Slab removing 900-2929 75
116873 6/26/2019 0 0 52 SUGANTHI Slab Removing 900-2528 Null
Try this,
you can get the particular Empid set for date between 06/26/2019 and 07/25/2019 using your 1st query and by connecting it to your second query you can sum it up.
SELECT Empid, Cast(ROUND(SUM(Tstdmin) / NULLIF(SUM(TAvlblmin), 0) * 100,0) as int) AS [Efc]
FROM tblProductionEffcyDetails t
WHERE t.Empid in (
SELECT Empid
FROM tblProductionEffcyDetails tb
WHERE tb.wDate BETWEEN '06/26/2019' AND '07/25/2019'
AND tb.worker = 'Techn') and t.wDate BETWEEN '06/26/2019' AND '07/25/2019'
GROUP BY t.wdate, t.Empid
I made the query as I understood your question, let me know if it didn't get the output you want. so I can change the answer.
Hope this helps.
SELECT Empid, Cast(ROUND(SUM(Tstdmin) / NULLIF(SUM(TAvlblmin), 0) * 100,0) as
int) AS [Efc]
FROM (
SELECT *
FROM tblProductionEffcyDetails tb1
WHERE tb1.wDate BETWEEN '06/26/2019' AND '07/25/2019'
AND tb1.worker = 'Techn') as t
GROUP BY t.wdate, t.Empid

Selecting rows until a column value isn't the same

SELECT product.productID
, product.Name
, product.date
, product.status
FROM product
INNER JOIN shelf ON product.sheldID=shelf.shelfID
WHERE product.weekID = $ID
AND product.date < '$day'
OR (product.date = '$day' AND shelf.expire <= '$time' )
ORDER BY concat(product.date,shelf.expire)
I am trying to stop the SQL statement at a specific value e.g. bad.
I have tried using max-date, but am finding it hard as am making the time stamp in the query. (Combining date/time)
This example table shows that 3 results should be returned and if the status "bad" was the first result than no results should be returned. (They are ordered by date and time).
ProductID Date status
1 2017-03-27 Good
2 2017-03-27 Good
3 2017-03-26 Good
4 2017-03-25 Bad
5 2017-03-25 Good
Think I may have fixed it, I added this to my while loop.
The query gives the results in order by present to past using date and time, this while loop checks if the column of that row is equal to 'bad' if it is does something (might be able to use an array to fill it up with data). If not than the loop is broken.
I know it doesn't seem ideal but it works lol
while ($row = mysqli_fetch_assoc($result)) {
if ($row['status'] == "bad") {
$counter += 1;
}
else{
break;}
I will provide an answer just with your output as if it was just one table. It will give you the main ideia in how to solve your problem.
Basically I created a column called ord that will work as a row_number (MySql doesn't support it yet AFAIK). Then I got the minimum ord value for a bad status then I get everything from the data where ord is less than that.
select y.*
from (select ProductID, dt, status, #rw:=#rw+1 ord
from product, (select #rw:=0) a
order by dt desc) y
where y.ord < (select min(ord) ord
from (select ProductID, status, #rin:=#rin+1 ord
from product, (select #rin:=0) a
order by dt desc) x
where status = 'Bad');
Result will be:
ProductID dt status ord
-------------------------------------
1 2017-03-27 Good 1
2 2017-03-27 Good 2
3 2017-03-26 Good 3
Also tested with the use case where the Bad status is the first result, no results will be returned.
See it working here: http://sqlfiddle.com/#!9/28dda/1

MySql query counting and assigning constants based on ranges

There's one table
MEMBERS:
id - member id,
dt_activate - member's join unix datetime,
id_inviter - member's id who did invite current member (0 if member joined on his own)
Trying to write a Select that would return the following multiple rows result, where each row would contain:
(1) id - member id;
(2) cnt - count of rows in MEMBERS table where this "id" appeared in id_inviter field and at the same time the dt_activate should be between constants dt_start and dt_end;
(3) img - "imageA.gif" if cnt is between 10-19, "imageB.gif" if between 20-29, "imageC.gif" if greater than 29;
(4) reward - constant X if cnt is between 10-19, Y if between 20-29, Z if greater than 29
Unfortunately my knowledge doesn't let me go past the simple selects like:
SELECT id_inviter, sum(1) AS cnt
FROM members
WHERE dt_activate>=$dt_start AND dt_activate<=$dt_end
GROUP BY id_inviter
HAVING cnt>=10
Estimated record count for MEMBERS table might be between 10-50k.
Please help to achieve the result described above, thank you.
Try this one:
SELECT id_inviter, count (*) AS cnt,
case when count (*) <20 then 'imageA.gif'
when count (*) <30THEN 'imageB.gif' else 'imageC.gif' end as image,
case when count (*) <20 then 'X'
when count (*) <30 THEN 'Y' else 'Z' end as reward
FROM members
WHERE dt_activate>=$dt_start AND dt_activate<=$dt_end
GROUP BY id_inviter
HAVING cnt>=10

MySQL Complicated SELECT

I have a MySQL table (tbl_filters) with 3 columns: id, cat, val
id & val are numeric, cat is varchar. There are multiple rows for each id.
I also have another table (tbl_info) with multiple columns, including an id which corresponds to the id from tbl_filters. There is a column called name, which is what I'm looking for.
I would like to select the name of all the rows which match a set value for cat, but only if the val for cat is the maximum for this id, and only if it is above a minimum set val.
In pseudocode it would be something like:
SELECT tbl_info.name FROM tbl_info,tbl_filters
WHERE (tbl_info.id=tbl_filters.id) AND (cat="mycat") AND (val>=0.3)
AND (there are no other rows for this id in tbl_info with a higher value for val)
Example:
tbl_filters
id,cat,val
1 eg1 0.43
1 eg2 0.60
1 eg3 0.78
tbl_info
id name
1 MyName
In the above example, a value should only be returned if I am looking for the cat called eg3, since that has the highest value. For the other cats, nothing should be returned, since they are not the highest value.
Another option would be to make a column in tbl_info just for the cat with the highest value, but that is a messy solution I would prefer to avoid.
I THINK I'm following you... The INNER-MOST query pre-qualifies the HIGHEST Value per ID of your minimum value qualification, and the category that qualifies. ONCE you get that list, re-join back to get the name from the tbl_info. I've re-joined to the tbl_filters a second time in case there were other elements on that record you want, such as the date of the rate, or other things. If you DONT need that, you can ignore the second "tf2" join and just change the fields list from tf2.val to PreQualified.HighestQualVal.
select
ti.id,
ti.name,
tf2.val
from
( select
tf.id,
max( tf.val ) as HighestQualVal
from
tbl_filters tf
where
tf.cat = "mycat"
and tf.val >= 0.3
) PreQualified
JOIN tbl_info ti
on PreQualified.id = ti.id
JOIN tbl_filters tf2
on PreQualified.id = tf2.id
AND PreQualified.HighestQualVal = tf2.val
What about?
select ti.name, MaxId.maxVal from
(select tf1.id, tf1.cat, max(tf1.val) as maxVal from tbl_filters1 tf1
where tf1.cat = 'eg3' and tf1.val >= 0.0
group by tf1.id, tf1.cat) MaxCat
inner join (
select tf2.id, max(tf2.val) as maxVal from tbl_filters2 tf2
group by tf2.id) MaxId
on (MaxCat.id = MaxId.id and MaxCat.maxVal = MaxId.maxVal)
inner join tbl_info ti on MaxId.id = ti.id
Example here
Basically, and if I'm not wrong (again), I'm getting all the maximum val per each id and cat pair. Then get the maximum val for each id. If both match, i.e. if the max for the cat is the same as the max for the whole id, then I return the results.
Feel free to correct me if I'm wrong.