I have some data in a series of tables in a relational database. For the most part, the data is in a SQL Server database although I have an identical problem with data in a MySQL database and so solution should ideally but applicable to both. The data consists of a series of categorical variables (e.g. month, group, etc.) and a numerical value representing an output. For simplicity, the data can be considered as a single table such as:
DROP TABLE IF EXISTS `tableData`;
CREATE TABLE `tableData` (
`id` int(3) unsigned NOT NULL AUTO_INCREMENT,
`myDate` date NOT NULL,
`myGroup` varchar(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL DEFAULT '',
`myValue` int(3) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
Add data:
insert into tableData (myDate,myGroup,myValue) VALUES ('2020-01-01','A',34),
('2020-02-01','A',19),
('2020-03-01','A',65),
('2020-04-01','B',38),
('2020-05-01','A',37),
('2020-06-01','A',23),
('2020-07-01','B',82),
('2020-09-01','A',83),
('2020-10-01','A',16),
('2020-10-01','B',32),
('2020-11-01','A',84),
('2020-12-01','A',21);
The table data looks like:
SELECT myGroup AS 'group',
MONTH(myDate) AS 'month',
myValue AS 'value'
FROM
tableData
ORDER BY
myGroup,MONTH(myDate);
group month value
A 1 34
A 2 19
A 3 65
A 5 37
A 6 23
A 9 83
A 10 16
A 11 84
A 12 21
B 4 38
B 7 82
B 10 32
Clearly the query only returns the data in the table. However, I need to have all months and all groups represented even if there is no data for those groups explicitly held in the database.
My solution was to create a dummy table containing all combinations of categorical variables (in this case month and group) and LEFT JOIN the data to the dummy table.
DROP TABLE IF EXISTS `tableData`;
CREATE TABLE `tableDummy` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`myGroup` varchar(12) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL DEFAULT '',
`myMonth` int(11) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
INSERT INTO tableDummy (myGroup,myMonth) VALUES ('A',1),('A',2),('A',3),('A',4),('A',5),('A',6),
('A',7),('A',8),('A',9),('A',10),('A',11),('A',12),
('B',1),('B',2),('B',3),('B',4),('B',5),('B',6),
('B',7),('B',8),('B',9),('B',10),('B',11),('B',12);
This dummy table looks like:
SELECT myMonth,
myGroup
FROM
tableDummy
ORDER BY
myMonth,
myGroup;
myMonth myGroup
1 A
1 B
2 A
2 B
3 A
3 B
4 A
4 B
5 A
5 B
6 A
6 B
7 A
7 B
8 A
8 B
9 A
9 B
10 A
10 B
11 A
11 B
12 A
12 B
I can then LEFT JOIN the actual data to the dummy table catetories (replacing NULL values with zero) to produce the desired output:
SELECT dumTbl.myMonth AS 'month',
dumTbl.myGroup AS 'group',
IFNULL(datTbl.value,0) AS 'value' -- may be ISNULL() in SQL Server
FROM
(SELECT myMonth,
myGroup
FROM
tableDummy
ORDER BY
myMonth,
myGroup) dumTbl
LEFT JOIN
(SELECT myGroup as 'group',
MONTH(myDate) as 'month',
myValue as 'value'
FROM
tableData
ORDER BY
myGroup,MONTH(myDate) ) datTbl
ON dumTbl.myGroup = datTbl.group AND
dumTbl.myMonth = datTbl.month
ORDER BY
dumTbl.myMonth,
dumTbl.myGroup;
This produces the following desired output:
month group value
1 A 34
1 B 0
2 A 19
2 B 0
3 A 65
3 B 0
4 A 0
4 B 38
5 A 37
5 B 0
6 A 23
6 B 0
7 A 0
7 B 82
8 A 0
8 B 0
9 A 83
9 B 0
10 A 16
10 B 32
11 A 84
11 B 0
12 A 21
12 B 0
In the real-world, the situation is a little more complicated. There are several categorical variables, each of which could have 100+ possible options (even though any SELECT statement will only require data from 2 or 3 categories at a time). Generating a dummy variable in advance that covers all options produces a table with multiple millions of rows and is impractical to manage with current tech, especially over a home broadband connection.
My question is, rather than download the dummy data from a table in the database, can the data contained in the dummy table be created in memory on-the-fly for only the small number of required values in the categorical variables using pure SQL (i.e. no additional Python, PHP or other programming language)? It is not possible to create a reduced table containing only the necessary options because not all users of the query will have permission to create tables in the database.