MySQL Query output using if else or case..? - mysql

I have a table as
mysql> select * FROM testa;
+---------+-------+
| month_x | money |
+---------+-------+
| 11101 | 12345 |
| 11105 | 100 |
| 11105 | 100 |
| 11105 | 100 |
| 11105 | 100 |
| 11106 | 12345 |
+---------+-------+
6 rows in set (0.00 sec)
where last two digits in the month_x are months now i want my output as
Month TOTAL
01 12345
02 0
03 0
04 0
05 400
06 12345
07 0
08 0
09 0
10 0
11 0
12 0
IS possible using the If else or case.

You can use modular arithmetic to obtain the trailing two digits (they're the remainder when the number is divided by 100), then assuming you wish to sum money when your data is "grouped by" month:
SELECT month_x % 100 AS Month, SUM(money) AS TOTAL
FROM testa
GROUP BY Month
ORDER BY Month ASC;
Alternatively, you could use rely on MySQL's implicit type conversion and use its string functions:
SELECT RIGHT(month_x, 2) AS Month, SUM(money) AS TOTAL
FROM testa
GROUP BY Month
ORDER BY Month ASC;
UPDATE
As #shiplu.mokadd.im states, to show every month (even those for which you have no data), you need to obtain numbers 1 through 12 from a temporary table. However, you can create such a temporary table in your query using UNION:
SELECT 1
UNION SELECT 2
UNION SELECT 3 -- etc
Therefore:
SELECT Month, Sum(money) AS TOTAL
FROM testa
RIGHT JOIN (
SELECT 1 AS Month
UNION SELECT 2 UNION SELECT 3 UNION SELECT 4 UNION SELECT 5 UNION SELECT 6
UNION SELECT 7 UNION SELECT 8 UNION SELECT 9 UNION SELECT 10 UNION SELECT 11
UNION SELECT 12
) months ON testa.month_x % 100 = months.Month
GROUP BY Month;
HOWEVER I would note that usually one doesn't usually do this in the database, as it really belongs in the presentation layer: from whatever language you're accessing the database, you'd loop over 1...12 and assume TOTAL to be 0 if there's no corresponding record in the resultset.

For this you need to create a table first with months' numeric value in it.
CREATE TABLE `months` (
`mon` int(11) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8
INSERT INTO `months` VALUES (1), (2), (3), (4), (5), (6), (7), (8), (9), (10), (11), (12);
Then execute this query,
SELECT m.mon,
IF(Sum(t.money) IS NULL, 0, Sum(t.money)) AS `money`
FROM testa t
RIGHT OUTER JOIN months m
ON ( t.month_x%100 = m.mon )
GROUP BY m.mon;
Result is,
+------+-------+
| mon | money |
+------+-------+
| 1 | 12345 |
| 2 | 0 |
| 3 | 0 |
| 4 | 0 |
| 5 | 400 |
| 6 | 12345 |
| 7 | 0 |
| 8 | 0 |
| 9 | 0 |
| 10 | 0 |
| 11 | 0 |
| 12 | 0 |
+------+-------+

You can use IF statements - yes.
Look # this:
http://dev.mysql.com/doc/refman/5.5/en/if-statement.html

Related

Group Rows in group by though it contains NULL value in mysql / postgres

I have a table from where I am getting month names and some quantity measures.
Table Name = Month_Name
SELECT month_name,q1,q2 FROM month_name;
mysql> SELECT * FROM MONTH;
+------------+------+------+
| month_name | q1 | q2 |
+------------+------+------+
| January | 10 | 20 |
| March | 30 | 40 |
| March | 10 | 5 |
+------------+------+------+
Expected Output:
mysql> SELECT month_name ,SUM(q1),SUM(q2) FROM MONTH GROUP BY month_name;
+------------+---------+---------+
| month_name | sum(q1) | sum(q2) |
+------------+---------+---------+
| January | 10 | 20 |
| Febuary | 0 | 0 |
| March | 40 | 45 |
| April | 0 | 0 |
+------------+---------+---------+
Group by month will not print February and April since these 2 months are not present in base table. I do not want to use Union All since there will be performance issues with union All, Is there any other optimised approach to this.
You can use a calendar table which keeps track of all the month names which you want to appear in your report.
SELECT
m1.month_name,
SUM(q1) AS q1_sum,
SUM(q2) AS q2_sum
FROM
(
SELECT 'January' AS month_name UNION ALL
SELECT 'February' UNION ALL
SELECT 'March' UNION ALL
...
SELECT 'December'
) m1
LEFT JOIN month m2
ON m1.month_name = m2.month_name
GROUP BY
m1.month_name;
Note that while this solve your immediate problem, it is still not ideal, because we don't have any easy way to sort the months. A much better table design would be to maintain a date column. The month name is easily derived from the date.

Mysql Query with multiple subqueries with group by distinct condition

Hi I am a PHP Developer weak in MySQL Medium complex queries make my head fired.
The below is the table vulnerability.
+-----------------------------------------------------------------------------------------------+
| id | webisite_id | low_count| high_count | medium_count | date_time | vul_date |
+-----------------------------------------------------------------------------------------------+
| 20 | 6 | 1 | 1 | 1 | 2018-07-04 09:14:04 | 2018-02-01 |
| 19 | 6 | 30 | 30 | 30 | 2018-07-04 09:13:38 | 2018-01-30 |
| 18 | 6 | 1 | 1 | 1 | 2018-07-04 09:13:16 | 2018-01-01 |
+-----------------------------------------------------------------------------------------------+
This table represent count of low, medium, high - vulnerability count for each website in database. We can enter as many entries for each websites. But the only relevant entry for a website is the latest entry in each month (based on vul_date).
Here I need help I want query which fetch sum of counts low, high, medium of each websites of each month of last 1 year, for example if -> website with id 1 has 1, 2, 3 low, high, medium number of vulnerabilities, on month June and
-> that of with id 2 has 7, 8, 9 respectively the result should be for June 8, 10, 12. And like last 1 year's each month result should be get. If no entry it should be 0.
Note that the entries should be the maximum value of vul_date and if a site has multiple entries on the same vul_date get the latest date_time entry.
I tried to write question as much as simple. hope the question is understood.
Please help me on this
Thanks in advance.
I think below query will work for you.
SELECT
SUM(low_count),
SUM(medium_count),
SUM(high_count),
MONTH(vul_date)
FROM
(SELECT
low_count, medium_count, high_count, vul_date, date_time
FROM
test
WHERE
(website_id , vul_date) IN (SELECT website_id, MAX(vul_date)
FROM test GROUP BY website_id , MONTH(vul_date))) t
WHERE
date_time IN (SELECT MAX(date_time) FROM test GROUP BY website_id , vul_date)
GROUP BY MONTH(vul_date);
What it does is, first finds the latest entry month wise for each website id which is your max vul_date.
SELECT website_id, MAX(vul_date)
FROM test GROUP BY website_id , MONTH(vul_date)
If there are more than one entry for a vul_date, it uses date_time to select maximum value from them. Finally it sums all website date after grouping it month wise.
You can change the above query to get 0 value for those months where there is no entry for any websites.
DROP TABLE IF EXISTS T;
CREATE TABLE T(id INT, website_id INT, low_count INT, high_count INT, medium_count INT, date_time DATETIME, vul_date DATE);
INSERT INTO T VALUES
( 20 , 6 , 1 , 1 , 1, '2018-07-04 09:14:04' , '2018-02-01'),
( 19 , 6 , 30, 30, 30, '2018-07-04 09:13:38' , '2018-01-30'),
( 18 , 6 , 2 , 2 ,2 , '2018-07-04 09:13:16' , '2018-01-01'),
( 17 , 6 , 2 , 2 ,2 , '2018-07-04 09:12:01' , '2018-01-01'),
( 90 , 1,1,2,3,'2017-07-05 01:00:00',' 2017-07-06'),
( 90 , 2,8,9,10,'2017-07-05 01:00:00',' 2017-07-06');
select coalesce(c.yyyymm,d.yyyymm) yyyymm,
coalesce(c.lo,0) lo,
coalesce(c.hi,0) hi,
coalesce(c.med,0) med
from
(
SELECT concat(year(a.vul_date),'-',month(a.vul_date)) yyyymm,
SUM(LOW_COUNT) lo,SUM(HIGH_COUNT) hi,sum(medium_count) med
from
(
select website_id,date_time,vul_date
from t
where date_time = (select max(date_time) from t t1 where t1.website_id = t.website_id and t1.vul_date = t.vul_date)
) a
join
(select website_id, date_time,vul_date,
LOW_COUNT,HIGH_COUNT,medium_count
from t) b
on b.website_id = a.website_id and b.date_time = a.date_time
group by concat(year(a.vul_date),'-',month(a.vul_date))
) c
right join
(select distinct concat(year(dte),'-',month(dte)) yyyymm from dates d
where dte between date_sub(now(), interval 1 year) and now()
) d on d.yyyymm = c.yyyymm
;
Sub query a get the vul_date with the most recent data_time which is then self joined, aggregated and then infilled with missing dates using a right join to a dates/calender table. If you don't have a dates/calender it would be useful for this kind of exercise nut there are alternatives which you can find in SO.
Result
+---------+------+------+------+
| yyyymm | lo | hi | med |
+---------+------+------+------+
| 2017-7 | 9 | 11 | 13 |
| 2017-8 | 0 | 0 | 0 |
| 2017-9 | 0 | 0 | 0 |
| 2017-10 | 0 | 0 | 0 |
| 2017-11 | 0 | 0 | 0 |
| 2017-12 | 0 | 0 | 0 |
| 2018-1 | 32 | 32 | 32 |
| 2018-2 | 1 | 1 | 1 |
| 2018-3 | 0 | 0 | 0 |
| 2018-4 | 0 | 0 | 0 |
| 2018-5 | 0 | 0 | 0 |
| 2018-6 | 0 | 0 | 0 |
| 2018-7 | 0 | 0 | 0 |
+---------+------+------+------+
13 rows in set (0.04 sec)

mysql - can i differentiate values of same column?

Is there a way to make two or more same values in the same column to be different by database engine?
Imagine that I have column like this:
id | votes
----------
1 | 20
2 | 20
3 | 19
4 | 16
5 | 15
I have to make all the votes different in that way that i increment first of two equal values, and substract other one.
After one iteration my DB should look like this:
----------
1 | 21
2 | 19
3 | 19
4 | 16
5 | 15
Because we still have two same values (id 2 and 3) we keep going with second iteration:
----------
1 | 21
2 | 20
3 | 18
4 | 16
5 | 15
Can i update mysql database somehow to make that for me? Or should i select values, compare them in php and update?
Thank you!
This does not exactly match your expected result, but may lead you toward an acceptable compromise.
MySQL does not yet have window functions such as rank(), dense_rank() or row_number() but you can mimic row_number using #variables such as seen below.
See this demo
MySQL 5.6 Schema Setup:
CREATE TABLE Table1
(`id` int, `votes` int)
;
INSERT INTO Table1
(`id`, `votes`)
VALUES
(1, 20),
(2, 20),
(3, 19),
(4, 16),
(5, 15)
;
Query 1:
SELECT
t.id
, t.votes
, m.v - #row_num AS adjusted_votes
, #row_num := #row_num+1 AS RowNumber
FROM Table1 t
CROSS JOIN (SELECT MAX(votes) v from Table1) m
CROSS JOIN (SELECT #row_num :=0) vars
ORDER BY
t.votes DESC
, t.id
Results:
| id | votes | adjusted_votes | RowNumber |
|----|-------|----------------|-----------|
| 1 | 20 | 20 | 1 |
| 2 | 20 | 19 | 2 |
| 3 | 19 | 18 | 3 |
| 4 | 16 | 17 | 4 |
| 5 | 15 | 16 | 5 |
Note that the reduction of 20 to 19 for id 2 appears to be completely arbitrary and that id is used purely as a tie-breaker when values are equal.

To find the last value in the dataset of 15 minutes interval

ID Timestamp Value
1 11:59.54 10
1 12.04.00 20
1 12.12.00 31
1 12.16.00 10
1 12.48.00 05
I want the result set as
ID Timestamp Value
1 11:59.54 10
1 12:00:00 10
1 12.04.00 20
1 12.12.00 31
1 12:15:00 31
1 12:16.00 10
1 12:30:00 10
1 12:45:00 10
1 12.48.00 05
More coffee will probably lead to a simpler solution, but consider the the following...
DROP TABLE IF EXISTS my_table;
CREATE TABLE my_table
(id INT NOT NULL AUTO_INCREMENT PRIMARY KEY
,timestamp TIMESTAMP
,value INT NOT NULL
);
INSERT INTO my_table VALUES
(1 ,'11:59:54',10),
(2 ,'12:04:00',20),
(3 ,'12:12:00',31),
(4 ,'12:16:00',10),
(5 ,'12:48:00',05);
... in addition, I have a table of integers, that looks like this:
SELECT * FROM ints;
+---+
| i |
+---+
| 0 |
| 1 |
| 2 |
| 3 |
| 4 |
| 5 |
| 6 |
| 7 |
| 8 |
| 9 |
+---+
So...
SELECT a.timestamp
, b.value
FROM
( SELECT x.*
, MIN(y.timestamp) min_timestamp
FROM
( SELECT timestamp
FROM my_table
UNION
SELECT SEC_TO_TIME((i2.i*10+i1.i)*900)
FROM ints i1
, ints i2
WHERE SEC_TO_TIME((i2.i*10+i1.i)*900)
BETWEEN (SELECT MIN(timestamp) FROM my_table)
AND (SELECT MAX(timestamp) FROM my_table)
ORDER
BY timestamp
) x
LEFT
JOIN my_table y
ON y.timestamp >= x.timestamp
GROUP
BY x.timestamp
) a
JOIN my_table b
ON b.timestamp = min_timestamp;
+-----------+-------+
| timestamp | value |
+-----------+-------+
| 11:59:54 | 10 |
| 12:00:00 | 20 |
| 12:04:00 | 20 |
| 12:12:00 | 31 |
| 12:15:00 | 10 |
| 12:16:00 | 10 |
| 12:30:00 | 5 |
| 12:45:00 | 5 |
| 12:48:00 | 5 |
+-----------+-------+
The idea is as follows. Use SERIES_GENERATE() to generate the missing time stamps with the 15 minute intervals and and union it with the existing data your table T. Now you would want to use LAST_VALUE with IGNORE NULLS. IGNORE NULLS is not implemented in HANA, therefore you have to do a bit of a workaround. I use COUNT() as a window function to count the non null values. I do the same on the original data and then join both on the count. This way I repeat the last non-null value.
select X.ID, X.TIME, Y.VALUE from (
select ID, TIME, value,
count(VALUE) over (order by TIME rows between unbounded preceding and current row) as CNT
from (
--add the missing 15 minute interval timestamps
select 1 as ID, GENERATED_PERIOD_START as TIME, NULL as VALUE
from SERIES_GENERATE_TIME('INTERVAL 15 MINUTE', '12:00:00', '13:00:00')
union all
select ID, TIME, VALUE from T
)
) as X join (
select ID, TIME, value,
count(value) over (order by TIME rows between unbounded preceding and current row) as CNT
from T
) as Y on X.CNT = Y.CNT

Get date even if it doesn't exist in table from SQL SELECT statement

I have a table that stores the amount of errors according to what alarm-id it is. The table looks something like this:
|----DATE----|---ALARM_ID---|---COUNTER---|
| 2012-01-01 | 1 | 32 |
| 2012-01-01 | 2 | 28 |
| 2012-01-02 | 1 | 12 |
| 2012-01-02 | 2 | 23 |
| 2012-01-03 | 1 | 3 |
| 2012-01-03 | 2 | 9 |
| 2012-01-05 | 1 | 8 |
| 2012-01-05 | 2 | 1 |
| 2012-01-07 | 1 | 102 |
| 2012-01-07 | 2 | 78 |
Notice the gap between date (2012-01-03 - 2012-01-05) and (2012-01-05 - 2012-01-07). On these dates there isn't any data because the system, that my program is monitoring, haven't reported any errors at that date. What I'm looking for is a SQL SELECT query that returns the total amount of errors on each date, for example:
|----DATE----|---COUNTER---|
| 2012-01-01 | 60 |
| 2012-01-02 | 35 |
| 2012-01-03 | 12 |
| 2012-01-04 | 0 |
| 2012-01-05 | 9 |
| 2012-01-06 | 0 |
| 2012-01-07 | 180 |
I have a query that returns ID's even if they doesn't exist in the table, and if the ID doesn't exist, return the ID anyway with the COUNTER value 0. As such:
BEFORE AFTER
|---ID---|---COUNTER---| |---ID---|---COUNTER---|
| 1 | 2 | | 1 | 2 |
| 2 | 6 | | 2 | 6 |
| 3 | 1 | --> | 3 | 1 |
| 5 | 9 | | 4 | 0 |
| 6 | 10 | | 5 | 9 |
| 6 | 10 |
| 7 | 0 |
| 8 | 0 |
The query goes like this:
select t.num as ID, coalesce(yt.COUNTER, 0)
from all_stats yt right join
( select t1.num + t2.num * 10 + t3.num * 100 + t4.num * 1000 as num
from ( select 1 as num union select 2 union select 3 union select 4 union select 5 union select 6 union select 7 union select 8 union select 9 union select 0 ) t1 cross join
( select 1 as num union select 2 union select 3 union select 4 union select 5 union select 6 union select 7 union select 8 union select 9 union select 0 ) t2 cross join
( select 1 as num union select 2 union select 3 union select 4 union select 5 union select 6 union select 7 union select 8 union select 9 union select 0 ) t3 cross join
( select 1 as num union select 2 union select 3 union select 4 union select 5 union select 6 union select 7 union select 8 union select 9 union select 0 ) t4 )
t on yt.ID = t.num
where (t.num between (select min(ID) from all_stats) and (select max(ID) from all_stats)) order by ID
I can't figure out how I can change this query when it's regarding dates. Can someone please help me on this issue?
I'm using MySQL
Thanks in advance, Steve-O
The exact details will depend on the DBMS, and on the nature of the database (e.g., OLAP-oriented vs. OLTP-oriented), but one common general approach is to create an auxiliary calendar table that represents dates as a dimension. Then you can use regular JOINs, rather than having to use complex logic to generate missing dates.
The answers to this StackOverflow question describe how to apply this approach on MySQL.
You can use a similar approach for numbers, by the way, by having a numbers tables; I've never done that myself for numbers, but it seems to be a popular idea; see this dba.stackexchange.com question.
If you're using SQL Server 2005 or above you can use a CTE (if not, a loop or other sql technique to populate a table with the dates in the range). Note also there is a limit to the levels of recursion within a CTE.
declare #dateRange table
(
dateBegin datetime,
dateEnd datetime
)
insert into #dateRange (dateBegin, dateEnd)
values ('2012-01-01', '2012-01-07')
;with cte (d)
as (select dateBegin as d
from #dateRange tbl
where datediff(day, tbl.dateBegin, tbl.dateEnd) <= 100
union all
select dateadd(day, 1, cte.d) as d
from cte
inner join #dateRange tbl on cte.d < tbl.dateEnd)
Then get the full results either using the CTE or a temporary table that contains the set of dates in the range:
select cte.d, sum(isnull(e.errorCounter, 0))
from cte
left outer join #errors e on e.errorDate = cte.d
group by cte.d
order by cte.d
You really should handle this at the application layer (ie iterate over the known date range and pull the non-zero vals from the resultset) or fix your table to always include the dates needed if you MUST have a database-centered solution. There's no really good way to generate, on the fly, a set of dates to use in building a continuous date range query.
You can see this for some examples of DB scripting solutions:
Return temp table of continuous dates
But I think you're posing the wrong question. Fix the database to include what you need, or fix how you're generating your report. Databases aren't meant to do interpolation and data generation.