{"id":398,"date":"2015-03-05T13:34:58","date_gmt":"2015-03-05T13:34:58","guid":{"rendered":"https:\/\/solutionstreet.com\/blog\/?p=398"},"modified":"2015-03-05T13:34:58","modified_gmt":"2015-03-05T13:34:58","slug":"mysql-postgresql-migration","status":"publish","type":"post","link":"https:\/\/www.solutionstreet.com\/blog\/2015\/03\/05\/mysql-postgresql-migration\/","title":{"rendered":"MySQL to PostgreSQL Migration"},"content":{"rendered":"<p><strong>\u00a0<\/strong><\/p>\n<p>By: Mike B.<\/p>\n<p>&nbsp;<\/p>\n<p>&nbsp;<\/p>\n<p>&nbsp;<\/p>\n<h1><\/h1>\n<p>In a recent customer engagement we were tasked with migrating an application from MySQL to PostgreSQL. At first glance, this would appear to be a relatively simple and straightforward process. However, there are significant differences in how PostgreSQL and MySQL implement the ANSI-SQL standard which necessitated a review and refactoring of the data access objects, some interesting differences in the support of various data types, and a couple of production configuration issues we had not expected.<\/p>\n<p><strong><strong>\u00a0<\/strong><\/strong><\/p>\n<p>What follows is a brief outline of our approach, a few techniques and tools that proved useful in transitioning the application to a PostgreSQL backend, and a list of common sql query features we addressed. The project used python so several of the utilities mentioned in the outline are python-related.<\/p>\n<p>&nbsp;<\/p>\n<p>&nbsp;<\/p>\n<h1>Installing &amp; Using PostgreSQL<\/h1>\n<p>As with all modern db systems, there are on-line instructions and tools that make working with Postgres and MySQL easy. There are many <a href=\"https:\/\/wiki.postgresql.org\/wiki\/Community_Guide_to_PostgreSQL_GUI_Tools\">GUI<\/a> tools available for working with PostgreSQL. Our team works primarily with Linux systems and we opted to use <a href=\"http:\/\/www.pgadmin.org\/http:\/\/www.pgadmin.org\">pgAdmin3<\/a>. In our work, the pgAdmin3 GUI did have a number of limitations and most of us chose to work primarily with the command line utility, psql.<\/p>\n<p><strong><strong>\u00a0<\/strong><\/strong><\/p>\n<p>PSQL is a terminal-based front-end to PostgreSQL that provides an interactive session as well as support for using sql\/data files. It provides a number of meta-commands (very linux like) and features that support a variety of tasks. For example in MySQL you use &#8216;show databases&#8217; or &#8216;show tables&#8217; to see lists of database objects. The corresponding commands in psql are &#8216;\\l&#8217; and &#8216;\\d&#8217;. \u00a0PostgreSQL provides good in-line documentation for the commands and there are many helpful on-line resources, e.g., <a href=\"http:\/\/www.postgresonline.com\/downloads\/special_feature\/postgresql83_psql_cheatsheet.pdf\">PSQL Cheat Sheet<\/a>.<\/p>\n<p>&nbsp;<\/p>\n<p>&nbsp;<\/p>\n<h3><b>PostgreSQL Roles<\/b><\/h3>\n<p>PostgreSQL manages data access using roles and it is critical that they are properly defined in order to protect the data and data access. Roles that have log-in rights are called users.<\/p>\n<p>Roles can also be a group of users, or a group of roles, depending on how they are constructed.<\/p>\n<p><strong><strong>\u00a0<\/strong><\/strong><\/p>\n<p>By default, all roles have create and usage rights on the public schema. That is, all roles that can connect to a PostgreSQL database have the ability to create objects in the \u2018public\u2019 schema. And moreover, these privileges are also extended to any roles that might be created later. \u00a0To reiterate, any particular role will have the sum of privileges granted directly to it, privileges granted to any role (i.e., group) it is presently a member of, and those privileges explicitly granted to public.<\/p>\n<p><strong><strong>\u00a0<\/strong><\/strong><\/p>\n<p>In order to properly limit access to data objects in the database schema, the following script was used to create the appropriate access rights for developer use on local systems and on integration servers.<\/p>\n<p>&nbsp;<\/p>\n<pre lang=\"LANGUAGE\" line=\"1\">Create your userCREATE USER &lt;user&gt; WITH PASSWORD '&lt;user_passwd&gt;\u2019;\nCreate a new databaseCREATE DATABASE my_pg_database\n\nWITH OWNER = testdba\n\nENCODING = 'UTF8'\n\nTABLESPACE = pg_default\n\nLC_COLLATE = 'en_US.UTF-8'\n\nLC_CTYPE = 'en_US.UTF-8'\n\nCONNECTION LIMIT = -1;\nLimit access\nREVOKE ALL ON SCHEMA PUBLIC FROM PUBLIC;\n\nREVOKE ALL ON ALL TABLES IN SCHEMA PUBLIC FROM PUBLIC;\n\nREVOKE ALL PRIVILEGES ON ALL SEQUENCES IN SCHEMA PUBLIC FROM PUBLIC;\n\nREVOKE ALL PRIVILEGES ON ALL FUNCTIONS IN SCHEMA PUBLIC FROM PUBLIC;\n\nREVOKE CREATE ON SCHEMA PUBLIC FROM PUBLIC;\n\nREVOKE CREATE ON SCHEMA PUBLIC FROM &lt;user&gt;;\nGRANT USAGE ON SCHEMA PUBLIC TO &lt;user&gt;;\n\nGRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA PUBLIC TO \u00a0&lt;user&gt;;\n\nGRANT USAGE, \u00a0SELECT ON ALL SEQUENCES IN SCHEMA PUBLIC TO \u00a0&lt;user&gt;;\n\nGRANT EXECUTE ON ALL FUNCTIONS IN SCHEMA PUBLIC TO \u00a0&lt;user&gt;;\nALTER DEFAULT PRIVILEGES IN SCHEMA PUBLIC GRANT SELECT, INSERT, UPDATE, DELETE ON TABLES TO \u00a0&lt;user&gt;;\n\nALTER DEFAULT PRIVILEGES IN SCHEMA PUBLIC GRANT USAGE, SELECT ON SEQUENCES TO \u00a0&lt;user&gt;;\n\nALTER DEFAULT PRIVILEGES IN SCHEMA PUBLIC GRANT EXECUTE ON FUNCTIONS TO \u00a0&lt;user&gt;;\nREVOKE CONNECT ON DATABASE &lt;database&gt; FROM PUBLIC;\n\nGRANT CONNECT ON DATABASE &lt;database&gt; TO \u00a0&lt;user&gt;;\n<\/pre>\n<p>&nbsp;<\/p>\n<h1>Migration Process<\/h1>\n<p>While the basic plan for migration is simple:<\/p>\n<p><strong><strong>\u00a0<\/strong><\/strong><\/p>\n<ul>\n<li>Define a new PostgreSQL schema based on the existing production MySQL schema.<\/li>\n<li>Create an empty database with this schema on a PostgreSQL instance.<\/li>\n<li>Migrate data from the MySQL database into the new PostgreSQL schema.<\/li>\n<li>Verify the data migration via tests, sql reports, and QA regression testing.<\/li>\n<\/ul>\n<p><strong><strong>\u00a0<\/strong><\/strong><\/p>\n<p>the implementation of these steps was the challenge.<\/p>\n<p>&nbsp;<\/p>\n<p>&nbsp;<\/p>\n<h3><b>Schema Conversion<\/b><\/h3>\n<p>The most important migration issue to be addressed in our work revolved around data types. \u00a0MySQL and PostgreSQL differ in their support of <a href=\"http:\/\/dev.mysql.com\/doc\/workbench\/en\/wb-migration-database-postgresql-typemapping.html\">data types <\/a>which requires re-mapping or translating the MySQL data and potentially, default values, into data types that PostgreSQL supports. To make this process fast, efficient, and as error-free as possible, we needed a migration-data-conversion tool.<\/p>\n<p><strong><strong>\u00a0<\/strong><\/strong><\/p>\n<p>After some research and testing we opted to use, <a href=\"https:\/\/github.com\/philipsoutham\/py-mysql2pgsql\">py-mysql2pgsql <\/a>instead of building a custom tool. This module is easily installed via pip (python package installer), has great documentation, and is easy to configure. Executing the script is simple, requiring a small YAML file that defines the two database instances to be used and settings that support schema migration, or data migration, or both. The ability to quickly switch between these migration options proved exceedingly valuable, allowing us to differentiate various schema\/data loading errors throughout the project\u2019s lifetime.<\/p>\n<p>&nbsp;<\/p>\n<h4 style=\"margin: 10px 20px;\"><b>The production MySQL database schema was being actively modified while the migration effort was underway. Having a tool that allowed us to simply and quickly identify and compare schemas vs data issue was truly invaluable.<\/b><\/h4>\n<p>&nbsp;<\/p>\n<p>As with most of our python projects, py-mysql2pgsql was installed into a virtualenv to make it simpler to upgrade and manage. We used python2.7 for the virtualenv. Installation was simple and all the dependencies were correctly downloaded and installed. In the version we downloaded (v.0.1.5), and for our purposes, it was necessary to create a simple patch file to provide a PostgreSQL supportable default for mysql timestamps set as &#8216;0000-00-00\u2019 and null. There are notes about this issue on the py-mysql2pgsql site in the Conversion caveats section of the <a href=\"https:\/\/github.com\/philipsoutham\/py-mysql2pgsql\">site<\/a>.<\/p>\n<p><b>Commands to instantiate the migration tool.<\/b><\/p>\n<pre lang=\"LANGUAGE\" line=\"1\">virtualenv --no-site-packages ~\/envs\/py-mysql2pgsql\nsource ~\/envs\/py-mysql2pgsql\/bin\/activate\npip install py-mysql2pgsql\n<\/pre>\n<p><b>Command to run the migration tool<\/b><\/p>\n<pre lang=\"LANGUAGE\" line=\"1\">py-mysql2pgsql -v -f mysql2pgsql.yml<\/pre>\n<p>&nbsp;<\/p>\n<p>In the early stages of the migration process, the py-mysql2pgsql script was used to convert and migrate the database structures and a small subset of data. In our testing we immediately noted that not all of the MySQL data structures (e.g., triggers, stored-procs, etc.) were migrated by comparing the ddls for each database. In our case, there were a minimal number of triggers and functions, and these were re-crafted and added as a separate set of installation scripts into our comprehensive migration script. We were exceptionally happy to see that the conversion of MySQL auto-increment attributes into PostgreSQL sequences was properly handled.<\/p>\n<p><strong><strong>\u00a0<\/strong><\/strong><\/p>\n<p>At this juncture, our team started running the full suite of unit tests (JUnit) and integration tests (Selenium) against the new schema and application code. It was very important to have a comprehensive and high coverage test-set in order to quickly uncover sql statement flaws and data conditions. The application of interest had a significant set of highly optimized manual sql queries and these tests were invaluable in helping to re-cast the application sql code into proper PostgreSQL syntax. We also used the results of these tests to update and enhance our unit tests going forward.<\/p>\n<p><strong><strong>\u00a0<\/strong><\/strong><\/p>\n<p>We cycled through this update, reconvert, retest pattern many times, incorporating changes to our scripts as needed. In the end, this process allowed for a repeatable process, proper documentation of schema changes, and a reliable set of data-consistency checks.<\/p>\n<p>&nbsp;<\/p>\n<p>&nbsp;<\/p>\n<h3><b>Migrating Data<\/b><\/h3>\n<p><strong><strong>\u00a0<\/strong><\/strong><\/p>\n<p>Only after fully validating the database structures and having the application code pass the necessary testing is it reasonable to consider migrating the production data. One of the key issues many migration scenarios miss is to disable any and all constraints in the target database. That is, make sure to start with a target database that has tables and no indices, unique constraints, or triggers. In general, migrators can not rely on the tables being populated in the proper order to comply with all foreign key constraints which will cause the migration to fail. \u00a0If update or insert triggers are not disabled, the migration process can quickly slow or fail as the triggers fire. It makes a huge difference when you turn these constraints off!<\/p>\n<p><strong><strong>\u00a0<\/strong><\/strong><\/p>\n<p>But, be sure to add these back, AFTER your data has been migrated to help validate the new data. It also helps to migrate using a few big transactions vice many small transactions. In our migration effort, the data-set was fairly small and so this was not a particular concern.<\/p>\n<p>&nbsp;<\/p>\n<h4 style=\"margin: 10px 20px;\"><b>There is also a setting in the postgresql.conf file you can use to log slow queries. It is suggested that this be disabled (set to 0) for migration purposes, as some big tables could flood the log as the data <a href=\"http:\/\/www.postgresql.org\/docs\/current\/static\/runtime-config-logging.html\">loads<\/a>. Again, this was not the case for our effort.<\/b><\/h4>\n<p>&nbsp;<\/p>\n<h3><b>Testing<\/b><\/h3>\n<p>Once the schema and data had been migrated and validated, a series of custom Perl and shell scripts were used to compare and contrast the corresponding MySQL and PostgreSQL tables (row by col). This worked well for all data types except some varchar columns holding json data. In these cases, manual review was required.<\/p>\n<p><strong><strong>\u00a0<\/strong><\/strong><\/p>\n<p>A secondary set of tests comprised a series of business reporting scripts that had been originally written for use with the MySQL instance along with a newly written set of scripts for use with the PostgreSQL instance. These scripts were executed in parallel on a MySQL production snapshot and the migrated PostgreSQL schema instance, respectively. Demonstrating that the reports were identical was critical to getting approval from the corporate business team. These types of tests also helped verify that all the ancillary scripts and tools used within the business processes had been properly converted into valid PostgreSQL.<\/p>\n<p><strong><strong>\u00a0<\/strong><\/strong><\/p>\n<p>The QA and Internal testing groups ran automated and manual tests against both application versions (MySQL and PostgreSQL) to verify proper operation.<\/p>\n<p><strong><strong>\u00a0<\/strong><\/strong><\/p>\n<p>And finally, we automated the entire process (bash and sql scripts) so that we could run and rerun the migration as needed to insure proper server configuration, properly plan for outages, and to have a reproducible process.<\/p>\n<p>&nbsp;<\/p>\n<p>&nbsp;<\/p>\n<h1>Production Migration<\/h1>\n<p>As expected, based on our process and testing, the migration from MySQL to PostgreSQL went smoothly. \u00a0However, the PostgreSQL system is significantly slower that the earlier MySQL one. To gain insight into the potential issues involved, work is underway with regards to these two issues:<\/p>\n<p><strong><strong>\u00a0<\/strong><\/strong><\/p>\n<p>The development team is working with the admin team reviewing the results of a series of <a href=\"http:\/\/www.postgresql.org\/docs\/9.1\/static\/sql-analyze.html\">ANALYZE queries<\/a> to see what insight can be gained into how to alter the database configuration or the server configuration.<\/p>\n<p><strong><strong>\u00a0<\/strong><\/strong><\/p>\n<p>Investigation\/Researching the use of standalone database poolers for Postgres, e.g., <a href=\"http:\/\/www.craigkerstiens.com\/2014\/05\/22\/on-connection-pooling\">PG Bouncer<\/a>.<\/p>\n<p>&nbsp;<\/p>\n<h1>\u2018PostgreSQL-isms\u2019<\/h1>\n<p>During the course of our work on the project, our team encountered expected and unexpected issues regarding SQL syntax and implementations. What follows below is a list of some of the more interesting scenarios\/solutions, as potential helpful reminders for me, and perhaps other developers.<\/p>\n<p>&nbsp;<\/p>\n<h3><b>CASE SENSITIVITY<\/b><\/h3>\n<p>PostgreSQL is case-sensitive. This applies to objects (table names, column names, etc) as well as the data in the tables. To demonstrate, the queries below will all return as expected from MySQL, but only #3 will return in PostgreSQL.<\/p>\n<p><strong><strong>\u00a0<\/strong><\/strong><\/p>\n<pre lang=\"LANGUAGE\" line=\"1\">insert into people (firstname) value ('Mike');\n<\/pre>\n<pre lang=\"LANGUAGE\" line=\"1\">select * from people where firstname='mike';\nselect * from people where firstname='MiKe';\n\nselect * from people where firstname='Mike';\n\nselect * from 'PEOPLE' where firstname='Mike';\n\nselect * from `PEOPLE` where firstname='Mike';\n<\/pre>\n<p>&nbsp;<\/p>\n<p>Case-sensitivity with respect to table and column names was also problematic as a number of migrated tables and columns which were defined in all caps. Rather than attempting to properly double-quote (with escaping) every sql statement, we created a set of sql queries (as a script) to generate the appropriate sql to lowercase all table names, column names and any constraints. The table data was not modified and, yes, this did require a number of unit and api tests to be rewritten to properly test for case-specific data.<\/p>\n<p>&nbsp;<\/p>\n<p>&nbsp;<\/p>\n<h3><b>ENUMS vs VARCHAR ARRAYS<\/b><\/h3>\n<p>In translating the enum MySQL data type, the py-mysql2pgsql \u00a0migration script re-created the MySQL enum values as part of a varchar array constraint for the column. PostgreSQL allows columns of a table to be defined as <a href=\"http:\/\/www.postgresql.org\/docs\/9.2\/static\/arrays.html\">variable-length multidimensional arrays<\/a>. However, unlike MySQL, PostgreSQL does not provide the first array element as the default entry for a non-null column. This was resolved, for our project, \u00a0by an explicit assignment within PostgreSQL using a series of update sql scripts.<\/p>\n<p>&nbsp;<\/p>\n<h3><b>BOOLEAN<\/b><\/h3>\n<p>MySQL uses 1 and 0 as representations of boolean True and False. PostgreSQL uses literal values for the true and false states (see below). This issue required a number of modifications to the application code and business reporting scripts to properly boolean data types.<\/p>\n<p><strong><strong>\u00a0<\/strong><\/strong><\/p>\n<h4 style=\"margin: 10px 20px;\"><b>Valid literal values for the &#8220;true&#8221; PostgreSQL state are:<br \/>\nTRUE, &#8216;t&#8217;, &#8216;true&#8217;, &#8216;y&#8217;, &#8216;yes&#8217;, &#8216;on&#8217;, &#8216;1&#8217;<\/b><\/h4>\n<h4 style=\"margin: 10px 20px;\"><b>Valid literal values for the &#8220;false&#8221; PostgreSQL state are:<br \/>\nFALSE, &#8216;f&#8217;, &#8216;false&#8217;, &#8216;n&#8217;, &#8216;no&#8217;, &#8216;off&#8217;, &#8216;0&#8217;<br \/>\n<\/b><\/h4>\n<p>&nbsp;<\/p>\n<h3><b>SEQUENCES<\/b><\/h3>\n<p>In MySQL it is a pretty simple to have the database auto-generate ID&#8217;s for new records. You simply set the AUTO_INCREMENT attribute on your column. PostgreSQL uses sequences to support this feature. Sequences are commonly implemented through the &#8216;serial&#8217; pseudo-type that provides for the following:<\/p>\n<p><strong><strong>\u00a0<\/strong><\/strong><\/p>\n<ul>\n<li>The values for the column will be generated by consulting the sequence<\/li>\n<li>Sets the default value for the column to be the next value produced by the sequence, e.g., calls nextval(sequence_name)<\/li>\n<li>Adds a NOT NULL constraint to the column<\/li>\n<li>Since the sequence that is auto-produced, PostgreSQL assumes that the sequence is only used to generate values for the table containing the serial column. Therefore, if this column is dropped, the sequence will be automatically removed.<\/li>\n<\/ul>\n<p><strong><strong>\u00a0<\/strong><\/strong><\/p>\n<p>Note that the pseudo-type does not implicitly create an index or mark the column as primary key!<\/p>\n<p>&nbsp;<\/p>\n<h4 style=\"margin: 10px 20px;\"><b>Here is an example of how to script the complete, correct definition for a table with a sequence \u00a0&#8211; note that this command will create a \u2018books_id_seq\u2019 for the id column and adds a unique constraint and an index on the column<\/b><\/h4>\n<pre lang=\"LANGUAGE\" line=\"1\">CREATE TABLE books (\n     id SERIAL PRIMARY KEY,\n     color TEXT,\n     size INT4 );\n<\/pre>\n<p>&nbsp;<\/p>\n<h3><b>MySQL if() vs PostgreSQL case()<\/b><\/h3>\n<p>The syntax of the MySQL IF function is IF(condition, return_if_true, return_if_false). In Postgres, these are rewritten as CASE statements<\/p>\n<p>&nbsp;<\/p>\n<p><b>MySQL<\/b><\/p>\n<pre lang=\"LANGUAGE\" line=\"1\">SELECT IF(score &gt; 100, 100, score) AS test_score FROM \u00a0tests;\n<\/pre>\n<p><b>PostgreSQL<\/b><\/p>\n<pre lang=\"LANGUAGE\" line=\"1\">SELECT CASE\n     WHEN score &gt;100 THEN 100\n     ELSE score\nEND AS test_score \nFROM tests;\n<\/pre>\n<p>&nbsp;<\/p>\n<h3><b>GROUP BY<\/b><\/h3>\n<p>In PostgreSQL, we must explicitly add each table column (but not aggregates) to the group by and order by lists, as depicted below. This is not required when using MySQL.<\/p>\n<p>&nbsp;<\/p>\n<p><b>Generic PostgreSQL Group By<\/b><\/p>\n<pre lang=\"LANGUAGE\" line=\"1\">SELECT column1, column2...columnN, sum(column1,column2)\nFROM table_nameWHERE conditions\nGROUP BY column1,....columnNORDER BY column1,....columnN;\n<\/pre>\n<p>&nbsp;<\/p>\n<h3><b>CONCAT \u00a0vs STRING_AGG<\/b><\/h3>\n<p>In PostgreSQL, the array_agg function returns an array of elements in the group. The function array_to_string is used (at least in our work) to concatenate the array into a string using the indicated character.<\/p>\n<p>&nbsp;<\/p>\n<p><b>MySQL Example<\/b><\/p>\n<pre lang=\"LANGUAGE\" line=\"1\">--Query\nselect owner, group_concat(dog_name SEPARATOR \u2018 | \u2019) from dogs group by owner;\n\n--Results\n--OWNER \u00a0\u00a0\u00a0\u00a0\u00a0 GROUP_CONCAT(NAME SEPARATOR '|')\n--mike      \u00a0 koda | xingu\n--tia \u00a0\u00a0\u00a0\u00a0\u00a0   fiona | grady\n<\/pre>\n<p><b>PostgreSQL Examples<\/b><\/p>\n<pre lang=\"LANGUAGE\" line=\"1\">--Query 1\nselect owner array_to_string(array_agg(dog_name),\u2019 | \u2018) from dogs group by owner;\n\n--Results 1\n--OWNER \u00a0\u00a0\u00a0\u00a0\u00a0 ARRAY_TO_STRING\n--mike \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 koda | xingu\n--tia \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 fiona | grady\n\n--Query 2\nSELECT breed_id, string_agg(dog, ', ') FROM breeds GROUP BY breed_id;\n\n--Results 2\n--breed_id \u00a0\u00a0\u00a0\u00a0dog\n--1 \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0Koda, Xingu\n--2 \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0Grady, Fiona\n<\/pre>\n<p>&nbsp;<\/p>\n<h3><b>ISNULL vs COALESCE<\/b><\/h3>\n<p>We used the PostgreSQL coalesce function to replace the standard MySQL IFNULL and ISNULL() functions. Coalesce is in the SQL standard, and it \u00a0is supported by MySQL. So it seemed a more flexible for supporting any future migrations as well.<\/p>\n<p>&nbsp;<\/p>\n<p><b>MySQL Example<\/b><\/p>\n<pre lang=\"LANGUAGE\" line=\"1\">SELECT ISNULL(Field,'Empty') from Table;\n<\/pre>\n<p><b>PostgreSQL Examples<\/b><\/p>\n<pre lang=\"LANGUAGE\" line=\"1\">SELECT CASE WHEN field IS NULL THEN 'Empty' ELSE field END AS field_alias\nSELECT coalesce(field, 'Empty') AS field_alias\n<\/pre>\n<p>&nbsp;<\/p>\n<h3><b>SQL_CALC_FOUND_ROWS and FOUND_ROWS()<\/b><\/h3>\n<p>All of our attempts to re-implement the ubiquitous MySQL SQL_CALC_FOUND_ROWS and FOUND_ROWS() \u00a0in a PostgreSQL system failed. Searching the literature, we did find one <a href=\"http:\/\/stackoverflow.com\/questions\/3984643\/equivalent-of-found-rows-function-in-postgresql\">post<\/a> that might be useful, to others, but did not adequately address our needs. So, in the end, we implemented the following, which ensures consistent results as it is done in a single transaction, but is slow in comparison.<\/p>\n<p>&nbsp;<\/p>\n<pre lang=\"LANGUAGE\" line=\"1\">BEGIN;\n     SELECT * FROM mytable OFFSET X LIMIT Y;\n     SELECT COUNT(*) AS total FROM mytable;\nEND;\n<\/pre>\n<p>&nbsp;<\/p>\n<p>&nbsp;<\/p>\n<h3><b>Handy Database Import &amp; Export Commands<\/b><\/h3>\n<p>Like MySQL, PostgreSQL provides a special command for exporting a full database instance, or any variation of that into a script file. The command is pg_dump and the two most common usages are:<\/p>\n<p><strong><strong>\u00a0<\/strong><\/strong><\/p>\n<ul>\n<li>Backup data and schema\n<ul>\n<li>pg_dump -U &lt;username&gt; &lt; db_name&gt; &gt; backup.sql<\/li>\n<\/ul>\n<\/li>\n<li>Backup schema only\n<ul>\n<li>pg_dump -U &lt;username&gt; &lt; db_name&gt; -s &gt; schema.sql<\/li>\n<\/ul>\n<\/li>\n<\/ul>\n<p><strong><strong>\u00a0<\/strong><\/strong><\/p>\n<p>To reload these dumps back into an <b>existing <\/b>postgres database<\/p>\n<ul>\n<li>psql -d &lt;existing_db_name&gt; \u00a0-f \u00a0&lt;pg_dumped_file&gt;<\/li>\n<\/ul>\n<p>&nbsp;<\/p>\n<p>&nbsp;<\/p>\n","protected":false},"excerpt":{"rendered":"<p>\u00a0 By: Mike B. &nbsp; &nbsp; &nbsp; In a recent customer engagement we were tasked with migrating an application from MySQL to PostgreSQL. At first glance, this would appear to be a relatively simple and straightforward process. However, there are significant differences in how PostgreSQL and MySQL implement the ANSI-SQL standard which necessitated a review [&hellip;]<\/p>\n","protected":false},"author":3,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[1],"tags":[],"class_list":["post-398","post","type-post","status-publish","format-standard","hentry","category-uncategorized"],"yoast_head":"<!-- This site is optimized with the Yoast SEO plugin v27.8 - https:\/\/yoast.com\/product\/yoast-seo-wordpress\/ -->\n<title>MySQL to PostgreSQL Migration - Solution Street Blog<\/title>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/www.solutionstreet.com\/blog\/2015\/03\/05\/mysql-postgresql-migration\/\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"MySQL to PostgreSQL Migration - Solution Street Blog\" \/>\n<meta property=\"og:description\" content=\"\u00a0 By: Mike B. &nbsp; &nbsp; &nbsp; In a recent customer engagement we were tasked with migrating an application from MySQL to PostgreSQL. At first glance, this would appear to be a relatively simple and straightforward process. However, there are significant differences in how PostgreSQL and MySQL implement the ANSI-SQL standard which necessitated a review [&hellip;]\" \/>\n<meta property=\"og:url\" content=\"https:\/\/www.solutionstreet.com\/blog\/2015\/03\/05\/mysql-postgresql-migration\/\" \/>\n<meta property=\"og:site_name\" content=\"Solution Street Blog\" \/>\n<meta property=\"article:published_time\" content=\"2015-03-05T13:34:58+00:00\" \/>\n<meta name=\"author\" content=\"afrankel\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<meta name=\"twitter:label1\" content=\"Written by\" \/>\n\t<meta name=\"twitter:data1\" content=\"afrankel\" \/>\n\t<meta name=\"twitter:label2\" content=\"Est. reading time\" \/>\n\t<meta name=\"twitter:data2\" content=\"15 minutes\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\\\/\\\/schema.org\",\"@graph\":[{\"@type\":\"Article\",\"@id\":\"https:\\\/\\\/www.solutionstreet.com\\\/blog\\\/2015\\\/03\\\/05\\\/mysql-postgresql-migration\\\/#article\",\"isPartOf\":{\"@id\":\"https:\\\/\\\/www.solutionstreet.com\\\/blog\\\/2015\\\/03\\\/05\\\/mysql-postgresql-migration\\\/\"},\"author\":{\"name\":\"afrankel\",\"@id\":\"https:\\\/\\\/www.solutionstreet.com\\\/blog\\\/#\\\/schema\\\/person\\\/4918a8dfd726e6176a3b516b9f1fd00f\"},\"headline\":\"MySQL to PostgreSQL Migration\",\"datePublished\":\"2015-03-05T13:34:58+00:00\",\"mainEntityOfPage\":{\"@id\":\"https:\\\/\\\/www.solutionstreet.com\\\/blog\\\/2015\\\/03\\\/05\\\/mysql-postgresql-migration\\\/\"},\"wordCount\":2480,\"commentCount\":0,\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"CommentAction\",\"name\":\"Comment\",\"target\":[\"https:\\\/\\\/www.solutionstreet.com\\\/blog\\\/2015\\\/03\\\/05\\\/mysql-postgresql-migration\\\/#respond\"]}]},{\"@type\":\"WebPage\",\"@id\":\"https:\\\/\\\/www.solutionstreet.com\\\/blog\\\/2015\\\/03\\\/05\\\/mysql-postgresql-migration\\\/\",\"url\":\"https:\\\/\\\/www.solutionstreet.com\\\/blog\\\/2015\\\/03\\\/05\\\/mysql-postgresql-migration\\\/\",\"name\":\"MySQL to PostgreSQL Migration - Solution Street Blog\",\"isPartOf\":{\"@id\":\"https:\\\/\\\/www.solutionstreet.com\\\/blog\\\/#website\"},\"datePublished\":\"2015-03-05T13:34:58+00:00\",\"author\":{\"@id\":\"https:\\\/\\\/www.solutionstreet.com\\\/blog\\\/#\\\/schema\\\/person\\\/4918a8dfd726e6176a3b516b9f1fd00f\"},\"breadcrumb\":{\"@id\":\"https:\\\/\\\/www.solutionstreet.com\\\/blog\\\/2015\\\/03\\\/05\\\/mysql-postgresql-migration\\\/#breadcrumb\"},\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\\\/\\\/www.solutionstreet.com\\\/blog\\\/2015\\\/03\\\/05\\\/mysql-postgresql-migration\\\/\"]}]},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\\\/\\\/www.solutionstreet.com\\\/blog\\\/2015\\\/03\\\/05\\\/mysql-postgresql-migration\\\/#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\\\/\\\/www.solutionstreet.com\\\/blog\\\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"MySQL to PostgreSQL Migration\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\\\/\\\/www.solutionstreet.com\\\/blog\\\/#website\",\"url\":\"https:\\\/\\\/www.solutionstreet.com\\\/blog\\\/\",\"name\":\"Solution Street Blog\",\"description\":\"Quality Software Engineering - Technology and Consulting Articles\",\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\\\/\\\/www.solutionstreet.com\\\/blog\\\/?s={search_term_string}\"},\"query-input\":{\"@type\":\"PropertyValueSpecification\",\"valueRequired\":true,\"valueName\":\"search_term_string\"}}],\"inLanguage\":\"en-US\"},{\"@type\":\"Person\",\"@id\":\"https:\\\/\\\/www.solutionstreet.com\\\/blog\\\/#\\\/schema\\\/person\\\/4918a8dfd726e6176a3b516b9f1fd00f\",\"name\":\"afrankel\",\"image\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\\\/\\\/secure.gravatar.com\\\/avatar\\\/906488c05ad8322b055f0d85b5cee1f6f966ce8715b51d59df3f0ced8ae01b6b?s=96&d=mm&r=g\",\"url\":\"https:\\\/\\\/secure.gravatar.com\\\/avatar\\\/906488c05ad8322b055f0d85b5cee1f6f966ce8715b51d59df3f0ced8ae01b6b?s=96&d=mm&r=g\",\"contentUrl\":\"https:\\\/\\\/secure.gravatar.com\\\/avatar\\\/906488c05ad8322b055f0d85b5cee1f6f966ce8715b51d59df3f0ced8ae01b6b?s=96&d=mm&r=g\",\"caption\":\"afrankel\"},\"url\":\"https:\\\/\\\/www.solutionstreet.com\\\/blog\\\/author\\\/afrankel\\\/\"}]}<\/script>\n<!-- \/ Yoast SEO plugin. -->","yoast_head_json":{"title":"MySQL to PostgreSQL Migration - Solution Street Blog","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/www.solutionstreet.com\/blog\/2015\/03\/05\/mysql-postgresql-migration\/","og_locale":"en_US","og_type":"article","og_title":"MySQL to PostgreSQL Migration - Solution Street Blog","og_description":"\u00a0 By: Mike B. &nbsp; &nbsp; &nbsp; In a recent customer engagement we were tasked with migrating an application from MySQL to PostgreSQL. At first glance, this would appear to be a relatively simple and straightforward process. However, there are significant differences in how PostgreSQL and MySQL implement the ANSI-SQL standard which necessitated a review [&hellip;]","og_url":"https:\/\/www.solutionstreet.com\/blog\/2015\/03\/05\/mysql-postgresql-migration\/","og_site_name":"Solution Street Blog","article_published_time":"2015-03-05T13:34:58+00:00","author":"afrankel","twitter_card":"summary_large_image","twitter_misc":{"Written by":"afrankel","Est. reading time":"15 minutes"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"Article","@id":"https:\/\/www.solutionstreet.com\/blog\/2015\/03\/05\/mysql-postgresql-migration\/#article","isPartOf":{"@id":"https:\/\/www.solutionstreet.com\/blog\/2015\/03\/05\/mysql-postgresql-migration\/"},"author":{"name":"afrankel","@id":"https:\/\/www.solutionstreet.com\/blog\/#\/schema\/person\/4918a8dfd726e6176a3b516b9f1fd00f"},"headline":"MySQL to PostgreSQL Migration","datePublished":"2015-03-05T13:34:58+00:00","mainEntityOfPage":{"@id":"https:\/\/www.solutionstreet.com\/blog\/2015\/03\/05\/mysql-postgresql-migration\/"},"wordCount":2480,"commentCount":0,"inLanguage":"en-US","potentialAction":[{"@type":"CommentAction","name":"Comment","target":["https:\/\/www.solutionstreet.com\/blog\/2015\/03\/05\/mysql-postgresql-migration\/#respond"]}]},{"@type":"WebPage","@id":"https:\/\/www.solutionstreet.com\/blog\/2015\/03\/05\/mysql-postgresql-migration\/","url":"https:\/\/www.solutionstreet.com\/blog\/2015\/03\/05\/mysql-postgresql-migration\/","name":"MySQL to PostgreSQL Migration - Solution Street Blog","isPartOf":{"@id":"https:\/\/www.solutionstreet.com\/blog\/#website"},"datePublished":"2015-03-05T13:34:58+00:00","author":{"@id":"https:\/\/www.solutionstreet.com\/blog\/#\/schema\/person\/4918a8dfd726e6176a3b516b9f1fd00f"},"breadcrumb":{"@id":"https:\/\/www.solutionstreet.com\/blog\/2015\/03\/05\/mysql-postgresql-migration\/#breadcrumb"},"inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["https:\/\/www.solutionstreet.com\/blog\/2015\/03\/05\/mysql-postgresql-migration\/"]}]},{"@type":"BreadcrumbList","@id":"https:\/\/www.solutionstreet.com\/blog\/2015\/03\/05\/mysql-postgresql-migration\/#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/www.solutionstreet.com\/blog\/"},{"@type":"ListItem","position":2,"name":"MySQL to PostgreSQL Migration"}]},{"@type":"WebSite","@id":"https:\/\/www.solutionstreet.com\/blog\/#website","url":"https:\/\/www.solutionstreet.com\/blog\/","name":"Solution Street Blog","description":"Quality Software Engineering - Technology and Consulting Articles","potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/www.solutionstreet.com\/blog\/?s={search_term_string}"},"query-input":{"@type":"PropertyValueSpecification","valueRequired":true,"valueName":"search_term_string"}}],"inLanguage":"en-US"},{"@type":"Person","@id":"https:\/\/www.solutionstreet.com\/blog\/#\/schema\/person\/4918a8dfd726e6176a3b516b9f1fd00f","name":"afrankel","image":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/secure.gravatar.com\/avatar\/906488c05ad8322b055f0d85b5cee1f6f966ce8715b51d59df3f0ced8ae01b6b?s=96&d=mm&r=g","url":"https:\/\/secure.gravatar.com\/avatar\/906488c05ad8322b055f0d85b5cee1f6f966ce8715b51d59df3f0ced8ae01b6b?s=96&d=mm&r=g","contentUrl":"https:\/\/secure.gravatar.com\/avatar\/906488c05ad8322b055f0d85b5cee1f6f966ce8715b51d59df3f0ced8ae01b6b?s=96&d=mm&r=g","caption":"afrankel"},"url":"https:\/\/www.solutionstreet.com\/blog\/author\/afrankel\/"}]}},"_links":{"self":[{"href":"https:\/\/www.solutionstreet.com\/blog\/wp-json\/wp\/v2\/posts\/398","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.solutionstreet.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.solutionstreet.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.solutionstreet.com\/blog\/wp-json\/wp\/v2\/users\/3"}],"replies":[{"embeddable":true,"href":"https:\/\/www.solutionstreet.com\/blog\/wp-json\/wp\/v2\/comments?post=398"}],"version-history":[{"count":0,"href":"https:\/\/www.solutionstreet.com\/blog\/wp-json\/wp\/v2\/posts\/398\/revisions"}],"wp:attachment":[{"href":"https:\/\/www.solutionstreet.com\/blog\/wp-json\/wp\/v2\/media?parent=398"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.solutionstreet.com\/blog\/wp-json\/wp\/v2\/categories?post=398"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.solutionstreet.com\/blog\/wp-json\/wp\/v2\/tags?post=398"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}