MySQL - Multiply column by value depending on that column - mysql

How can I write this.. I have table 'Company' with a column 'Size'. The size references enums. I need to display the average company size as alias AS 'AverageEstimatedCompanySize' by substituting column 'Size' when column 'Size' is:
1 = 15
2 = 30
3 = 50
4 = 100
5 = 250
In other words, my table shows company size as either 1, 2, 3, 4 or 5. While 1 is actually a company size of 15.
This is all part of a bigger query:
SELECT COUNT(DISTINCT(ID)) AS 'Total # of Opps', AVG(Size*?) AS 'AverageEstimatedCompanySize'
FROM persontable AS POJT INNER JOIN opportunity
ON POJT.ID = opportunity.id
WHERE opportunity.TimeStamp >= '2012-01-01' AND opportunity.TimeStamp <= '2012-12-31' AND POJT.JobTitleID IN
(SELECT Id
FROM job
WHERE CategoryID IN
(SELECT id
FROM job_category
WHERE name IN ('Sc', 'Ma', 'Co', 'En', 'Tr')))

Sounds like something solvable with a case statement. The following is untested but should point you in the right direction.
SELECT
COUNT(DISTINCT(ID)) AS 'Total # of Opps',
AVG(
CASE Size
WHEN 1 THEN 15
WHEN 2 THEN 30
WHEN 3 THEN 50
WHEN 4 THEN 100
WHEN 5 THEN 250
END
) AS 'AverageEstimatedCompanySize'
FROM persontable AS POJT INNER JOIN opportunity
ON POJT.ID = opportunity.id
WHERE opportunity.TimeStamp >= '2012-01-01' AND opportunity.TimeStamp <= '2012-12-31' AND POJT.JobTitleID IN
(SELECT Id
FROM job
WHERE CategoryID IN
(SELECT id
FROM job_category
WHERE name IN ('Sc', 'Ma', 'Co', 'En', 'Tr')))

I'm thinking that one approach might be to modify the query to JOIN to the Company table appropriately (that's something you'll need to work out), and then modify the AVG statement:
... AVG(CASE `Size`
WHEN 1 THEN 15
WHEN 2 THEN 30
WHEN 3 THEN 50
WHEN 4 THEN 100
WHEN 5 THEN 250 END) AS 'AverageEstimatedCompanySize'
where Size is from the Company table.
Now, a more dynamic approach would be to create a new field, or even a new table, that maps those sizes and just JOIN the Company table and say the new table in the query and just grab the appropriate field per row then. That would get rid of the CASE statement.

Related

MySQL (version lower then 8.0) : Select where date value and return a row of dates from table of milestone date

Table a (milestone) :
Pk_id
emp_id
milestone_date
1
2
2022-01-17
2
2
2021-03-23
3
2
2018-06-29
4
3
2018-01-15
5
3
2016-02-17
...
...
...
Table b :
PK_id
Emp_id
ins_date
1
2
2022-01-20
2
2
2019-03-30
3
3
2017-06-29
My problem is I want to select the floor date row of Table A.but I don't know how to use the command.
The method is, in each row of Table B. Determine the column of ins_date and emp_id. Then use that value to select the date in Table A that is the floor date (I don't know how to explain it. But try to understand in the example below). and then show the results in the list.
for example
Row 1: Table B. The ins_date value is '2022-01-20' and emp_id is 2. Consider emp_id = 2 and the floor date of '2022-01-20' (Because Greater than 2022-01-20 ), so select the item PK_ID = 1.
Row 2: Table B. The ins_date value is '2019-03-30' and emp_id is 2. Consider emp_id = 2 and the floor date of '2018-06-29' (Because Greater than 2018-06-29 but less than 2021-03-23 ), so select item PK_ID = 3.
Row 3: Table B. The ins_date value is '2017-06-29' and emp_id is 3. Consider emp_id = 3 and the floor date of '2016-02-17' (Because Greater than 2016-02-17 but less than 2018-01-15 ), so select the item PK_ID = 5.
Thank you and Sorry my English.
P.S. I don't know how I can explain the question and details. But if you edit the text to make it easier to understand. I strongly allow editing.
You are looking for the maximum milestone_date that is less or equal to an ins_date.
Two ways to get that date:
option #1
select b.*, max(a.milestone_date) as floor_milestone_date
from b
join a on a.emp_id = b.emp_id and a.milestone_date <= b.ins_date
group by b.pk_id
order by b.pk_id;
option #2
select
b.*,
(
select max(a.milestone_date)
from a
where a.emp_id = b.emp_id and a.milestone_date <= b.ins_date
) as floor_milestone_date
from b;
The difference between the two queries above: When there is no floor milestone date for an ins_date, then the first query doesn't return the b row, while the second query returns the b row with a null milestone date.
Now, if you want more information from table a, then join the table to one of the queries above.
The final query
select ab.*, a.pk_id
from
(
select b.*, max(a.milestone_date) as floor_milestone_date
from b
join a on a.emp_id = b.emp_id and a.milestone_date <= b.ins_date
group by b.pk_id
) ab
join a on a.emp_id = ab.emp_id and a.milestone_date = ab.floor_milestone_date
order by ab.pk_id;

How to find a best of 2 in 3 column and average of those 2

In my database I have a table analog with columns like id, name, IA1, IA2, IA3 and average.
In the average column I need to set the average of the highest two values in IA1, IA2 and IA3.
For example:
anil has 20, 15 and 28 in IA1, IA2, IA3 respectively so average will be (20+28)/2 i.e. 24
ashwin has 21, 30 and 28 in IA1, IA2, IA3 respectively so average will be (30+28)/2 i.e. 29
I tried this query but I got only the greatest of those values:
update analog set average = greatest (IA1,IA2,IA3);
Please help
Since you have only three values, you can subtract the LEAST of the 3 from the sum of all of them and then divide that by 2 to get the average of the two largest values:
UPDATE analog
SET average = (IA1 + IA2 + IA3 - LEAST(IA1, IA2, IA3)) / 2
Demo on dbfiddle
Update
#GordonLinoff raises a good point that if it's possible that any of the IA* values could be NULL, you can use COALESCE to convert them to a valid integer (0 is probably the most appropriate assuming that the values would normally be positive) so that you don't end up with NULL values in the average column:
UPDATE analog
SET average = (COALESCE(IA1,0) + COALESCE(IA2,0) + COALESCE(IA3,0) -
LEAST(COALESCE(IA1,0), COALESCE(IA2,0), COALESCE(IA3,0))) / 2
Demo on dbfiddle
if you have only 3 values then you could try using a union for the 3 columns and
update analog a
inner join (
select id, (sum(IA) - min(IA))/2 val
from (
select id, IA1 IA
from analog union all
select id, IA2
from analog union all
select id, IA3
from analog
) t1
group by id
) t2 ON a.id = t2.id
set a.average = t2.val

Use COALESCE within SUM in MySQL SELECT statement

I have the following tables:
Table users
id name base_discount
1 jack 10
2 michael 20
3 richard 30
Table item
id name category_id price
1 hammer 1 10
2 knife 2 15
3 spoon 2 12
4 plate 3 20
5 tree 4 400
Table category
id name
1 tools
2 kitchen
3 dishes
4 garden
Table discount_category
id user_id category_id discount
1 1 1 20
2 1 3 25
3 3 3 10
4 1 2 15
Table discount_item
id user_id item_id discount
1 2 1 50
2 1 2 50
Now what I want to achieve. I want to attach the discount per item that a user has to the correct item. If that is not available (NULL) I want to attach the discount per category that a user has. And if that is not available (NULL), I want to attach the base discount that a user has. With the discount I then calculate the new price of the item. However, when I try using COALESCE() within SUM() I get a syntax error. What am I doing wrong?
Below is my current query:
SELECT item.id, item.name, category.id,
category.name AS category_name, item.price, SUM((100 -
COALESCE(
(
SELECT discount_item.discount
FROM discount_item
INNER JOIN users ON discount_item.user_id = users.id
WHERE users.id = '1' AND discount_item.item_id = item.id
),
(
SELECT discount_category.discount
FROM discount_category
INNER JOIN users ON discount_category.user_id = users.id
WHERE users.id = '1' AND discount_category.category_id = item.category_id
),
(
SELECT users.base_discount
FROM users
WHERE users.id = '1'
)
)) / 100 * item.price) AS new_price,
FROM item
INNER JOIN category ON item.category_id = category.id;
Please also see the below link for an SQL Fiddle (couldn't do it on sqlfiddle.com as it wouldn't load). In the example I have appended a suffix to each table name.
http://rextester.com/LCCKSD59098
You have an extra comma after new_price and before FROM ITEM, hence the error.
Rextester Demo
Do not select other columns in select if you are not using them in group by, as in other relational RDBMS, you will get error.
Also use alias for table names for better readibily and to avoid confusion.

selecting max record in mysql

I have a table of multiple transactions. I am trying to get the row of the last transaction.
I am using the following:
select n.AccountNumber, max(PostDate), f.TransAmt
from mp_cycle n, fintrans f
where n.AccountNumber = f.AccountNumber
and TransCode >= '50'
and TransCode <= '59'
group by n.AccountNumber
This is returning the last date for a particular account, but the TransAmt is not for the same record.
ie:
Acct # Date Amt
1 1/1 10.00
1 1/2 11.00
1 1/3 12.00
2 1/2 20.00
2 1/3 21.00
2 1/4 22.00
My select will return the last date for each account, so 1/3 for act # 1 and 1/4 for act # 2, but the Amt field is not the amt that goes with that record.
Any assistance will be greatly appreciated.
There are many ways to solve this problem, one is by joining extra subquery which separate gets the latest PostDate for every AccountNumber. The result of the subquery will then be joined on the other table provided that it should match on two columns: AccountNumber and PostDate.
SELECT a.*, b.*
FROM mp_cycle a
INNER JOIN fintrans b
ON a.AccountNumber = b.AccountNumber
INNER JOIN
(
SELECT AccountNumber, MAX(PostDate) max_date
FROM fintrans
GROUP BY AccountNumber
) c ON b.AccountNumber = c.AccountNumber AND
b.PostDate = c.max_date
-- WHERE ..your conditions here..

Query to add missing rows using values from prior period

I have a record set for inspections of many pieces of equipment. The four cols of interest are equip_id, month, year, myData.
My requirement is to have EXACTLY ONE record per month for each piece of equipment.
I have a query that makes the data unique over equip_id, month, year. So there is no more than one record for each month/year for a piece of equipment. But now I need to simulate data for the missing month. I want to simply go back in time to get the last piece of my data.
So that may seem confusing, so I'll show by example.
Given this sample data:
equip_id month year myData
-----------------------------
1 1 2010 500
1 2 2010 600
1 5 2010 800
2 2 2010 300
2 4 2010 400
2 6 2010 500
I want this output:
equip_id month year myData
-----------------------------
1 1 2010 500
1 2 2010 600
1 3 2010 600
1 4 2010 600
1 5 2010 800
2 2 2010 300
2 3 2010 300
2 4 2010 400
2 5 2010 400
2 6 2010 500
Notice that I'm filling in missing data with the data from the month (or two months etc.) before. Also note that if the first record for equip 2 is in 2/2010 than I don't need a record for 1/2010 even though I have one for equip 1.
I just need exactly one record for each month/year for each piece of equipment. So if the record does not exist I just want to go back in time and grab the data for that record.
Thanks!
By no means perfect:
SELECT equip_id, month, mydata
FROM (
SELECT equip_id, month, mydata FROM equip
UNION ALL
SELECT EquipNum.equip_id, EquipNum.Num,
(SELECT Top 1 mydata
FROM equip
WHERE equip.month<n.num And equip.equip_id=equipnum.equip_id
ORDER BY equip.month desc) AS Data
FROM
(SELECT e.equip_id, n.Num
FROM
(SELECT DISTINCT equip_id FROM equip) AS e,
Numbers AS n) AS EquipNum
LEFT JOIN equip
ON (EquipNum.Num = equip.month)
AND (EquipNum.equip_id = equip.equip_id)
WHERE EquipNum.Num<DMax("month","equip")
AND
(SELECT top 1 mydata
FROM equip
WHERE equip.month<n.num And equip.equip_id=equipnum.equip_id
ORDER BY equip.month desc) Is Not Null
AND equip.equip_id Is Null AND equip.Month Is Null) AS x
ORDER BY equip_id, month
For this to work you need a Numbers table, in this case it needs only hold integers from 1 to 12. The numbers table I used is called Numbers and the field is called Num.
EDIT re years comment
SELECT equip_id, year, month, mydata
FROM (
SELECT equip_id, year, month, mydata FROM equip
UNION ALL
SELECT en.equip_id, en.year, en.Num, (SELECT Top 1 mydata
FROM equip e
WHERE e.month<n.num And e.year=en.year And e.equip_id=en.equip_id
ORDER BY e.month desc) AS Data
FROM (SELECT e.equip_id, n.Num, y.year
FROM
(SELECT DISTINCT equip_id FROM equip) AS e,
Numbers AS n,
(SELECT DISTINCT year FROM equip) AS y) AS en
LEFT JOIN equip AS e ON en.equip_id = e.equip_id
AND en.year = e.year
AND en.Num = e.month
WHERE en.Num<DMax("month","equip") AND
(SELECT Top 1 mydata
FROM equip e
WHERE e.month<n.num And e.year=en.year And e.equip_id=en.equip_id
ORDER BY e.month desc) Is Not Null
AND e.equip_id Is Null
AND e.Month Is Null) AS x
ORDER BY equip_id, year, month
I've adjusted to account for year and month... The primary principles remain the same as the original queries presented where just the month. However, for applying a month and year, you need to test for the SET of YEAR + MONTH, ie: what happens if Nov/2009, then jump to Feb/2010, You can't rely on just a month being less than another, but the "set". So, I've apply the year * 12 + month to prevent a false value such as Nov=11 + year=2009 = 2009+11 = 2020, then Feb=2 of year=2010 = 2010+2 = 2012... But 2009*12 = 24108 + Nov = 11 = 24119 compared to 2010*12 = 24120 + Feb =2 = 24122 -- retains proper sequence per year/month combination. The rest of the principles apply. However, one additional, I created a table to represent the span of years to consider. For my testing, I added a sample Equip_ID = 1 entry with a Nov-2009, and Equip_ID = 2 with a Feb-2011 entry and the proper roll-over works too. (Table C_Years, column = year and values of 2009, 2010, 2011)
SELECT
PYML.Equip_ID,
PYML.Year,
PYML.Mth,
P1.MyData
FROM
( SELECT
PAll.Equip_ID,
PAll.Year,
PAll.Mth,
( SELECT MAX( P1.Year*12+P1.Mth )
FROM C_Preset P1
WHERE PAll.Equip_ID = P1.Equip_ID
AND P1.Year*12+P1.Mth <= PAll.CurYrMth) as MaxYrMth
FROM
( SELECT
PYM1.Equip_ID,
Y1.Year,
M1.Mth,
Y1.Year*12+M1.Mth as CurYrMth
FROM
( SELECT p.equip_id,
MIN( p.year*12+p.mth ) as MinYrMth,
MAX( p.year*12+p.mth ) as MaxYrMth
FROM
C_Preset p
group by
1
) PYM1,
C_Years Y1,
C_Months M1
WHERE
Y1.Year*12+M1.Mth >= PYM1.MinYrMth
AND Y1.Year*12+M1.Mth <= PYM1.MaxYrMth
) PAll
) PYML,
C_Preset P1
WHERE
PYML.Equip_ID = P1.Equip_ID
AND PYML.MaxYrMth = P1.Year*12+P1.Mth
If this is going to be a repetative thing/report, I would just create a temporary table with 12 months -- then use that as the primary table, and do a left OUTER join to the rest of your data. This way, you know you'll always get every month, but only when a valid join to the "other side" is identified, you'll get that data too. Ooops... missed your point about the filling in missing elements from the last element... Thinking...
The following works... and I'll describe the elements to what is going on. First, I created a temp table "C_Months" with a column Mth (month) with numbers 1-12. I used "Mth" as an abbreviation of Month to not cause possible conflict with POSSIBLE reserved word MONTH. Additionally, in my query, the table reference "C_Preset" is the prepared set of data you mentioned you already have of distinct elements.
SELECT
LVM.Equip_ID,
LVM.Mth,
P1.Year,
P1.MyData
FROM
( SELECT
JEM.Equip_ID,
JEM.Mth,
( SELECT MAX( P.Mth )
FROM C_Preset P
WHERE P.Equip_ID = JEM.Equip_ID
AND P.Mth <= JEM.Mth ) as MaxMth
FROM
( SELECT distinct
p.equip_id,
c.mth
FROM
C_months c,
C_Preset p
group by
1, 2
HAVING
c.mth >= MIN( p.Mth )
and c.mth <= MAX( p.Mth )
ORDER BY
1, 2 ) JEM
) LVM,
C_Preset P1
WHERE
LVM.Equip_ID = P1.Equip_ID
AND LVM.MaxMth = P1.Mth
ORDER BY
1, 2
The inner most query is a query of the available months (C_Months) associated with a given equipment ID. In your example, equipment ID 1 had a values of 1,2,5. So this would return 1, 2, 3, 4, 5. And for Equipment ID 2, it started with 2, but ended with 6, so it would return 2, 3, 4, 5, 6. Hence the aliased reference JEM (Just Equipment Months)
Then, the field selection for MaxMth (Maximum month)... This is the TRICKY ONE
( SELECT MAX( P.Mth )
FROM C_Preset P
WHERE P.Equip_ID = JEM.Equip_ID
AND P.Mth <= JEM.Mth ) as MaxMth
From this, stating I want the maximum month AVAILABLE (from JEM) associated with the given equipment that is AT OR LESS than the month In question (detecting the highest "valid" equipment item/month within the qualified list. The result of this would result in...
Equip_ID Mth MaxMth
1 1 1
1 2 2
1 3 2
1 4 2
1 5 5
2 2 2
2 3 2
2 4 4
2 5 4
2 6 6
So, for your example of ID = 1, you had months 1, 2, 5 (3 and 4 were missing), so the last valid month that 3 and 4 would refer to is sequence #2's month. Likewise for ID = 2, you had months 2, 4 and 6... Here, 3 would refer back to 2, 5 would refer back to 4.
The rest is the easy part. Now, we join your LVM (Last Valid Month) result as shown above to your original C_Preset (less records). But since we now have the last valid month to directly associate to an existing record in the C_Preset, we join by equipment id and the MaxMth colum, and NOT THE ACTUAL month.
Hope this helps... Again, you'll probably have to change my "mth" column references to "month" to match your format.