title | url | date | description | categories | tags | weight | toc |
---|---|---|---|---|---|---|---|
Lesson 5: Programming |
/dev/programming/ |
2025-04-10 |
1 |
true |
- Differentiate between stored procedures and stored functions using the native MariaDB SQL/PSM language
- Use JSON document stores
- Understand how to access MariaDB via APIs using both native and non-native MariaDB connectors
- Set SQL modes to affect error output and use the SHOW WARNINGS and SHOW ERRORS statements to interpret error messages
How to use hybrid data models
Relational
data integrity, transactions and reliability
JSON
flexibility, simplicity and ubiquity
- JSON data type since 10.2
- Many JSON functions available besides schema support
- Indexing for faster retrieval is possible
- Stored as
LONGTEXT
inmysqldump
A data model can be comprised of structured and semi-structured data
- Structured data is externally described
- Semi-structured data is self described
Name | Format | Price | Video |
---|---|---|---|
Aliens | Blu-ray | 13.99 | {"resolution": "1080p", "aspectRatio": "1.85:1"} |
Structured
Structure described by the schema
Semi-structured
Structure described by the data
CREATE TABLE IF NOT EXISTS products (
id INT NOT NULL PRIMARY KEY AUTO_INCREMENT,
type VARCHAR(1) NOT NULL,
name VARCHAR(40) NOT NULL,
format VARCHAR(20) NOT NULL,
price FLOAT(5, 2) NOT NULL,
attr JSON NOT NULL);
INSERT INTO products (type, name, format, price, attr) VALUES
('M', 'Aliens', 'Blu-ray', 13.99,'{"video": {"resolution": "1080p", "aspectRatio": "1.85:1"}, "cuts":
[{"name": "Theatrical", "runtime": 138}, {"name": "Special Edition", "runtime": 155}], "audio":
["DTS HD", "Dolby Surround"]}');
INSERT INTO products (type, name, format, price, attr) VALUES
('B', 'Foundation', 'Paperback', 7.99, '{"author": "Isaac Asimov", "page_count": 296}');
Creating and Querying JSON Documents
SELECT name, format, price,
JSON_VALUE(attr, '$.video.aspectRatio') AS aspect_ratio
FROM products
WHERE type = 'M';
| Name | Format | Price | Aspect_ratio |
|--------|--------|-------|--------------|
| Aliens | Blu-ray| 13.99 | 1.85:1 |
Creating and Querying JSON Documents
SELECT type, name, format, price,
JSON_VALUE(attr,$.video.aspectRatio') AS aspect_ratio
FROM products;
| Type | Name | Format | Price | Aspect_ratio |
|------|------------|-----------|-------|--------------|
| M | Aliens | Blue-ray | 13.99 | 1.85:1 |
| B | Foundation | Paperback | 7.99 | NULL |
Creating and Querying JSON Documents
SELECT name, format, price,
JSON_VALUE(attr, '$.video.aspectRatio') AS aspect_ratio
FROM products
WHERE type = 'M' AND
JSON_CONTAINS_PATH(attr, 'one', '$.video.resolution') = 1;
| Name | Format | Price | Aspect_ratio |
|-------|--------|-------|--------------|
| Aliens | Blu-ray | 13.99 | 1.85:1 |
Creating and Querying JSON Documents
SELECT id, name, format, price
FROM products
WHERE type = 'M' AND
JSON_CONTAINS(attr, '\"DTS HD\"', '$.audio') = 1;
| Name | Format | Price | Aspect_ratio |
|--------|--------|-------|--------------|
| Aliens | Blu-ray| 13.99 | 1.85:1 |
Creating and Querying JSON Documents
SELECT name, format, price,
JSON_QUERY(attr, '$.audio') AS audio
FROM products
WHERE type = 'M';
| Name | Format | Price | Audio |
|--------|--------|-------|--------------------------------|
| Aliens | Blu-ray| 13.99 | [ "DTS HD", "Dolby Surround" ] |
Creating and Querying JSON Documents
SELECT name, format, price,
JSON_VALUE(attr, '$.audio[0]') AS default_audio
FROM products
WHERE type = 'M';
| Name | Format | Price | Audio |
|-------|--------|-------|------- |
| Aliens | Blu-ray | 13.99 | DTS HD |
SELECT name, format, price,
JSON_QUERY(attr, '$.video') AS video
FROM products
WHERE type = 'M';
| Name | Format | Price | Video |
|-------|--------|-------|------------------------------------------|
| Aliens| Blu-ray| 13.99 | { "resolution": "1080p", "aspectRatio": "1.85:1" } |
Creating and Querying JSON Documents
SELECT name,
JSON_QUERY(attr, '$.audio') AS audio,
JSON_QUERY(attr, '$.video') AS video
FROM products
WHERE type = 'M';
| Name | Audio | Video |
|--------|---------------------------------|------------------------------------------|
| Aliens | [ "DTS HD", "Dolby Surround" ] | { "resolution": "1080p", "aspectRatio": "1.85:1" } |
Creating and Querying JSON Documents
SELECT id, name, format, price
FROM products
WHERE type = 'M' AND
JSON_VALUE(attr, '$.video.resolution') = '1080p';
Name | Format | Price | Aspect_ratio |
---|---|---|---|
Aliens | Blu-ray | 13.99 | 1.85:1 |
Creating and Querying JSON Documents
ALTER TABLE products ADD COLUMN
video_resolution VARCHAR(5) AS (JSON_VALUE(attr, '$.video.resolution')) VIRTUAL;
EXPLAIN SELECT name, format, price FROM products WHERE video_resolution = '1080p';
| ID | Select_type | Table | Type | Possible_keys |
|----|-------------|----------|------|---------------|
| 1 | SIMPLE | Products | ALL | NULL |
CREATE INDEX resolutions ON products(video_resolution);
EXPLAIN SELECT name, format, price FROM products WHERE video_resolution = '1080p';
| ID | Select_type | Table | Ref | Possible_keys |
| --- | ----------- | -------- | --- | ------------- |
| 1 | SIMPLE | Products | ref | resolutions |
UPDATE products SET attr = JSON_INSERT(attr, '$.disks', 1) WHERE id = 1;
SELECT name, format, price,
JSON_VALUE(attr, '$.disks') AS disks
FROM products
WHERE type = 'M';
| Name | Format | Price | Disks |
|--------|--------|-------|-------|
| Aliens | Blu-ray| 13.99 | 1 |
Modifying JSON Documents
UPDATE products SET attr = JSON_INSERT(attr, '$.languages',
JSON_ARRAY('English', 'French')) WHERE id = 1;
SELECT name, format, price, JSON_QUERY(attr, '$.languages') AS languages
FROM products WHERE type = 'M';
| Name | Format | Price | Languages |
|--------|--------|-------|----------------|
| Aliens | Blu-ray| 13.99 | ["English", "French"] |
UPDATE products SET attr = JSON_ARRAY_APPEND(attr, '$.languages', 'Spanish')
WHERE id = 1;
SELECT name, format, price, JSON_QUERY(attr, '$.languages') AS languages
FROM products WHERE type = 'M';
| Name | Format | Price | Languages |
|--------|--------|-------|-------------------------------------|
| Aliens | Blu-ray | 13.99 | [ "English", "French", "Spanish" ] |
UPDATE products SET attr = JSON_REMOVE(attr, '$.languages[0]') WHERE id = 1;
SELECT name, format, price, JSON_QUERY(attr, '$.languages') AS languages
FROM products WHERE type = 'M';
| Name | Format | Price | Languages |
|--------|--------|-------|--------------------------|
| Aliens | Blu-ray| 13.99 | [ "French", "Spanish" ] |
SELECT JSON_OBJECT('name', name, 'format', format, 'price', price) AS DATA
FROM products
WHERE type = 'M';
{
"name": "Tron",
"format": "Blu-ray",
"price": 29.99
}
SELECT JSON_OBJECT('name', name, 'format', format, 'price', price, 'resolution',
JSON_VALUE(attr, '$.video.resolution')) AS DATA
FROM products
WHERE type = 'M';
{
"name": "Aliens",
"format": "Blu-ray",
"price": 13.99,
"resolution": "1080p"
}
SELECT JSON_MERGE(
JSON_OBJECT(
'name', name,
'format', format),
attr) AS DATA
FROM products
WHERE type = 'M';
{
"name": "Tron",
"format": "Blu-ray",
"video": {
"resolution": "1080p",
"aspectRatio": "1.85:1"},
"cuts": [
{"name": "Theatrical", "runtime": 138},
{"name": "Special Edition", "runtime": 155}],
"audio": [
"DTS HD",
"Dolby Surround"]
}
Enforcing Data Integrity With JSON Documents
ALTER TABLE products ADD CONSTRAINT check_attr
CHECK (
type != 'M' or (type = 'M' and
JSON_TYPE(JSON_QUERY(attr, '$.video')) = 'OBJECT' and
JSON_TYPE(JSON_QUERY(attr, '$.cuts')) = 'ARRAY' and
JSON_TYPE(JSON_QUERY(attr, '$.audio')) = 'ARRAY' and
JSON_TYPE(JSON_VALUE(attr, '$.disks')) = 'INTEGER' and
JSON_EXISTS(attr, '$.video.resolution') = 1 and
JSON_EXISTS(attr, '$.video.aspectRatio') = 1 and
JSON_LENGTH(JSON_QUERY(attr, '$.cuts')) > 0 and
JSON_LENGTH(JSON_QUERY(attr, '$.audio')) > 0));
Enforcing Data Integrity With JSON Documents
INSERT INTO products (type, name, format, price, attr) VALUES
('M', 'Tron', 'Blu-ray', 29.99, '{"video": {"aspectRatio": "2.21:1"}, "cuts":
[{"name": "Theatrical",
"runtime": 96}], "audio": ["DTS HD", "Dolby Digital"], "disks": 1}');
ERROR 4025 (23000): CONSTRAINT `check_attr` failed for `test`.`products`
NO RESOLUTION FIELD!
INSERT INTO products (type, name, format, price, attr) VALUES
('M', 'Tron', 'Blu-ray', 29.99, '{"video": {"resolution": "1080p", "aspectRatio": "2.21:1"}, "cuts":
[{"name": "Theatrical", "runtime": 96}], "audio": ["DTS HD", "Dolby Digital"], "disks": "one"}');
ERROR 4038 (HY000): Syntax error in JSON text in argument 1 to function 'json_type' at position 1
“ONE” IS NOT A NUMBER!
Database API allows an Application Server to interface easily with a MariaDB Server:
- Standardised communication interface
- Serves as bottom-most layer in the communications tack on top of which SQL runs
End Users → Web Page → Application Server → Database Server
Browser App → Backend / API → MariaDB
Preparation
- Install and configure API software on server
- Create a MariaDB user account for application
Basic components of application
- Establish a connection to server
- Query the database
- Parse the data and display to user
Set Host to Localhost
Limit Privileges (e.g., Only SELECT
)
CREATE USER 'api_user'@'localhost' IDENTIFIED BY 'mariadb';
GRANT SELECT ON api.* TO 'api_user'@'localhost';
Connector/J is the standard connector for Java
Built on top of JDBC
Available from MariaDB web site as binary download (compiled classes in JAR)
Just place the JAR somewhere in your CLASSPATH
Connect a Java Application to MariaDB
- Import required packages
- Authentication Variables
- New Connection Variable
- Error Handling via try… catch (throws SQLException)
import java.sql.Connection;
import java.sql.DriverManager;
import import java.sql.SQLException;
String host = 'localhost';
String user = 'api_user';
String pw = 'mariadb';
String db = 'api';
Connection conn = DriverManager.getConnection("jdbc:mysql://"
+ host + "/" + db + "?" + "user=" + user + "&password=" + pw);
Import required packages
Query Server
- Declare prepared statement
- Init prepared statement
- Bind variable to statement
- Execute statement
- Fetch Results (Loop through Results & Display)
import java.sql.PreparedStatement;
import java.sql.ResultSet;
String sql = "SELECT common_name, saber_color
FROM jedis
WHERE LOWER(common_name) LIKE LOWER(?)";
PreparedStatement stmt = connect.prepareStatement(sql);
stmt.setString(1, "some_value");
stmt.executeQuery();
ResultSet rs = stmt.getResultSet();
while (rs.next()) {
System.out.println(rs.getString("common_name")
+ " - " + rs.getString("saber_color"))
}
Standard connector for Java using the Reactive Relational Database Connectivity (R2DBC) API
R2DBC operations are non-blocking, which makes the R2DBC API more scalable than Java’s standard JDBC API.
Capable of being used with a native R2DBC implementation or in combination with an R2DBC Client Library (e.g. Spring Data R2DBC).
Get it from MariaDB web site as binary download (compiled classes in JAR)
Just place the JAR somewhere in your CLASSPATH
The R2DBC specification is currently pre-GA and is planned to be released GA (v 1.0) by end of 2021.
Connect an R2DBC application to MariaDB
import org.mariadb.r2dbc.MariadbConnectionConfiguration;
import org.mariadb.r2dbc.MariadbConnectionFactory;
import io.r2dbc.spi.Connection;
MariadbConnectionConfiguration conf =
MariadbConnectionConfiguration.builder()
.host("192.0.2.1")
.port(3306)
.username("connr2dbc_test")
.password("passwd")
.database("test")
.build();
MariadbConnectionFactory connFactory = new MariadbConnectionFactory(conf);
Connection conn = connFactory.create().block();
- Declare a statement
- Execute the statement and map back the result to an iterator
- Define the action inside the iterator
- Define the action outside the iterator
import io.r2dbc.spi.Statement;
import io.r2dbc.spi.Result;
import io.r2dbc.spi.Row;
import io.r2dbc.spi.RowMetadata;
import reactor.core.publisher.Flux;
Statement stmt = conn.createStatement("SELECT name, email FROM contacts");
for (String contact_entry :
Flux.from(stmt.execute()).flatMap( res -> res.map( (row, metadata) -> {
return String.format("%s: %s",
row.get(0, String.class),
row.get(1, String.class)
))).toIterable()) {
System.out.println(contact_entry);
}
- Multiple legacy connectors exists like
mysql
andmysql2
- MariaDB provides its own connector
mariadb
- Higher performance
- Modern API (promise) and legacy support (callbacks)
- Available in NPM
# npm install mariadb
Connect a Node.js Program to MariaDB
- Include the mariadb module
- Authentication Variables
- New Connection Variable
- Error Handling
- Disconnecting
const mariadb = require('mariadb');
const host = 'localhost';
const user = 'api_user';
const pw = 'mariadb';
const db = 'api';
var conn = mariadb.createConnection({host: host, user: user, password: pw, database: db})
.catch(err => {
console.log(err);
});
conn.end();
Declare a SQL statement
Query Server
- Use the Promise interface
- Fetch Results
- Handle potential errors
Close Statement Handle and Connection
const sql = "SELECT common_name, saber_color
FROM jedis
WHERE LOWER(common_name) LIKE LOWER('%some_value%')";
conn.query(sql)
.then(row => {
console.log(row[0].common_name + ' - ' + row[0].saber_color);
})
.catch(err => {
//handle connection error
});
.finally(() => {
conn.end();
});
Native connector for MariaDB exists under the name mariadb
Open-source project, created and maintained by the community
Available in the Python Package Index (PyPI) repository
# pip install mariadb
Connect a Python Program to MariaDB
- Import the mariadb module
- Set the connection parameters
- Errors will generate exceptions, so use try/catch to handle any of them
- Get a connection cursor to run your queries
import mariadb
# Connect to MariaDB Platform
try:
conn = mariadb.connect(user="db_user", password="db_user_passwd", host="192.0.2.1", port=3306, database="employees")
except mariadb.Error as e:
print(f"Error connecting to MariaDB Platform: {e}")
# Get Cursor
cur = conn.cursor()
Queries are run through a cursor
Statements will be prepared on-the-fly
AUTOCOMMIT is on by default
Connection should be closed when not needed any more
# Get data
some_name = "George"
cur.execute("SELECT first_name, last_name FROM employees WHERE first_name=?", (some_name,))
for first_name, last_name in cur:
print(f"First name: {first_name}, Last name: {last_name}")
# Add data
try:
cur.execute("INSERT INTO employees (first_name, last_name) VALUES (?, ?)", ("Maria","DB"))
except mariadb.Error as e:
print(f"Error: {e}")
# Close connection
conn.close()
MariaDB Connector/C++ is a native C++ driver that can be used within C++ projects
The standard C client library can also be used in C++ projects
MariaDB Connector/C++ can be downloaded and installed for use on Linux and Windows
For more information on getting started with Connector/C++ see the official documentation -
https://mariadb.com/docs/clients/connector-cpp/#connector-cpp-installation
Connect a C++ Program to MariaDB Connector/C++
- Including required headers
- Defining connection properties
- Creating an account
- Connecting to the database
- Disconnecting
#include <mariadb/conncpp.hpp>
sql::Driver* driver =
sql::mariadb::get_driver_instance();
sql::SQLString
url("jdbc:mariadb://192.0.2.1:3306/employees")
sql::Properties properties({{"user",
"db_user"}, {"password", "db_user_password"}});
std::unique_ptr<sql::Connection>
conn(driver->connect(url, properties));
conn->close();
Include required headers
Define a prepared statement
Prepare the statement
Bind variables
Execute the query and obtain result set
Loop over the result set to read it
#include <mariadb/conncpp.hpp>
#define SQL "SELECT common_name, saber_color FROM jedis WHERE LOWER(common_name) LIKE LOWER(?)"
std::unique_ptr<sql::Statement> stmt(conn->createStatement());
stmt->setString(1, "some_value");
sql::ResultSet* res = stmt->executeQuery("SELECT common_name, saber_color FROM jedis WHERE LOWER(common_name) LIKE LOWER(?)");
while (res->next()) {
cout << res->getString(0);
cout << res->getString(1);
}
MariaDB provides a client shared object library with header files
Use yum
or similar to install the development package:
# yum install MariaDB-devel
Alternatively, if you compile MariaDB from source, client library and headers will be installed locally
Connect a C Programme to MariaDB
Including the required header
Defining connection properties
Creating a new connection handler
Connecting to server
Handling errors
Disconnecting from server
#include <mysql.h>
#define HOST "localhost"
#define USER "api_user"
#define PW "mariadb"
#define DB "api"
MYSQL *conn = mysql_init(NULL);
if (!mysql_real_connect(conn, HOST, USER, PW, DB, 3306, NULL, 0))
printf("%s\n", mysql_error(conn));
mysql_close(conn);
Define a SQL query
Query Server
- Execute the query
- Retrieve a result set
- Iterate over the result set to read values
Free resources
#define SQL "SELECT common_name, saber_color FROM jedis WHERE LOWER(common_name) LIKE LOWER('some_value')"
mysql_query(conn, sql);
MYSQL_RES *rs = mysql_store_result(conn);
while (MYSQL_ROW row = mysql_fetch_row(rs))
printf("%s = %s\n", row[0], row[1]);
mysql_free_result(rs);
Multiple connectors exist, both free to use and commercial ones.
MySql Connector is a truly async MySQL ADO.NET provider, supporting MariaDB
It has a permissive license (MIT)
It is available from the NuGet repository
C:\> Install-Package MySqlConnector -Version 1.3.8
Connect a .NET Program to MariaDB
- Define the necessary connection properties and pass them to a new instance of the connection
- Open the connection
using (var connection = new MySqlConnection("Server=myserver;User ID=mylogin;Password=mypass;Database=mydatabase"))
{
connection.Open();
}
Use an existing connection.
Define the query string as a new instance of the MySqlCommand class.
Pass this new instance to the connection and it will execute it.
Get back a reader from the command class.
Loop around the reader to fetch the result set row by row.
using (var connection = new MySqlConnection("Server=myserver;User ID=mylogin;Password=mypass;Database=mydatabase"))
{
connection.Open();
using (var command = new MySqlCommand("SELECT field FROM table;", connection))
using (var reader = command.ExecuteReader())
while (reader.Read())
Console.WriteLine(reader.GetString(0));
}
Three PHP APIs (mysql, mysqli, and PDO Extensions)
- MySQL Improved (mysqli) is Recommended
Use yum
or similar Utility to Install PHP and mysqli
# yum install php php-mysql
The web server should be configured to render PHP pages properly; if PHP is installed from a distribution package, this is usually included in the package.
Connect a Web Page or Program to MariaDB
- PHP Tags
- Authentication Variables
- New Connection Variable
- Error Handling
<?php
$host = 'localhost';
$user = 'api_user';
$pw = 'mariadb';
$db = 'api';
$connect = new mysqli($host, $user, $pw, $db);
if (mysqli_connect_errno()) {
printf("Connect failed: %s\n",
mysqli_connect_error());
exit();
}
?>
-
Capture & Parse User Input
-
Query Server
- Store SQL Statement with Placeholder
- Prepare Connection with SQL Statement
- Prepare Variables to Store Fields from Results
- Fetch Results (Loop through Results & Display)
-
Close Statement Handle and Connection
<?php
$search_parameter = $_REQUEST['jediname'];
$search_parameter = "%" . $search_parameter . "%";
$sql = "SELECT common_name, saber_color
FROM jedis
WHERE LOWER(common_name) LIKE LOWER(?)";
$sth = $connect->prepare($sql);
$sth->bind_param('s', $search_parameter);
$sth->execute();
$sth->bind_result($common, $saber_color);
while($sth->fetch()) {
print "$common - <i>$saber_color</i><br/>";
}
$sth->close();
$connect->close();
?>
Many other programming languages provide their own connectors for MariaDB Server
Some could be native, others are often based on the MariaDB client shared library
- For Perl, look at the
DBD::MySQL
module, available in CPAN. - For Ruby, there is a package available in the Rubygem repository.
- For R, use the
RMariaDB
package. - For Erlang, there is a native implementation named
MySQL/OTP
which can be used with MariaDB Server. - For dotNet, there are commercially supported connectors from various vendors which may offer advanced features, compared to the open-source one.
Object-Relational Mapping (ORM) is technique that lets you query and manipulate data from a database using an object-oriented paradigm.
There are many ORM libraries/frameworks that exist across a variety of languages. A few examples that have been tested to work with native MariaDB connectors are:
- Python: SQLAlchemy
- Java: Hibernate, jOOQ, JPA
- Node.js: Sequelize
Before incorporating an ORM into a project, make sure you understand how it works with the database and how this relates to your projected workload.
SHOW WARNINGS Statement returns errors, warnings, and notes
Relevant to the last statement that used a table
Each new INSERT that uses a table clears the list first
CREATE TABLE table1 (col1 TINYINT);
INSERT INTO table1 VALUES (1000);
Query OK, 1 row affected, 1 warning (0.00 sec)
SHOW WARNINGS;
+---------+------+----------------------------------------+
| Level | Code | Message |
+---------+------+----------------------------------------+
| Warning | 1264 | Out of range value for column 'c1' at row 1 |
+---------+------+----------------------------------------+
Low level errors may affect MariaDB's operation
Things like low disk space, file system permissions, etc
OS level error codes are passed back up the chain
MariaDB includes the perror
utility for displaying errors
LOAD DATA INFILE '/root/test' INTO TABLE t1;
ERROR 13 (HY000):
Can't get stat of '/root/test' (Errcode: 13)
# perror 13
Error code 13: Permission denied
MariaDB uses DNS and Reverse DNS for IP and Hostname with Client Connections
A Slow DNS Server can Slow MariaDB Client Connection
Running a Local DNS Server Helps with Lookup Speed and Security
Use --skip-name-resolve
to Tell MariaDB Not to do DNS Lookup
Then Use Only IP Addresses in Permissions Tables
Some Client Languages and Connection Pool Solutions use Persistent Connections (PHP mysql_pconnect()
)
Not very Effective
A New Client Connection is Simpler
Client Session Buffers can Stay Allocated too Long if the Client Pool of Application is not Efficient, Increasing Memory Usage
Transactions can be Held Open if Application is Built Poorly, Reducing Concurrency
MariaDB Fails Silently and Produces a NULL
Response for Divide-by-zero Error
- Unless SQL Mode
ERROR_FOR_DIVISION_BY_ZERO
is Active
The COALESCE
and NULLIF
functions can be used to provide a solution for divide-by-zero errors:
SET @INPUT1 = 0;
SELECT COALESCE(50 /NULLIF(@INPUT1,0),0) AS response;
+----------+
| response |
+----------+
| 0.0000 |
+----------+
SET @INPUT1 = 5;
SELECT COALESCE(50 /NULLIF(@INPUT1,0),0) AS response;
+----------+
| response |
+----------+
| 10.0000 |
+----------+
- Differentiate between stored procedures and stored functions using the native MariaDB SQL/PSM language
- Use JSON document stores
- Understand how to access MariaDB via APIs using both native and non-native MariaDB connectors
- Set SQL modes to affect error output and use the
SHOW WARNINGS
andSHOW ERRORS
statements to interpret error messages
- 7-1 Connecting to MariaDB with a PHP-Based Web Form
- 7-2 Connecting to MariaDB with a Python Script
- 7-3 Creating a View
- 7-4 Creating a Stored Procedure in SQL/PSM