Matthew Hodge
Full Stack Developer

To get us started with some useful MySQL commands I asked ChatGTP the following: "provide a basic employee table containing fake data with columns id, name, department and salary where the department information is in another table".

Lets go ahead and make a database called useful_mysql_commands and import the script below:

import.sql
CREATE TABLE departments (
  id INT AUTO_INCREMENT PRIMARY KEY,
  name VARCHAR(255)
);

INSERT INTO departments (name)
VALUES
  ('Sales'),
  ('Marketing'),
  ('Technology');

CREATE TABLE employees (
  id INT AUTO_INCREMENT PRIMARY KEY,
  name VARCHAR(255),
  department_id INT,
  salary INT,
  FOREIGN KEY (department_id) REFERENCES departments(id)
);

INSERT INTO employees (name, department_id, salary)
VALUES
  ('John Doe', 1, NULL),
  ('Jane Doe', 2, 65000),
  ('Bob Smith', 3, 75000),
  ('Alice Johnson', 1, 65000),
  ('Michael Davis', 2, 75000),
  ('Sarah Wilson', 3, 55000),
  ('William Brown', 1, 75000),
  ('Emma Wilson', 2, 65000),
  ('David Johnson', 3, 55000),
  ('Natalie Davis', 1, 65000),
  ('Richard Wilson', 2, 75000),
  ('Angela Johnson', 3, 55000),
  ('Christopher Davis', 1, 65000),
  ('Ashley Brown', 2, 75000),
  ('Brian Wilson', 3, 55000),
  ('Margaret Johnson', 1, 65000),
  ('Evelyn Davis', 2, 75000),
  ('Dorothy Wilson', 3, 55000),
  ('Joseph Brown', 1, 65000),
  ('Ava Johnson', 2, 75000),
  ('Daniel Davis', 3, 55000),
  ('Ruth Wilson', 1, 65000),
  ('Ryan Johnson', 2, 75000),
  ('Edward Davis', 3, 55000),
  ('Maria Brown', 1, 75000),
  ('Samantha Johnson', 2, 65000),
  ('Jenna Davis', 3, 55000),
  ('Harold Wilson', 1, 65000),
  ('Isabelle Johnson', 2, 75000),
  ('Brandon Davis', 3, 55000),
  ('Madison Brown', 1, 65000),
  ('Keith Johnson', 2, 75000),
  ('Deborah Wilson', 3, 55000),
  ('Mark Davis', 1, 65000),
  ('Olivia Johnson', 2, 75000),
  ('Avery Davis', 3, 55000),
  ('Aiden Wilson', 1, 65000),
  ('Lucas Johnson', 2, 75000),
  ('Ethan Davis', 3, 55000),
  ('Mia Brown', 1, 75000),
  ('William Johnson', 2, 65000),
  ('Sofia Davis', 3, 55000),
  ('Mason Wilson', 1, 65000),
  ('Emily Johnson', 2, 75000),
  ('Ella Davis', 3, 55000);

Great, you might of noticed that our first record is null regarding John Doe's salary (which is intended), now that our Database is ready and our records have been imported, let's take a look at some useful MySQL commands

1. COALESCE

The COALESCE function is a function in MySQL used to return the first non-NULL value from a list of expressions. It is often used to substitute missing or NULL values with a default value.

Example:

Suppose we have a table named employees with columns id, name, department, and salary. Some of the salary values are missing in the table, and we want to replace the missing values with the value R10,000.

To replace the missing salary values with R10,000, we can use the following query:

caolesce.sql
SELECT 
    id, 
    name
    COALESCE(salary, 10000) as salary
FROM 
    employees;

In this query, the COALESCE function is used to substitute missing or NULL salary values with R10,000. The resulting table will have the missing salary values replaced with R10,000.

In conclusion, the COALESCE function is an important function in MySQL that can help you manage missing or NULL values in your data by providing a default value.

2. Simple Join

A join is a way to combine rows from two or more tables based on a related column between them. In MySQL, we have several types of join like inner join, left join, right join, and full outer join.

Example:

Using the two tables named employees and departments, To get the information of employees along with their respective departments, we can use the following query:

simple-join.sql
SELECT 
    employees.name, 
    departments.name
FROM 
    employees
JOIN 
    departments 
ON 
    employees.department_id = departments.id;

3. Group By

The GROUP BY clause is used to group rows with similar values into summary rows. It is often used with aggregate functions like SUM, AVG, MAX, MIN, COUNT, etc.

Example:

To get the average salary of employees grouped by departments, we can use the following query:

simple-group-by.sql
SELECT 
    departments.name,
    AVG(salary)
FROM 
    employees
JOIN 
    departments 
ON 
    employees.department_id = departments.id
GROUP BY 
    departments.name;

4. Having Clause on Group By

The HAVING clause is used to filter the grouped result set and limit the number of groups returned. It is used after the GROUP BY clause and works like a filter on the groups.

Example:

To get the average salary of employees grouped by departments where the average salary is greater than R60,000, we can use the following query:

having-clause.sql
SELECT 
    departments.name,
    AVG(salary)
FROM 
    employees
JOIN 
    departments 
ON 
    employees.department_id = departments.id
GROUP BY 
    departments.name;
HAVING
    AVG(salary) > 60000;

5. Indexes what they are and how to create one

Indexes are a common way to improve the performance of queries in MySQL. By creating an index on one or more columns of a table, you can speed up searches and reduce the amount of data that needs to be scanned.

Here's how you can create an index on the employees table:

index-employee-name.sql
CREATE INDEX idx_employee_name ON employees (name);

In this example, we're creating an index on the name column of the employees table. The idx_employee_name is the name of the index, which you can choose to be anything meaningful.

If you frequently run queries that search for employees by name, this index will significantly improve the performance of those queries. For example, here's a query that searches for all employees with a name containing 'John':

select-after-index.sql
SELECT 
    * 
FROM 
    employees
WHERE 
    name 
LIKE '%John%';

With the index in place, this query will run much faster and be more efficient than without the index.

It's important to note that while indexes can improve query performance, they can also slow down updates and inserts, since the database has to maintain the index whenever data is changed. So it's important to consider the trade-off and choose which columns to index carefully.


I was recently tasked with implementing a SOAP service in order to interact with an accounting system and an ecommerce store, to update products based on the data returned from the service i.e pricing and stock updates scheduled to run on a schedule.

I use a great WsdlToPhp/PackageGenerator on GitHub to generate PHP classes from a WSDL, I thought to myself why not write about it in the hopes of it being of use for other developers.

Lets take a look at this example calculator wsdl which will allow us to use the functions thereof to perform some calculations. I'm sure your WSDL will be more complex and have a better use case, but this is just to get you started

directory structure
- calculator.php
- index.php
- composer.json
sample soap service
http://www.dneonline.com/calculator.asmx?wsdl
composer.json
{
    "name": "mhodge/playground",
    "autoload": {
        "psr-4": {
            "Calculator\\": "Calculator/src/Calculator"
        }
    },
    "require-dev": {
        "wsdltophp/packagegenerator": "^4.1"
    }
}

calculator.php
<?php
require_once 'vendor/autoload.php';

use WsdlToPhp\PackageGenerator\ConfigurationReader\GeneratorOptions;
use WsdlToPhp\PackageGenerator\Generator\Generator;

$options = GeneratorOptions::instance();
$options
    ->setOrigin('http://www.dneonline.com/calculator.asmx?wsdl')
    ->setDestination('./Calculator')
    ->setComposerName('calculator')
    ->setNamespace('Calculator');

    // Generator instantiation
$generator = new Generator($options);

// Package generation
$generator->generatePackage();
index.php
<?php
require_once 'vendor/autoload.php';

$options = [
    WsdlToPhp\PackageBase\AbstractSoapClientBase::WSDL_URL => 'http://www.dneonline.com/calculator.asmx?wsdl',
    WsdlToPhp\PackageBase\AbstractSoapClientBase::WSDL_CLASSMAP => \Calculator\ClassMap::get(),
];
/**
 * Samples for Add ServiceType
 */
$add = new \Calculator\ServiceType\Add($options);
/**
 * Sample call for Add operation/method
 */
if ($add->Add(new \Calculator\StructType\Add(668,669)) !== false) {
    print_r($add->getResult());
    print_r("<br />");
} else {
    print_r($add->getLastError());
    print_r("<br />");
}

Why 668 + 669 ? well thats just 1337!

There are many other options you can set and use for your project other than the ones I used above, I highly recommend going through all of them to get familiar so you can apply to your use case.

One of the options I find really usefull is overriding the xsd_types of the wsdl you are generating this Weather WSDL for example uses multiple XSD types string, decimal, datetime etc. It's sometimes usefull to extend the default xsd_types.yml and specify the type you want returned for your WSDL.

i.e you could update the index.php and set the setXsdTypesPath directive

calculator.php - updated
require_once 'vendor/autoload.php';

use WsdlToPhp\PackageGenerator\ConfigurationReader\GeneratorOptions;
use WsdlToPhp\PackageGenerator\Generator\Generator;

$options = GeneratorOptions::instance();
$options
    ->setOrigin('http://www.dneonline.com/calculator.asmx?wsdl')
    ->setDestination('./Calculator')
    ->setComposerName('calculator')
    ->setNamespace('Calculator')
    ->setXsdTypesPath('xsd_types.yml');

    // Generator instantiation
$generator = new Generator($options);

// Package generation
$generator->generatePackage();
xsd_types.yml
# List of XSD types that can be defined as a type hint parameter.

xsd_types:
    anonymous: "string"
    anySimpleType: "string"
    anyType: "string"
    anyURI: "string"
    base64Binary: "string"
    bool: "bool"
    boolean: "bool"
    byte: "string"
    date: "string"
    dateTime: "string"
    decimal: "float"
    double: "float"
    DayOfWeekType: "string"
    DOMDocument: "string"
    duration: "string"
    ENTITY: "string"
    ENTITIES: "string"
    float: "float"
    gDay: "string"
    gMonth: "string"
    gMonthDay: "int"
    gYear: "string"
    gYearMonth: "int"
    hexBinary: "string"
    int: "int"
    integer: "int"
    ID: "string"
    IDREF: "string"
    IDREFS: "string"
    language: "string"
    long: "int"
    Name: "string"
    negativeInteger: "int"
    nonNegativeInteger: "int"
    nonPositiveInteger: "int"
    normalizedString: "string"
    NCName: "string"
    NMTOKEN: "string"
    NMTOKENS: "string"
    NOTATION: "string"
    positiveInteger: "int"
    QName: "string"
    short: "int"
    string: "string"
    timestamp: "int"
    timeStamp: "int"
    time: "string"
    token: "string"
    unsignedByte: "string"
    unsignedInt: "int"
    unsignedLong: "int"
    unsignedShort: "int"
    UID: "string"
    UNKNOWN: "string"

I have recently been doing some updates to a website I created and had the need to alter the diffForHumans output that carbon provides to a laravel models created at time. Found this pretty interesting and thought I would share.

The system is a listing portal where you are either wanting to buy something other people have listed or alternatively you can list an item you would like to sell.

Difference for humans
Listing::first()->created_at
// => Illuminate\Support\Carbon @1667281103 {#4822
//      date: 2022-11-01 05:38:23.0 UTC (+00:00),
//    }

Listing::first()->created_at->diffForHumans();
// => "10 minutes ago"

Listing::first()->created_at->diffForHumans([
    'parts' => 1
]);
// => "10 minutes ago"

>>> Listing::first()->created_at->diffForHumans([
    'parts' => 2
]);
// => "10 minutes 36 seconds ago"

Listing::first()->created_at->diffForHumans([
    'parts' => 2,
    'join' => ' and '
]);
// => "10 minutes and 36 seconds ago"

I hope you find the above as usefull as I have. To find out more head over to nesbot carbon difference for humans and see how you can use it in your project.


Over the course of a good couple of years I have built up quite a library of Gists, which is something I highly recomend every developer does, I thought it would be nice to share a couple of bash/terminal comamnds that have been pretty usefull

Zipping and Unzipping files and or folders

Calculate size of directories

Copying or syncing one location to another

Memory

Zipping and Unzipping files and or folders

using tar
man tar
tar --help # Shows all available params
zip file/folders
# c = create
# z = filter the archive through gzip
# v = verbosely list files processed
# f = use the archive file or device archive

# tar -czvf <name_of_tar_gz_file> <directory_or_file_to_compress>

tar -czvf plugins.tar.gz plugins
unzip file/folders
tar --help # Shows all available params

# x = extract from the arhive
# z = filter the archive through gzip
# v = verbosely list files processed
# f = use the archive file or device archive

# tar -czvf <name_of_tar_gz_file> <directory_or_file_to_compress>
tar -czvf plugins.tar.gz plugins

Calculate size of directories

using du
man du
du --help # Shows all available params
dir size
# you may or maynot need to sudo pending the file and folder 
# permissions of the directory you are wanting to calculate

# s = summarize
# h = human readable (my favourite)

# du -sh <path>/*
du -sh ./* # will display summary of the current working directory

Copying or syncing one location to another

rsync
man rsync
rsync --help
how to use
# a = archive mode; equals -rlptgoD (no -H,-A,-X)
# v = increase verbosity
# z = compress file data during the transfer
# h = output numbers in a human-readable format
# --dry-run = simulates what would happen but does not actualy copy or sync

# local example
# rsync -avzh <source> <dest> --dry-run

# copy a file/folder from local to server over ssh
# rsync -avzh <source>/* <user>@<host>:<dest>/ --dry-run

# copy a file/folder from local to a server over ssh with a non standard port
# rsync -avzh --rsh='ssh -p2345' <source>/* user@host:<dest>/ --dry-run

rsync -avzh ./ /tmp/backups # copy files/folders from current working directory to backup directory

Memory

using free
man free
free --help
system memory
# m = show output in mebibytes
# h = show human-readable output

free -mh # available memory
ps
man ps
ps --help simple
what is using the memory
ps aux  | awk '{print $6/1024 " MB\t\t" $11}'  | sort -n # memory used by what

WordPress along with its plugins directory has a folder called mu-plugins which when created inside the wp-content directory, will "force" WordPress to load the "must use" plugins from this directory.

A typical fresh install of WordPress has the following inside the plugins: default - plugins

Lets say just for arguments sake that you would like to set the error reporting level in PHP to display all errors BUT not deprecation warnings.

You can then create the following file in the mu-plugins directory as per below

/wp-content/mu-plugins/error-reporting.php
<?php 
/*
    Plugin Name: PHP - Don't report deprecation warnings
    Description: Don't report deprecation warnings
*/
error_reporting(E_ALL & ~E_DEPRECATED);

Now when you refresh your plugins page you will notice the new link "Must-Use" which will show the plugins WordPress has been told to load.

must use plugin added

If you open the Must-Use link you will see the plugins listed which are being force loaded i.e you cannot activate, deactivate or remove (as the name implied, Must Use!)

must use plugin list

Now you will not have your log files building up with deprecation notices!