Archive for February, 2011
I finally got around to finishing my testing of Excel 2011 querying MySQL. That meant installing and configuring Excel 2011 and MySQL 5.5.9 on Mac OS X (Snow Leopard). While installing Microsoft Office is pretty trivial, installing and configuring MySQL wasn’t. You can read about installing and configuring MySQL here. A quick test after this, led me to discover that you still need a third party ODBC, as covered in this earlier blog. You should take note that Microsoft’s future direction adopts Oracle ODBC, like the approach they’ve chosen with MySQL’s ODBC driver.
I downloaded and installed one of third party ODBC tool sets. I opted for OpenLink Software’s ODBC Driver. Instructions for the install with screen shots are in this blog page. The only downside of this was the discovery that Microsoft’s solution requires Rosetta, like Excel 2008. Rosetta enables Power PC application to run on Intel-based Mac OS X.
Launching the Database icon from Excel 2011, I configured the Data Source Name, which you can find here with screen shots. After you configure the Data Source Name, restarting Excel 2011 is the best choice because otherwise you may see several non-fatal errors.
The following screen shots show you how to establish a connection between Excel 2011 and MySQL, and how to query data from the MySQL database:
- After you click the Database icon, you see the following dialog. Select a Data Source Name and click the OK button to begin a query.
- The OpenLink MySQL Lite Login screen requires the user name and password. Click the Connect button to launch the Microsoft Query, which appears to be a native Power PC application that requires Rosetta to run it
- Microsoft Query appears to be a native Power PC application that requires Rosetta to run it. If you want to enter a query, click the SQL View button.
- Having clicked the SQL View button you now have a work area where you can enter a standard SQL
SELECTstatement, like the one below. Then, you click the Return Data button.
- This dialog lets you select where you want to put the return result set from the query. The default is the absolute cell reference of the top and left most cell,
$A$1. Click the OK button to query and load the data into the worksheet.
- Now you can see the data in the worksheet. The only problem is the extraneous characters returned into the column headers of the table. While tedious, they’re easy to fix. The following illustrates the downloaded result set from the previous query:
- If you perform a query with a join operation, the column names are never displayed whether you provide aliases to the query or not. It means you have to convert the table to a range, remove the false headers, and recreate the table. This appears to be a limitation of Microsoft Query and unlike the behavior in Excel 2010 on Windows. Perhaps it’s all wrapped up in the emulation provided by Rosetta but I couldn’t find any information about what’s happening. That leaves me with pure speculation, which I never like. If you find the reason, post a comment with a link because everyone would benefit.
As always, I hope this helps those who want to work only in the Mac OS X environment. The risk is Rosetta because it will go away, the only question is when and whether the vendors will fix their dependency first or not. The problem with this solution is that Microsoft Query doesn’t return any tables when it appears that it should.
I was surprised to discover the MySQL Workbench couldn’t add a user while working with MySQL Workbench 5.2.31 (ce) and MySQL 5.5.9. Naturally, I was tempted to simply drop to the command line and add it manually, but I thought about my students who struggle at the command line. Poking around, I discovered a Severity 1 bug for this issue and a way to fix most of it before the next release.
The problem returns a dialog box that says very little, as you can see:
More detail is posted in the status bar, where it provides the following error message. Unfortunately, there wasn’t much luck Googling it. Hopefully, this post will fix that.
Error in securityManager module: error calling WbAdmin.openSecurityManager: see output for details
Bug 59000 contains a copy of the Python code you’ll need to manually patch into your MySQL Workbench installation. That’s the corrected
wb_admin_security_be.py module that fixes most of the problem. The reason why I qualify it as most of the problem is because you can still raise an exception. The exception occurs when the the new user is granted any role.
You can avoid the error by only granting privileges. It appears that you can also ignore the error because it doesn’t mean that it failed. Just navigate away from the Accounts tab and back to see that the change was made. Also, it only happens when you’re creating a user not updating a user.
You can ignore the discussion over the
%MYSQL_WORKBENCH_INSTALLDIR% environment parameter. It’s simply never set when you install with the
mysql-5.5.8-winx64.msi file. You would have to set the environment variable manually in Windows. To save time, I’ve simply listed where the product installs on the 32-bit and 64-bit releases of Windows.
Here’s how you can manually patch it on Windows 7 (64-bit), and if you’re interested in learning a bit about Python, try The Quick Python Book, 2nd Edition:
- Download the file.
- Open Windows Explorer and navigate to
C:\Program Files (x86)\MySQL\MySQL Workbench 5.2 CE\modulesdirectory; and delete both the
wb_admin_security_be.pycfiles. You have to remove both because removing only the source Python (
.py) file won’t cause the interpreter to create a new byte code version (
*.pyc). You must remove the byte code version to force the interpreted to read the source file. If you’re on a 32-bit version of Windows you’ll find it in this
C:\Program Files\MySQL\MySQL Workbench 5.2 CE\modulesdirectory.
- Copy the downloaded
wb_admin_security_be.pyfile into the
C:\Program Files (x86)\MySQL\MySQL Workbench 5.2 CE\modulesdirectory.
- Launch MySQL Workbench from the Start menu and it will create the
wb_admin_security_be.pycfile from the source file you copied into the directory for step #3.
That should do it. As always, I hope this helps folk save time too.
Somebody raised the question about writing stored functions while we were discussing scalar subqueries against
COMMON_LOOKUP tables. Common look up tables store collections of possible
<OPTION> elements. They’re basically generalized tables that contain a set of smaller tables, where the row sets make up a list of unique values.
When you write a function that must return one and only one row, the
SELECT-INTO syntax is ideal in Oracle because it automatically raises an exception when the query returns no row or two or more rows. MySQL doesn’t raise an automatic exception when a
SELECT-INTO fails to return a row but it does raise an
ERROR 1722 when two or more rows are found.
Here are sample implementations written in Oracle’s PL/SQL and MySQL’s SQL/PSM languages. If you’re an Oracle developer and new to MySQL, the biggest oddity may be the delimiter, or it may be the sizing of formal parameters. Hopefully, its not the lack of formal declaration and exception blocks in SQL/PSM. For those coming from MySQL, the
OR REPLACE command or dynamic sizing of formal parameter list values are the big changes.
Oracle PL/SQL Function
The Oracle PL/SQL function is very straightforward and helped by pre-defined exceptions for both the no data found and too many rows found errors. Formal parameters inherit their physical size at run time from the calling scope program.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
-- Create or replace existing function with new implementation. CREATE OR REPLACE FUNCTION get_common_lookup_id ( pv_table VARCHAR2 , pv_column VARCHAR2 , pv_type VARCHAR2 ) RETURN NUMBER IS -- Declare a return variable. lv_return NUMBER; BEGIN -- Query data and assign it to a local variable. SELECT common_lookup_id INTO lv_return FROM common_lookup WHERE common_lookup_table = pv_table AND common_lookup_column = pv_column AND common_lookup_type = pv_type; -- Return the value found. RETURN lv_return; EXCEPTION -- Handle errors. WHEN NO_DATA_FOUND THEN RAISE; WHEN TOO_MANY_ROWS THEN RAISE; END; /
MySQL SQL/PSM Function
The lack of an implicitly raised exception when a
SELECT-INTO structure fails to return a row adds several steps to a SQL/PSM function. You must define a custom exception and an if-block to raise the exception, which is trigger by returning a null value into the
lv_return local variable.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41
/* Conditionally drop the function before trying to create it. */ DROP FUNCTION IF EXISTS get_common_lookup_id; /* Change the delimiter to write a stored procedure into the database. */ DELIMITER $$ /* Create a function that replaces a scalar subquery. */ CREATE FUNCTION get_common_lookup_id ( pv_table VARCHAR(30) , pv_column VARCHAR(30) , pv_type VARCHAR(30)) RETURNS INT UNSIGNED BEGIN /* Declare a return variable. */ DECLARE lv_return INT UNSIGNED; /* Declare a local variable for a subsequent handler. */ DECLARE no_data_found CONDITION FOR SQLSTATE '99001'; /* Query data and assign it to a local variable. */ SELECT common_lookup_id INTO lv_return FROM common_lookup WHERE common_lookup_table = pv_table AND common_lookup_column = pv_column AND common_lookup_type = pv_type; /* The local variable is only null when no rows are returned by the implicit cursor. */ IF lv_return IS NULL THEN SIGNAL no_data_found SET MESSAGE_TEXT = 'Result was no rows found.'; END IF; /* Return the value found. */ RETURN lv_return; END; $$ /* Reset the delimiter to the default to run other programs. */ DELIMITER ;
Hope this help solve your problems.
The original blog post with the images was causing a page loading delay, so I’ve moved the post to a page of it’s own. This link send you to the original post with how you setup MySQL Connector/ODBC driver, add a new data source in Windows 7, and a new data import source in Excel 2007. As with other “how-to” posts, it includes screen shots to clear up any ambiguity. I apologize for any inconvenience caused by moving the content.
If you want instructions for using Excel to access an Oracle database, check this older post. I’m working on documentation for a native Mac OS X and Excel 2011 solution and will update this when it’s ready.
As always, I hope this helps those trying to sort through how this works. Naturally, a Visual Basic for Applications (VBA) solution is a better alternative once you’ve set up the data source. This is also on my calendar for documentation.
I’d hoped for a better solution with Excel 2011 on Mac OS X but it appears we still need the third party drivers to connect Excel to MySQL. It’s the same as I reported earlier on Excel 2008. Here’s the message and you can click on it to go the web site and links to buy the drivers.
I tried getting away with installing the MySQL Connector/ODBC before downloading one of those recommended by Microsoft. I discovered that it didn’t work.
Therefore, I download and installed the OpenLink Software ODBC Software, instructions with screen shots are in this blog page. I discovered that this software support track isn’t purely native Mac OS X on Intel-based software because it requires Rosetta like Excel 2008. It also only connects with Microsoft Query, which appears to be a Power PC native application too, at least based on the interface and look and feel. I didn’t do much more research because I’ve concluded that connectivity isn’t ready for prime time on the Mac OS X platform. Microsoft still has more work to do.
Installing MySQL 5.5.9 (updated for MySQL 5.5.16 and 5.5.18) on Mac OS X was on my list but it finally made the top. I needed to write instructions for a class that I teach because asking students who own a Mac to install VMWare and Windows before MySQL seems an unnecessary burden. Especially when you can install it directly on Mac OS X.
Mac OS X Mountain Lion (10.8.*) need to install XCode and Command Line Tools before installing MySQL. You can refer to this XCode installation and configuration post for those steps.
You can download MySQL for the Mac OS X. I downloaded the Mac OS X, Version 10.6 (x86, 64-bit) version for this installation. There were a couple shell syntax tricks beyond the instructions and then you need to configure database. That’s required because the database is installed with an unsecured anonymous and root account. After the step-by-step installation instructions, you’ll find the configuration steps to enable you to access the MySQL Monitor from a terminal session. It also configures your
$PATH environment to enable you to start and stop the MySQL Daemon.
- After the download completes, open the file folder in the download directory.
- The open file folder and it will look like the following. Launch the
mysql-5.5.9-osx 10.6-x86_64.pkgfile, which installs the product.
- After launching the executable, you are now on the first page of the Install MySQL 5.5.9 installation application. Click the Continue button.
- This page contains the instructions, you can pause to read them or continue with these instructions. Click the Continue button to proceed.
- This page contains the General Public License (GPL). You agree or stop the installation. Click the Continue button to proceed.
- The following overlay dialog contains your agreement. Click the Agree button to proceed.
- There are fewer options in this installation than the Windows installation. While you can change the installation location, the software installs by default in the
/usr/local/mysqldirectory. The installation requires that you have a
mysqluser account on the operating system, and you don’t need to do anything because one exists as part of the default Mac OS X installation. Click the Install button to proceed.
- This dialog requires the system administrator’s password. Enter the valid password and click the OK button to proceed.
- Depending on the system, this could take more than a minute. All you can do it wait.
- This page tells you that you’ve completed the installation. Click the Close button to proceed.
- This step requires that you return to the download folder, shown in Step #2 above. Launch the
MySQLStartupItem.pkgand you’ll see the following MySQL Startup Item Installer dialog. Click the Continue button to proceed.
- This page contains the instructions for the MySQL Startup software, you can pause to read them or continue with these instructions. Click the Continue button to proceed.
- This page asks if you want to change the directory. I’d recommend you leave it as the default because it requires the System Administrator’s password to start and stop the database. It should also start for you every time you boot the machine. Click the Continue button to proceed.
- Like Step #8, this dialog requires the system administrator’s password. Enter the valid password and click the OK button to proceed.
- You could see a progress dialog box but generally it happens so fast you should land at the Installation was Successful dialog. Click the Close button to proceed.
- This step requires that you return to the download folder, shown in Step #2 above. Launch the
MySQL.prefPaneand you’ll see the following MySQL Preferences dialog. Click the Install button to proceed.
- Like Step #8 and #14, this dialog requires the system administrator’s password. Enter the valid password and click the OK button to proceed.
- Don’t click in the automatic start button unless you’re sure. Otherwise, there’s a lot of cleanup to be able to return to this point and start or stop the server as required. This is the last screen, you should click the Start MySQL Server button to start MySQL. While installing MySQL 5.5.18 I discovered that the service menu is no longer installed in the Preferences, and you must start it manually.
You can start and stop the service by opening your System Preferences, where you’ll find them in the bottom Other row. If the intent was to have it start automatically, sometimes the permissions are incorrect. You’ll get the following error in MySQL 5.5.9 but not in MySQL 5.5.16 because the MySQL DMG file is fixed. You can skip the instructions until you get to Configure User’s Shell Environment below:
"/Library/StartupItems/MySQLCOM" has not been started because it does not have the proper security settings.
You can fix this by changing the permissions manually in a Terminal Session. Launch a Terminal Session from your Utilities folder inside your Applications folder.
Change directory to the
/Library/StartupItems/MySQLCOM directory. Then, list the files. These commands should do the trick for you:
cd /Library/StartupItems/MySQLCOM ls -al
If you see these permissions, you have problem because the group for startup files should be
drwxr-xr-x 4 root staff 136 Jan 20 13:46 . drwxr-xr-x 4 root wheel 136 Feb 9 21:11 .. -rwxr-xr-x 1 root staff 1300 Jan 20 13:46 MySQLCOM -rw-r--r-- 1 root staff 469 Jan 20 13:46 StartupParameters.plist
You can change the files with this command:
sudo chown root:wheel *
However, now you need to step up the directory tree one level to
/Library/StartupItems, and change the ownership of:
drwxr-xr-x 4 root staff 136 Jan 20 13:46 MySQLCOM
There are two commands to do this. The first changes directory by moving to the parent directory in the hierarchy (the parent directory is represented by two dots). The second changes the group ownership of the
cd .. sudo chown root:wheel MySQLCOM
Now you need to configure your shell environment and harden the database. Hardening means securing accounts with passwords. They’re covered in the next two sections.
Configure User’s Shell Environment
Assuming you accepted the defaults, you should be able to copy the required instructions directly into a
.bash_login file if one exists. Unless you’ve created one before there won’t be a file. Mac OS X doesn’t automatically create the file. If you don’t have the file, you can create one with the following syntax:
You can open the file with the
vi editor or a text editor of your choice. MySQL 5.5.9 installed in
/usr/local/mysql, which has changed to
/usr/local/mysql-version as noted below. You can copy the following contents into the file for MySQL 5.5.16 but will need to change the file for earlier or later releases (added
sudo per Don McArthur’s comment):
# Set the MySQL Home environment variable to point to the root directory of the MySQL installation. export set MYSQL_HOME=/usr/local/mysql-5.5.16-osx10.6-x86_64 # Add the /bin directory from the MYSQL_HOME location into your $PATH environment variable. export set PATH=$PATH:$MYSQL_HOME/bin # Create aliases that make it easier for you to manually start and stop the MySQL Daemon. alias mysqlstart="sudo /Library/StartupItems/MySQLCOM start" alias mysqlstop="sudo /Library/StartupItems/MySQLCOM stop"
Changes between MySQL 5.5.16 and 5.5.18 introduce a new directory structure, you need to use the following in the
.bash_login file (added
sudo and status per Don McArthur’s comments):
# Set the MySQL Home environment variable to point to the root directory of the MySQL installation. export set MYSQL_HOME=/usr/local/mysql-5.5.18-osx10.6-x86_64 # Add the /bin directory from the MYSQL_HOME location into your $PATH environment variable. export set PATH=$PATH:$MYSQL_HOME/bin # Create aliases that make it easier for you to manually start and stop the MySQL Daemon. alias mysqlstart="sudo /Library/StartupItems/MySQLCOM/MySQLCOM start" alias mysqlstop="sudo /Library/StartupItems/MySQLCOM/MySQLCOM stop" alias mysqlstatus="ps aux | grep mysql | grep -v grep"
As pointed out by Shashank’s comment, you should now use the following aliases:
alias mysqlstart='sudo /usr/local/mysql/support-files/mysql.server start' alias mysqlstop='sudo /usr/local/mysql/support-files/mysql.server stop'
You need to save the file and close and restart a new Terminal session to place these environment variables in scope. You could also run the following to put them in scope without closing and opening the terminal:
The preceding command sources the environment file into active memory. This should configure your environment. After restarting the shell, you should be able to run this command to confirm the new environment:
which -a mysql
It should return:
Secure the Database
This is presently necessary because of the different file structure in a Mac OS X MySQL install, which disables the
mysql_secure_installation file from running successfully. You can manually edit the file or follow these steps.
You need to connect to the database as the privileged super user,
root user. This is simple because the installation doesn’t set any passwords. You open another Terminal session to make these changes or you could install MyPHPAdmin or MySQL Workbench. The tools work as well in fixing the majority of issues.
Once connected to the database as the
root user, you can confirm that passwords aren’t set and an insecure anonymous user account has been previously configured. You do that by connecting to the
mysql database, which is the database catalog for MySQL. You do that by running the following command:
You can query the result set with the following query:
SELECT USER, password, host FROM USER\G
You should see the following output plus the user’s name preceding the
iMac.local) host name value:
*************************** 1. row *************************** user: root password: host: localhost *************************** 2. row *************************** user: root password: host: MacPro.local *************************** 3. row *************************** user: root password: host: 127.0.0.1 *************************** 4. row *************************** user: root password: host: ::1 *************************** 5. row *************************** user: password: host: localhost *************************** 6. row *************************** user: password: host: MacPro.local
You now need to change the password for the
root user. I would suggest that you do this with the SQL command rather than a direct update against the data dictionary tables. The syntax to fix the
root user account require you enter the user name, an
@ symbol, and complete
host values, like:
SET PASSWORD FOR 'root'@'localhost' = password('cangetin'); SET PASSWORD FOR 'root'@'MacPro.local' = password('cangetin'); SET PASSWORD FOR 'root'@'127.0.0.1' = password('cangetin'); SET PASSWORD FOR 'root'@'::1' = password('cangetin');
You should be able to drop both
anonymous user rows with the following syntax, but I did encounter a problem. Assuming you may likewise encounter the problem, the fix follows the first commands you should try:
DROP USER ''@'localhost'; DROP USER ''@'MacPro.local';
If either of the anonymous accounts remain in the
USER table, you can manually drop them from the database catalog. This syntax will get rid of them:
DELETE FROM USER WHERE LENGTH(USER) = 0;
You’ve completed the configuration and can now type
quit; to exit the MySQL Monitor. To reconnect, you’ll now need a password, like this:
mysql -uroot -pcangetin
Also, don’t forget to use a real password. The one shown here is trivial, which means easy to hack. Use something that others might not guess.
You can copy one of the sample configuration files as a starting point (as provided by Don McArthur’s comment):
sudo cp /usr/local/mysql/support-files/my-huge.cnf /etc/my.cnf
Starting and Stopping the Database
You can manually start the database server with the following command, which you defined as aliases in your
.bash_login shell script:
Stopping it is also straightforward, you do this:
You can check it’s status with this command:
A friend wanted to know how to create a synonym to a library of stored functions and procedures in MySQL. I had to deliver the sad news that you can’t create a synonym in MySQL 5.0, 5.1, or 5.5. You need to write a wrapper function or procedure.
A synonym (available in Oracle) would eliminate the need to call a function from another database through the component selector (or period). That’s because it maps a name to the reference and name instead of requiring you to include it with each call. The following shows a call from outside of the
SELECT lib.demo('Hello World!') AS "Statement";
He asked how to do it, so here’s how you do it below. Assume the following for this example:
- You have a
libdatabase where you define all your functions, procedures, and tables.
- You have a
appdatabase that supports web connections.
- You don’t want to allow web users to connect directly to the
libdatabase because access to the tables should only be available through the functions and procedures.
This base function is simplified to avoid interaction with table data but illustrate the technique of definer rights functions. The function takes a string of up to 20 characters and returns it enclosed in double quotes. To mimic these behaviors, as the
root user, you should create the
lib databases, the
web users, and grant privileges to the
dev user to act in the
lib database and the
web user to act in the
app database. The example below does this as the
root user, but in real life don’t use a trivial password like the example:
/* Create the two databases. */ CREATE DATABASE app; CREATE DATABASE lib; /* Create the two users, the developer can only connect locally. */ CREATE USER 'dev'@'localhost' IDENTIFIED BY 'dev'; CREATE USER 'web'@'%' IDENTIFIED BY 'web'; /* Grant privileges to be a definer in both databases. */ GRANT ALL ON app.* TO 'dev'@'localhost'; GRANT ALL ON lib.* TO 'dev'@'localhost'; /* Grant privileges to any function or privilege in the APP database to the WEB user. */ GRANT EXECUTE ON app.* TO 'web'@'%';
After creating and granting all the appropriate privileges, here are the steps to create the test case.
- You create and test the function as the
devuser in the
/* Set the delimiter to something other than a semi-colon so they can be typed in statements. */ DELIMITER $$ /* Create a function that echoes back the string with double quotes. */ CREATE FUNCTION demo(str VARCHAR(20)) RETURNS VARCHAR(20) BEGIN RETURN CONCAT('"',str,'"'); END; $$ /* Reset the DELIMITER value. */ DELIMITER ; /* Query the function. */ SELECT demo('Ciao amico!') AS "Statement";
- You create and test the wrapper function as the
devuser in the
/* Set the delimiter to something other than a semi-colon so they can be typed in statements. */ DELIMITER $$ /* Create a function that echoes back the string with double quotes. */ CREATE FUNCTION demo(str VARCHAR(20)) RETURNS VARCHAR(20) BEGIN RETURN lib.demo(str); END; $$ /* Reset the DELIMITER value. */ DELIMITER ; /* Query the function. */ SELECT demo('Ciao amico!') AS "Statement";
If you’re wondering why a
GRANT wasn’t required from the
lib database to the
app database, it’s because the
dev user has access to both databases and defined both objects.
- You can test the wrapper function as the
webuser in the
SELECT demo('Yes, it works!') AS "Statement";
This is the closest to a synonym for a function or procedure that is possible. I know this solves his problem and hope it solves a couple others too.
A second Excel 2011 question in as many days, February is off and running. The question posed: “How do you create a data validation list without referring to a range of cells?”
You can create a data validation list without referring to a cell range. There’s a small trick to it. You enter the list of values as comma delimited strings, but you don’t enclose them in double quotes. Excel reads a set of literal text strings as if they’re a list of cell values, which is why you don’t provide the double quotes that you use in every function.
The desired list was either TRUE or FALSE, and the required behavior was that they function like Boolean variables. Fortunately, that’s straightforward when you remember that the case insensitive text strings
FALSE are Boolean values.
You create a data validation list by navigating to the Data Tab (Windows Excel 2010 it’s a Ribbon), and choose Validate. You’ll see the following list. Click on Data Validation… in the list, as shown below.
The selection launches the Data Validation dialog box. Choose List from the Allow poplist. Then, enter
TRUE,FALSE in the Source entry field, like this:
Now click OK and you have a poplist. Hope this answer the question for others too.
I had an interesting reaction when I told my students they had to create a dynamic Excel model that required them to enter the months of the year without AutoFill feature. They were stumped. They couldn’t find a function to perform it for them.
It’s really quite easy, the
EDATE function lets you find it directly. Enter any day in the current month as the first parameter and one as the number of months to add as the second parameter to the function. Voilà, you have a function to add a month to any date. The only time it returns the first of the next month is when the source date was the first day of the current month.
Here’s the solution when cell
B1 contains January 1, 2011 and you want cell
C1 calculated as the first day of February in the same year.
You can also use the
EOMONTH function. It lets you find it the first day of the next month regardless of the start date. All you need is a simple trick. Enter any day in the current month as the first parameter and zero as the number of months to add as the second parameter to the function, and then add one.
Here’s the solution when cell
B1 contains any day in January and you want cell
C1 calculated as the first day of February in the same year.
A subsequent question asked how to calculate the 15th day of a month, and here’s the formula:
Hope this helps them and others looking for the trick.