Making one row many rows - mysql

I have some rows like this:
school 4
hotel 2
restaurant 6
I would like to do have this output like this:
school 1
school 2
school 3
school 4
hotel 1
hotel 2
restaurant 1
...
restaurant 6
Is there a MySQL query I can run to output it like this (i.e., the number of rows output corresponds to the number in the second field)?

Thanks for JB Nizet for telling me that MySQL would not be able to do this easily on it's own. It inspired me to write this PHP script:
$result = mysqli_query($dbc,"select abbrev,chapters from books");
while ($output = mysqli_fetch_array($result,MYSQL_ASSOC)) {
for ($i=1; $i<=$output['chapters']; $i++) {
echo "{$output['abbrev']}$i\n";
}
}

In Oracle or PostgreSQL, this kind problem can be solved relatively simple with row generator. Unfortunately, MySQL doesn't have any simple way to generate rows, you should have to do it manually. You have to create a view.
create or replace view generator_16 as
select 0 n union all select 1 union all select 2 union all
select 3 union all select 4 union all select 5 union all
select 6 union all select 7 union all select 8 union all
select 9 union all select 10 union all select 11 union all
select 12 union all select 13 union all select 14 union all
select 15;
This view has one column n and 16 rows from 0 to 15. With this view, you can write a query like this:
select a.name, b.n
from t a, generator_16 b
where b.n <= a.n and b.n > 0;
Since the rows of view starts from 0, there's additional filter condition b.n > 0 to exclude 0. If you define a view which starts from 1, the additional condition would not necessary.
I assume that the number is small (below 16). If you have to deal the case that the number over 15, then you should define an another view which contains 256 rows:
create or replace view generator_256 as
select (hi.n*16 + lo.n) as n
from generator_16 hi, generator_16 lo;
Then update the query to use generator_256.
For more detailed explanation for generator_16, generator_256 views, reading MySQL Row Generator would help you.

Related

Create histogram given start and end values in SQL

I have an SQL table with "start" and "end" columns: for the sake of simplicity, let's assume they are integers between 1 and 10. I would like to somehow obtain a histogram of the values between "start" and "end".
For instance, given the following rows:
start
end
3
8
4
9
I would like to obtain the following histogram:
time
count
1
0
2
0
3
1
4
2
5
2
6
2
7
2
8
2
9
1
10
0
I really have no idea where to start looking in the SQL syntax to get that result -- maybe an inner join?
You can use a recursive CTE to generate times -- if you don't have a handy tally or numbers table. Then join and aggregate:
with recursive cte as (
select 1 as t
union all
select t + 1
from cte
where t < 10
)
select cte.t,
(select count(*)
from t
where cte.t between t.start and t.end
) as cnt
from cte;
Here is a db<>fiddle.

Is somehow possible to create select a series in mysql?

Is possible to select a numerical series or date series in SQL? Like create a table with N rows like 1 to 10:
1
2
3
...
10
or
2010-01-01
2010-02-01
...
2010-12-01
If you install common_schema, you can use the numbers table to easily create queries to output those types of ranges.
For example, these 2 queries will produce the output from your examples:
select n
from common_schema.numbers
where n between 1 and 10
order by n
select ('2010-01-01' + interval n month)
from common_schema.numbers
where n between 0 and 11
order by n
An SQL solution:
SELECT *
FROM (
SELECT 1 as id
UNION SELECT 2
UNION SELECT 3
UNION SELECT 4
UNION SELECT 5
)
Yep! Both MySQL and Microsoft SQL Server (and others) have a BETWEEN operator. I don't remember off the top of my head if it's inclusive or exclusive, but here's a starting point!
http://www.w3schools.com/sql/sql_between.asp

Calculate (sum,max,avg) comma separated column in mysql

I have a table named POIN and has a column which have comma separated values. I want to calculate each values on the comma separated. It's looks duplicate question because it has answered here. But I want to achieved this using single query instead of create a mysql function.
This is my table looks like :
id poin
------------------
1 1,5,9,3,5
2 2,4,8,5
3 4,7,9,1,5,7
Desired result :
id max min sum avg
--------------------------------------
1 1 9 23 4,6
2 8 2 19 4,75
3 9 1 33 5,5
Actually, I searched this in Google and this forum and didn't get a correct answer yet. I can't show what I have tried so far, because I have no clue where to start.
I don't know what application are you design, but I think it was bad design to store values in comma separated instead of create a table details. You can solved this without using a mysql function actually. First, you need to convert comma separated columns into rows and then you can do some calculation. This query may help you :
select id,max(val) as max,min(val) as min,sum(val) as sum,avg(val) as avg
from(
select id,(substring_index(substring_index(t.poin, ',', n.n), ',', -1)) val
from poin_dtl t cross join(
select a.n + b.n * 10 + 1 n
from
(select 0 as n union all select 1 union all select 2 union all select 3
union all select 4 union all select 5 union all select 6
union all select 7 union all select 8 union all select 9) a,
(select 0 as n union all select 1 union all select 2 union all select 3
union all select 4 union all select 5 union all select 6
union all select 7 union all select 8 union all select 9) b
order by n
) n <-- To make this simple, Create a table with one column that has 100 rows.
where n.n <= 1 + (length(t.poin) - length(replace(t.poin, ',', '')))
order by val asc
) as c_rows -- c_rows = convert comma separated columns into rows
group by id
The results should be like this:
id max min sum avg
--------------------------------------
1 1 9 23 4,6
2 8 2 19 4,75
3 9 1 33 5,5

Return multiple rows based on int value from a single record

I have a database table containing project data with one record for each project. Below some sample data, duration is in months and is currently limited to max. 240, but can, theoretically, be infinite:
id name amount start_date duration
1 Project A 9.000 2013-06-24 3
2 Project B 5.000 2013-07-13 2
3 Project C 15.000 2013-08-06 3
Now I want MySQL to return the amount per month for each project: amount divided by duration for each month starting with the month the start_date is in. So based on the above data, MySQL will return something like below, ordered by month:
id name month amount_this_month
1 Project A 2013-06 3.000
1 Project A 2013-07 3.000
2 Project B 2013-07 2.500
1 Project A 2013-08 3.000
2 Project B 2013-08 2.500
3 Project C 2013-08 5.000
3 Project C 2013-09 5.000
3 Project C 2013-10 5.000
I saw something here for SQL Server, giving the suggestion to use a dummy table filled with numbers. Anyone has any ideas on how to do this without affecting the original database structure?
I need to do this in one query (so no temp (mem) tables).
How is this possible in MySQL? I saw some solutions for Oracle to iterate trough a (virtual) range/sequence, is it possible to do something like this in MySQL?
You can do something like this, joining a dummy range of numbers against itself to get the range of numbers, then adding that to the base month
SELECT id, name, DATE_FORMAT(DATE_ADD(start_date, INTERVAL Units.i + Tens.i * 10 + Hundreds.i * 100 MONTH), '%Y-%m') AS `month`, (amount / duration) AS `amount_this_month`
FROM SomeTable
CROSS JOIN (SELECT 0 AS i UNION SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4 UNION SELECT 5 UNION SELECT 6 UNION SELECT 7 UNION SELECT 8 UNION SELECT 9) Units
CROSS JOIN (SELECT 0 AS i UNION SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4 UNION SELECT 5 UNION SELECT 6 UNION SELECT 7 UNION SELECT 8 UNION SELECT 9) Tens
CROSS JOIN (SELECT 0 AS i UNION SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4 UNION SELECT 5 UNION SELECT 6 UNION SELECT 7 UNION SELECT 8 UNION SELECT 9) Hundreds
WHERE (Units.i + Tens.i * 10 + Hundreds.i * 100) < duration

Filling empty records through SELECT..INSERT statement

Before anything else, here is the simplified schema (with dummy records) of the database:
ItemList
ItemID ItemName DateAcquired Cost MonthlyDep CurrentValue
================================================================================
1 Stuff Toy 2011-12-25 100.00 10.00 100.00
2 Mouse 2011-12-23 250.00 50.00 200.00
3 Keyboard 2011-12-17 250.00 30.00 190.00
4 Umbrella 2011-12-28 150.00 20.00 110.00
5 Aircon 2011-12-29 950.00 25.00 925.00
DepreciationTransaction
ItemID DateOfDep MonthlyDep
======================================
2 2012-01-31 250.00
3 2012-01-31 30.00
4 2012-01-31 20.00
5 2012-01-31 25.00
3 2012-02-29 30.00
4 2012-02-29 20.00
I need your suggestions to help me solve this problem. Basically I am creating a depreciation monitoring system of a certain LGU. The problem of the current database is that it lacks some records for a specific date of depreciation, for instance:
Lacking Records (this is not a table from the database)
ItemID LackingDate
============================
1 2012-01-31
1 2012-02-29
2 2012-02-29
5 2012-02-29
And because of the lacking records, I cannot generate the depreciation report for the month of MARCH. Any idea how can I insert missing records on the DepreciationTransaction?
What have I done so far? None. But a simple query that calculates the newly depreciated value (which produces incorrect value because of the missing records)
The problem here is that you will have to generate data. MySQL is not intended to generate data, you should do that at an application level and just tell MySQL to store it. In this case, the application should check wether there are missing records and create them if needed.
Leaving that aside, you can (awfully) create dynamic data with MySQL like this:
select il.itemId, endOfMonths.aDate from ((
select aDate from (
select #maxDate - interval (a.a+(10*b.a)+(100*c.a)+(1000*d.a)) day aDate from
(select 0 as a union all select 1 union all select 2 union all select 3
union all select 4 union all select 5 union all select 6 union all
select 7 union all select 8 union all select 9) a, /*10 day range*/
(select 0 as a union all select 1 union all select 2 union all select 3
union all select 4 union all select 5 union all select 6 union all
select 7 union all select 8 union all select 9) b, /*100 day range*/
(select 0 as a union all select 1 union all select 2 union all select 3
union all select 4 union all select 5 union all select 6 union all
select 7 union all select 8 union all select 9) c, /*1000 day range*/
(select 0 as a union all select 1 union all select 2 union all select 3
union all select 4 union all select 5 union all select 6 union all
select 7 union all select 8 union all select 9) d, /*10000 day range*/
(select #minDate := (select min(dateAcquired) from il),
#maxDate := '2012-03-01') e
) f
where aDate between #minDate and #maxDate and aDate = last_day(aDate)
) endOfMonths, il)
left join dt
on il.itemId = dt.itemId and endOfMonths.aDate = dt.dateOfDep
where dt.itemId is null and last_day(il.dateAcquired) < endOfMonths.aDate
Depending on the length of the date range you can reduce the amount of dynamically generated results (10000 days means over 27 years of records each representing one day) by removing tables (d, c, b and a) and removing them from the upper formula. Setting the #minDate and #maxDate variables will allow you to specify the dates between you want to filter the results. This dates should be the min date from which you have an item and the max date should be march, in your case.
In plain english: If select min(dateAcquired) from il returns a date before '2012-03-01' - 10000 days then you'll have to add another union.
Finally, just add the insert statement (if you really need to insert those records).
You may build a temporary table, which contains the date needed. And use the table to LEFT OUTER JOIN the "DepreciationTransaction" table.
SELECT dt.date_value, dt.itemid, ISNULL(SUM(dt.MonthlyDep), 0)
FROM tmp_date
LEFT OUTER JOIN
DepreciationTransaction AS dt
ON tmp_date.date_value = dt.DateOfDep
GROUP BY dt.date_value, dt.itemid
Of course, if your want that all of the items to be on report, you should make a cartesian product with tmp_date and items_id.