diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index e4f72a8..2a998b2 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -2449,6 +2449,95 @@ lo_import 152801
         </listitem>
       </varlistentry>
 
+      <varlistentry>
+        <term><literal>\crosstabview [ [-|+]<replaceable class="parameter">colV</replaceable>  [-|+]<replaceable class="parameter">colH</replaceable> ] </literal></term>
+        <listitem>
+        <para>
+        Execute the current query buffer (like <literal>\g</literal>) and shows the results
+        inside a crosstab grid. The output column <replaceable class="parameter">colV</replaceable>
+        becomes a vertical header and the output column
+        <replaceable class="parameter">colH</replaceable> becomes a horizontal header.
+        The results for the other output columns are projected inside the grid.
+        </para>
+
+        <para>
+        <replaceable class="parameter">colV</replaceable>
+        and <replaceable class="parameter">colH</replaceable> can indicate a
+        column position (starting at 1), or a column name. Normal case folding
+        and quoting rules apply on column names. By default,
+        <replaceable class="parameter">colV</replaceable> is column 1
+        and <replaceable class="parameter">colH</replaceable> is column 2.
+        A query having only one output column cannot be viewed in crosstab, and
+        <replaceable class="parameter">colH</replaceable> must differ from
+        <replaceable class="parameter">colV</replaceable>.
+        </para>
+
+        <para>
+        The vertical header, displayed as the leftmost column,
+        contains the set of all distinct values found in
+        column <replaceable class="parameter">colV</replaceable>, in the order
+        of their first appearance in the query results.
+        </para>
+        <para>
+        The horizontal header, displayed as the first row,
+        contains the set of all distinct non-null values found in
+        column <replaceable class="parameter">colH</replaceable>.  They come
+        by default in their order of appearance in the query results, or in ascending
+        order if a plus (+) sign precedes <replaceable class="parameter">colH</replaceable>,
+        or in descending order if it's a minus (-) sign.
+        </para>
+
+        <para>
+        The query results being tuples of <literal>N</literal> columns
+        (including <replaceable class="parameter">colV</replaceable> and
+        <replaceable class="parameter">colH</replaceable>),
+        for each distinct value <literal>x</literal> of
+        <replaceable class="parameter">colH</replaceable>
+        and each distinct value <literal>y</literal> of
+        <replaceable class="parameter">colV</replaceable>,
+        a cell located at the intersection <literal>(x,y)</literal> in the grid
+        has contents determined by these rules:
+        <itemizedlist>
+        <listitem>
+        <para>
+         if there is no corresponding row in the results such that the value
+         for <replaceable class="parameter">colH</replaceable>
+         is <literal>x</literal> and the value
+         for <replaceable class="parameter">colV</replaceable>
+         is <literal>y</literal>, the cell is empty.
+        </para>
+        </listitem>
+
+        <listitem>
+        <para>
+         if there is exactly one row such that the value
+         for <replaceable class="parameter">colH</replaceable>
+         is <literal>x</literal> and the value
+         for <replaceable class="parameter">colV</replaceable>
+         is <literal>y</literal>, then the <literal>N-2</literal> other
+         columns are displayed in the cell, separated between each other by
+         a space character if needed.
+
+         If <literal>N=2</literal>, the letter <literal>X</literal> is displayed in the cell as
+         if a virtual third column contained that character.
+        </para>
+        </listitem>
+
+        <listitem>
+        <para>
+         if there are several corresponding rows, the behavior is identical to one row
+         except that the values coming from different rows are stacked
+         vertically, rows being separated by newline characters inside
+         the same cell.
+        </para>
+        </listitem>
+
+        </itemizedlist>
+        </para>
+
+        </listitem>
+      </varlistentry>
+
 
       <varlistentry>
         <term><literal>\s [ <replaceable class="parameter">filename</replaceable> ]</literal></term>
diff --git a/src/bin/psql/Makefile b/src/bin/psql/Makefile
index f1336d5..9cb0c4a 100644
--- a/src/bin/psql/Makefile
+++ b/src/bin/psql/Makefile
@@ -23,7 +23,7 @@ override CPPFLAGS := -I. -I$(srcdir) -I$(libpq_srcdir) -I$(top_srcdir)/src/bin/p
 OBJS=	command.o common.o help.o input.o stringutils.o mainloop.o copy.o \
 	startup.o prompt.o variables.o large_obj.o print.o describe.o \
 	tab-complete.o mbprint.o dumputils.o keywords.o kwlookup.o \
-	sql_help.o \
+	sql_help.o crosstabview.o \
 	$(WIN32RES)
 
 
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index 6e8c623..6ba5efa 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -46,6 +46,7 @@
 #include "mainloop.h"
 #include "print.h"
 #include "psqlscan.h"
+#include "crosstabview.h"
 #include "settings.h"
 #include "variables.h"
 
@@ -1081,6 +1082,34 @@ exec_command(const char *cmd,
 		free(pw2);
 	}
 
+	/* \crosstabview -- execute a query and display results in crosstab */
+	else if (strcmp(cmd, "crosstabview") == 0)
+	{
+		char	*opt1,
+				*opt2;
+
+		opt1 = psql_scan_slash_option(scan_state,
+									  OT_NORMAL, NULL, false);
+		opt2 = psql_scan_slash_option(scan_state,
+									  OT_NORMAL, NULL, false);
+
+		if (opt1 && !opt2)
+		{
+			psql_error(_("\\%s: missing second argument\n"), cmd);
+			success = false;
+		}
+		else
+		{
+			pset.crosstabview_col_V = opt1 ? pg_strdup(opt1): NULL;
+			pset.crosstabview_col_H = opt2 ? pg_strdup(opt2): NULL;
+			pset.crosstabview_output = true;
+			status = PSQL_CMD_SEND;
+		}
+
+		free(opt1);
+		free(opt2);
+	}
+
 	/* \prompt -- prompt and set variable */
 	else if (strcmp(cmd, "prompt") == 0)
 	{
diff --git a/src/bin/psql/common.c b/src/bin/psql/common.c
index a287eee..d8c0f70 100644
--- a/src/bin/psql/common.c
+++ b/src/bin/psql/common.c
@@ -24,6 +24,7 @@
 #include "command.h"
 #include "copy.h"
 #include "mbprint.h"
+#include "crosstabview.h"
 
 
 static bool ExecQueryUsingCursor(const char *query, double *elapsed_msec);
@@ -906,6 +907,8 @@ PrintQueryResults(PGresult *results)
 			/* store or print the data ... */
 			if (pset.gset_prefix)
 				success = StoreQueryTuple(results);
+			else if (pset.crosstabview_output)
+				success = PrintResultsInCrossTab(results);
 			else
 				success = PrintQueryTuples(results);
 			/* if it's INSERT/UPDATE/DELETE RETURNING, also print status */
@@ -1076,7 +1079,9 @@ SendQuery(const char *query)
 
 		/* but printing results isn't: */
 		if (OK && results)
+		{
 			OK = PrintQueryResults(results);
+		}
 	}
 	else
 	{
@@ -1192,6 +1197,18 @@ sendquery_cleanup:
 		pset.gset_prefix = NULL;
 	}
 
+	/* reset \crosstabview settings */
+	pset.crosstabview_output = false;
+	if (pset.crosstabview_col_V)
+	{
+		free(pset.crosstabview_col_V);
+		pset.crosstabview_col_V = NULL;
+	}
+	if (pset.crosstabview_col_H)
+	{
+		free(pset.crosstabview_col_H);
+		pset.crosstabview_col_H = NULL;
+	}
 	return OK;
 }
 
@@ -1354,7 +1371,25 @@ ExecQueryUsingCursor(const char *query, double *elapsed_msec)
 			is_pager = true;
 		}
 
-		printQuery(results, &my_popt, fout, is_pager, pset.logfile);
+		if (pset.crosstabview_output)
+		{
+			if (ntuples < fetch_count)
+				PrintResultsInCrossTab(results);
+			else
+			{
+				/*
+				  crosstabview is denied if the whole set of rows is not
+				  guaranteed to be fetched in the first iteration, because
+				  it's expected in memory as a single PGresult structure.
+				*/
+				psql_error("\\crosstabview must be used with less than FETCH_COUNT (%d) rows\n",
+					fetch_count);
+				PQclear(results);
+				break;
+			}
+		}
+		else
+			printQuery(results, &my_popt, fout, is_pager, pset.logfile);
 
 		PQclear(results);
 
diff --git a/src/bin/psql/crosstabview.c b/src/bin/psql/crosstabview.c
new file mode 100644
index 0000000..6d13b57
--- /dev/null
+++ b/src/bin/psql/crosstabview.c
@@ -0,0 +1,644 @@
+/*
+ * psql - the PostgreSQL interactive terminal
+ *
+ * Copyright (c) 2000-2015, PostgreSQL Global Development Group
+ *
+ * src/bin/psql/crosstabview.c
+ */
+
+#include "common.h"
+#include "pqexpbuffer.h"
+#include "crosstabview.h"
+#include "settings.h"
+
+#include <string.h>
+
+static int
+headerCompare(const void *a, const void *b)
+{
+	return strcmp( ((struct pivot_field*)a)->name,
+				   ((struct pivot_field*)b)->name);
+}
+
+static void
+accumHeader(char* name, int* count, struct pivot_field **sorted_tab, int row_number)
+{
+	struct pivot_field *p;
+
+	/*
+	 * Search for name in sorted_tab. If it doesn't exist, insert it,
+	 * otherwise do nothing.
+	 */
+
+	if (*count >= 1)
+	{
+		p = (struct pivot_field*) bsearch(&name,
+										  *sorted_tab,
+										  *count,
+										  sizeof(struct pivot_field),
+										  headerCompare);
+	}
+	else
+		p=NULL;
+
+	if (!p)
+	{
+		*sorted_tab = pg_realloc(*sorted_tab, sizeof(struct pivot_field) * (1+*count));
+		(*sorted_tab)[*count].name = name;
+		(*sorted_tab)[*count].rank = *count;
+		(*count)++;
+
+		qsort(*sorted_tab,
+			  *count,
+			  sizeof(struct pivot_field),
+			  headerCompare);
+	}
+}
+
+/*
+ * Send a query to sort all column values cast to the Oid passed in a VALUES clause
+ */
+static bool
+sortColumns(Oid coltype, struct pivot_field *columns, int nb_cols, int direction)
+{
+	bool retval = false;
+	PGresult *res = NULL;
+	PQExpBufferData query;
+	int i;
+	Oid *param_types;
+	const char** param_values;
+	int* param_lengths;
+	int* param_formats;
+
+	if (nb_cols < 2 || direction==0)
+		return true;					/* nothing to sort */
+
+	param_types = (Oid*) pg_malloc(nb_cols*sizeof(Oid));
+	param_values = (const char**) pg_malloc(nb_cols*sizeof(char*));
+	param_lengths = (int*) pg_malloc(nb_cols*sizeof(int));
+	param_formats = (int*) pg_malloc(nb_cols*sizeof(int));
+
+	initPQExpBuffer(&query);
+
+	/*
+	 * The query returns the original position of each value in our list,
+	 * ordered by its new position. The value itself is not returned.
+	 */
+	appendPQExpBuffer(&query, "SELECT n FROM (VALUES");
+
+	for (i=1; i <= nb_cols; i++)
+	{
+		if (i < nb_cols)
+			appendPQExpBuffer(&query, "($%d,%d),", i, i);
+		else
+		{
+			appendPQExpBuffer(&query, "($%d,%d)) AS l(x,n) ORDER BY x", i, i);
+			if (direction < 0)
+				appendPQExpBuffer(&query, " DESC");
+		}
+
+		param_types[i-1] = coltype;
+		param_values[i-1] = columns[i-1].name;
+		param_lengths[i-1] = strlen(columns[i-1].name);
+		param_formats[i-1] = 0;
+	}
+
+	res = PQexecParams(pset.db,
+					   query.data,
+					   nb_cols,
+					   param_types,
+					   param_values,
+					   param_lengths,
+					   param_formats,
+					   0);
+
+	if (res)
+	{
+		ExecStatusType status = PQresultStatus(res);
+		if (status == PGRES_TUPLES_OK)
+		{
+			for (i=0; i < PQntuples(res); i++)
+			{
+				int old_pos = atoi(PQgetvalue(res, i, 0));
+
+				if (old_pos < 1 || old_pos > nb_cols || i >= nb_cols)
+				{
+					/*
+					 * A position outside of the range is normally impossible.
+					 * If this happens, we're facing a malfunctioning or hostile
+					 * server or middleware.
+					 */
+					psql_error(_("Unexpected value when sorting horizontal headers"));
+					goto cleanup;
+				}
+				else
+				{
+					columns[old_pos-1].rank = i;
+				}
+			}
+		}
+		else
+		{
+			psql_error(_("Query error when sorting horizontal headers: %s"),
+					   PQerrorMessage(pset.db));
+			goto cleanup;
+		}
+	}
+
+	retval = true;
+
+cleanup:
+	termPQExpBuffer(&query);
+	if (res)
+		PQclear(res);
+	pg_free(param_types);
+	pg_free(param_values);
+	pg_free(param_lengths);
+	pg_free(param_formats);
+	return retval;
+}
+
+static void
+printCrosstab(const PGresult *results,
+			  int num_columns,
+			  struct pivot_field *piv_columns,
+			  int field_for_columns,
+			  int num_rows,
+			  struct pivot_field *piv_rows,
+			  int field_for_rows)
+{
+	printQueryOpt popt = pset.popt;
+	printTableContent cont;
+	int	i, j, rn;
+	char col_align = 'l';		/* alignment for values inside the grid */
+	int* horiz_map;			 	/* map indices from sorted horizontal headers to piv_columns */
+	char** allocated_cells; 	/*  Pointers for cell contents that are allocated
+								 *  in this function, when cells cannot simply point to
+								 *  PQgetvalue(results, ...) */
+
+	printTableInit(&cont, &popt.topt, popt.title, num_columns+1, num_rows);
+
+	/* Step 1: set target column names (horizontal header) */
+
+	/* The name of the first column is kept unchanged by the pivoting */
+	printTableAddHeader(&cont,
+						PQfname(results, field_for_rows),
+						false,
+						column_type_alignment(PQftype(results, field_for_rows)));
+
+	/*
+	 * To iterate over piv_columns[] by piv_columns[].rank, create a reverse map
+	 *  associating each piv_columns[].rank to its index in piv_columns.
+	 *  This avoids an O(N^2) loop later
+	 */
+	horiz_map = (int*) pg_malloc(sizeof(int) * num_columns);
+	for (i = 0; i < num_columns; i++)
+	{
+		horiz_map[piv_columns[i].rank] = i;
+	}
+
+	/*
+	 * In the case of 3 output columns, the contents in the cells are exactly
+	 * the contents of the "value" column (3rd column by default), so their
+	 * alignment is determined by PQftype(). Otherwise the contents are
+	 * made-up strings, so the alignment is 'l'
+	 */
+	if (PQnfields(results) == 3)
+	{
+		int colnum;				/* column placed inside the grid */
+		/*
+		 * find colnum in the permutations of (0,1,2) where colnum is
+		 * neither field_for_rows nor field_for_columns
+		 */
+		switch (field_for_rows)
+		{
+		case 0:
+			colnum = (field_for_columns == 1) ? 2 : 1;
+			break;
+		case 1:
+			colnum = (field_for_columns == 0) ? 2: 0;
+			break;
+		default:				/* should be always 2 */
+			colnum = (field_for_columns == 0) ? 1: 0;
+			break;
+		}
+		col_align = column_type_alignment(PQftype(results, colnum));
+	}
+	else
+		col_align = 'l';
+
+	for (i = 0; i < num_columns; i++)
+	{
+		printTableAddHeader(&cont,
+							piv_columns[horiz_map[i]].name,
+							false,
+							col_align);
+	}
+	pg_free(horiz_map);
+
+	/* Step 2: set row names in the first output column (vertical header) */
+	for (i = 0; i < num_rows; i++)
+	{
+		int k = piv_rows[i].rank;
+		cont.cells[k*(num_columns+1)] = piv_rows[i].name;
+		/* Initialize all cells inside the grid to an empty value */
+		for (j = 0; j < num_columns; j++)
+			cont.cells[k*(num_columns+1)+j+1] = "";
+	}
+	cont.cellsadded = num_rows * (num_columns+1);
+
+	allocated_cells = (char**) pg_malloc0(num_rows * num_columns * sizeof(char*));
+
+	/* Step 3: set all the cells "inside the grid" */
+	for (rn = 0; rn < PQntuples(results); rn++)
+	{
+		char* row_name;
+		char* col_name;
+		int row_number;
+		int col_number;
+		struct pivot_field *p;
+
+		row_number = col_number = -1;
+		/* Find target row */
+		if (!PQgetisnull(results, rn, field_for_rows))
+		{
+			row_name = PQgetvalue(results, rn, field_for_rows);
+			p = (struct pivot_field*) bsearch(&row_name,
+											  piv_rows,
+											  num_rows,
+											  sizeof(struct pivot_field),
+											  headerCompare);
+			if (p)
+				row_number = p->rank;
+		}
+
+		/* Find target column */
+		if (!PQgetisnull(results, rn, field_for_columns))
+		{
+			col_name = PQgetvalue(results, rn, field_for_columns);
+			p = (struct pivot_field*) bsearch(&col_name,
+											  piv_columns,
+											  num_columns,
+											  sizeof(struct pivot_field),
+											  headerCompare);
+			if (p)
+				col_number = p->rank;
+		}
+
+		/* Place value into cell */
+		if (col_number>=0 && row_number>=0)
+		{
+			int idx = 1 + col_number + row_number*(num_columns+1);
+			int src_col = 0;			/* column number in source result */
+			int k = 0;
+
+			do {
+				char *content;
+
+				if (PQnfields(results) == 2)
+				{
+					/*
+					  special case: when the source has only 2 columns, use a
+					  X (cross/checkmark) for the cell content, and set
+					  src_col to a virtual additional column.
+					*/
+					content = "X";
+					src_col = 3;
+				}
+				else if (src_col == field_for_rows || src_col == field_for_columns)
+				{
+					/*
+					  The source values that produce headers are not processed
+					  in this loop, only the values that end up inside the grid.
+					*/
+					src_col++;
+					continue;
+				}
+				else
+				{
+					content = (!PQgetisnull(results, rn, src_col)) ?
+						PQgetvalue(results, rn, src_col) :
+						(popt.nullPrint ? popt.nullPrint : "");
+				}
+
+				if (cont.cells[idx] != NULL && cont.cells[idx][0] != '\0')
+				{
+					/*
+					 * Multiple values for the same (row,col) are projected
+					 * into the same cell. When this happens, separate the
+					 * previous content of the cell from the new value by a
+					 * newline.
+					 */
+					int content_size =
+						strlen(cont.cells[idx])
+						+ 2 			/* room for [CR],LF or space */
+						+ strlen(content)
+						+ 1;			/* '\0' */
+					char *new_content;
+
+					/*
+					 * idx2 is an index into allocated_cells. It differs from
+					 * idx (index into cont.cells), because vertical and
+					 * horizontal headers are included in `cont.cells` but
+					 * excluded from allocated_cells.
+					 */
+					int idx2 = (row_number * num_columns) + col_number;
+
+					if (allocated_cells[idx2] != NULL)
+					{
+						new_content = pg_realloc(allocated_cells[idx2], content_size);
+					}
+					else
+					{
+						/*
+						 * At this point, cont.cells[idx] still contains a
+						 * PQgetvalue() pointer.  Just after, it will contain
+						 * a new pointer maintained in allocated_cells[], and
+						 * freed at the end of this function.
+						 */
+						new_content = pg_malloc(content_size);
+						strcpy(new_content, cont.cells[idx]);
+					}
+					cont.cells[idx] = new_content;
+					allocated_cells[idx2] = new_content;
+
+					/*
+					 * Contents that are on adjacent columns in the source results get
+					 * separated by one space in the target.
+					 * Contents that are on different rows in the source get
+					 * separated by newlines in the target.
+					 */
+					if (k==0)
+						strcat(new_content, "\n");
+					else
+						strcat(new_content, " ");
+					strcat(new_content, content);
+				}
+				else
+				{
+					cont.cells[idx] = content;
+				}
+				k++;
+				src_col++;
+			} while (src_col < PQnfields(results));
+		}
+	}
+
+	printTable(&cont, pset.queryFout, false, pset.logfile);
+	printTableCleanup(&cont);
+
+
+	for (i=0; i < num_rows * num_columns; i++)
+	{
+		if (allocated_cells[i] != NULL)
+			pg_free(allocated_cells[i]);
+	}
+
+	pg_free(allocated_cells);
+}
+
+/*
+ * Compare a user-supplied argument against a field name obtained by PQfname(),
+ * which is already case-folded.
+ * If arg is not enclosed in double quotes, pg_strcasecmp applies, otherwise
+ * do a case-sensitive comparison with these rules:
+ * - double quotes enclosing 'arg' are filtered out
+ * - double quotes inside 'arg' are expected to be doubled
+ */
+static int
+fieldnameCmp(const char* arg, const char* fieldname)
+{
+	const unsigned char* p = (const unsigned char*) arg;
+	const unsigned char* f = (const unsigned char*) fieldname;
+	unsigned char c;
+
+	if (*p++ != '"')
+		return pg_strcasecmp(arg, fieldname);
+
+	while ((c=*p++))
+	{
+		if (c=='"')
+		{
+			if (*p=='"')
+				p++;			/* skip second quote and continue */
+			else if (*p=='\0')
+				return *p-*f;		/* p finishes before f or is identical */
+
+		}
+		if (*f=='\0')
+			return 1;			/* f finishes before p */
+		if (c!=*f)
+			return c-*f;
+		f++;
+	}
+	return (*f=='\0') ? 0 : 1;
+}
+
+
+/*
+ * arg can be a number or a column name, possibly quoted (like in an ORDER BY clause)
+ * Returns:
+ *  on success, the 0-based index of the column
+ *  or -1 if the column number or name is not found in the result's structure,
+ *        or if it's ambiguous (arg corresponding to several columns)
+ */
+static int
+indexOfColumn(const char* arg, PGresult* res)
+{
+	int idx;
+
+	if (strspn(arg, "0123456789") == strlen(arg))
+	{
+		/* if arg contains only digits, it's a column number */
+		idx = atoi(arg) - 1;
+		if (idx < 0  || idx >= PQnfields(res))
+		{
+			psql_error(_("Invalid column number: %s\n"), arg);
+			return -1;
+		}
+	}
+	else
+	{
+		int i;
+		idx = -1;
+		for (i=0; i < PQnfields(res); i++)
+		{
+			if (fieldnameCmp(arg, PQfname(res, i)) == 0)
+			{
+				if (idx>=0)
+				{
+					/* if another idx was already found for the same name */
+					psql_error(_("Ambiguous column name: %s\n"), arg);
+					return -1;
+				}
+				idx = i;
+			}
+		}
+		if (idx == -1)
+		{
+			psql_error(_("Invalid column name: %s\n"), arg);
+			return -1;
+		}
+	}
+	return idx;
+}
+
+bool
+PrintResultsInCrossTab(PGresult* res)
+{
+	/* [-|+]COLV or null */
+	const char* opt_field_for_rows = pset.crosstabview_col_V;
+	/* [-|+]COLH or null */
+	const char* opt_field_for_columns = pset.crosstabview_col_H;
+	int		rn;
+	struct pivot_field	*piv_columns = NULL;
+	struct pivot_field	*piv_rows = NULL;
+	int		num_columns = 0;
+	int		num_rows = 0;
+	bool 	retval = false;
+	int		columns_sort_direction = 0; /* 1:ascending, 0:none, -1:descending */
+	int		rows_sort_direction = 0; /* 1:ascending, 0:none, -1:descending */
+
+	/* 0-based index of the field whose distinct values will become COLUMN headers */
+	int		field_for_columns;
+
+	/* 0-based index of the field whose distinct values will become ROW headers */
+	int		field_for_rows;
+
+	if (PQresultStatus(res) != PGRES_TUPLES_OK)
+		goto error_return;
+
+	if (PQnfields(res) < 2)
+	{
+		psql_error(_("The query must return at least two columns to be shown in crosstab\n"));
+		goto error_return;
+	}
+
+	/* field for rows in crosstab */
+	if (opt_field_for_rows == NULL)
+		field_for_rows = 0;
+	else
+	{
+		if (*opt_field_for_rows == '-')
+		{
+			rows_sort_direction = -1;
+			opt_field_for_rows++;
+		}
+		else if (*opt_field_for_rows == '+')
+		{
+			rows_sort_direction = 1;
+			opt_field_for_rows++;
+		}
+		field_for_rows = indexOfColumn(opt_field_for_rows, res);
+		if (field_for_rows < 0)
+			goto error_return;
+	}
+
+	if (field_for_rows < 0)
+		goto error_return;
+
+	/* field for columns in crosstab */
+	if (opt_field_for_columns == NULL)
+		field_for_columns = 1;
+	else
+	{
+		/*
+		 * descending sort is requested if the column reference is
+		 * preceded with a minus sign
+		 */
+		if (*opt_field_for_columns == '-')
+		{
+			columns_sort_direction = -1;
+			opt_field_for_columns++;
+		}
+		else if (*opt_field_for_columns == '+')
+		{
+			columns_sort_direction = 1;
+			opt_field_for_columns++;
+		}
+		field_for_columns = indexOfColumn(opt_field_for_columns, res);
+		if (field_for_columns < 0)
+			goto error_return;
+	}
+
+
+	if (field_for_columns == field_for_rows)
+	{
+		psql_error(_("The same column cannot be used for both vertical and horizontal headers\n"));
+		goto error_return;
+	}
+
+	/*
+	 * First part: accumulate row names and column names, each into their
+	 * array. Use client-side sort but only to build the set of DISTINCT
+	 * values. The final order displayed depends only on server-side
+	 * sorts.
+	 */
+	for (rn = 0; rn < PQntuples(res); rn++)
+	{
+		if (!PQgetisnull(res, rn, field_for_rows))
+		{
+			accumHeader(PQgetvalue(res, rn, field_for_rows),
+						&num_rows,
+						&piv_rows,
+						rn);
+		}
+
+		if (!PQgetisnull(res, rn, field_for_columns))
+		{
+			accumHeader(PQgetvalue(res, rn, field_for_columns),
+						&num_columns,
+						&piv_columns,
+						rn);
+			if (num_columns > 1600)
+			{
+				psql_error(_("Maximum number of columns (1600) exceeded\n"));
+				goto error_return;
+			}
+		}
+	}
+
+	/*
+	 * Second part: sort the list of target columns on the server.
+	 */
+	if (columns_sort_direction != 0)
+	{
+		if (!sortColumns(PQftype(res, field_for_columns),
+						 piv_columns,
+						 num_columns,
+						 columns_sort_direction))
+			goto error_return;
+	}
+
+
+	/*
+	 * Third part: sort the list of target columns on the server.
+	 */
+	if (rows_sort_direction != 0)
+	{
+		if (!sortColumns(PQftype(res, field_for_rows),
+						 piv_rows,
+						 num_rows,
+						 rows_sort_direction))
+			goto error_return;
+	}
+
+	/*
+	 * Fourth part: print the crosstab'ed results.
+	 */
+	printCrosstab(res,
+				  num_columns,
+				  piv_columns,
+				  field_for_columns,
+				  num_rows,
+				  piv_rows,
+				  field_for_rows);
+
+	retval = true;
+
+error_return:
+	pg_free(piv_columns);
+	pg_free(piv_rows);
+
+	return retval;
+}
diff --git a/src/bin/psql/crosstabview.h b/src/bin/psql/crosstabview.h
new file mode 100644
index 0000000..e8a0e01
--- /dev/null
+++ b/src/bin/psql/crosstabview.h
@@ -0,0 +1,30 @@
+/*
+ * psql - the PostgreSQL interactive terminal
+ *
+ * Copyright (c) 2000-2015, PostgreSQL Global Development Group
+ *
+ * src/bin/psql/crosstabview.h
+ */
+
+#ifndef CROSSTABVIEW_H
+#define CROSSTABVIEW_H
+
+struct pivot_field
+{
+	/* Pointer obtained from PGgetvalue() for colV or colH */
+	char*	name;
+
+	/* Rank of the field in its list, starting at 0.
+	 * - For headers stacked vertically, rank=N means it's the
+	 *   Nth distinct field encountered when looping through rows
+	 *   in their initial order.
+	 * - For headers stacked horizontally, rank is obtained
+	 *   by server-side sorting in sortColumns()
+	 */
+	int		rank;
+};
+
+/* prototypes */
+extern bool
+PrintResultsInCrossTab(PGresult* res);
+#endif   /* CROSSTABVIEW_H */
diff --git a/src/bin/psql/help.c b/src/bin/psql/help.c
index 5b63e76..c38d51d 100644
--- a/src/bin/psql/help.c
+++ b/src/bin/psql/help.c
@@ -175,6 +175,7 @@ slashUsage(unsigned short int pager)
 	fprintf(output, _("  \\g [FILE] or ;         execute query (and send results to file or |pipe)\n"));
 	fprintf(output, _("  \\gset [PREFIX]         execute query and store results in psql variables\n"));
 	fprintf(output, _("  \\q                     quit psql\n"));
+	fprintf(output, _("  \\crosstabview [V H]    execute query and display results in crosstab\n"));
 	fprintf(output, _("  \\watch [SEC]           execute query every SEC seconds\n"));
 	fprintf(output, "\n");
 
diff --git a/src/bin/psql/print.c b/src/bin/psql/print.c
index 05d4b31..b2f8c2b 100644
--- a/src/bin/psql/print.c
+++ b/src/bin/psql/print.c
@@ -3291,30 +3291,9 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
 
 	for (i = 0; i < cont.ncolumns; i++)
 	{
-		char		align;
-		Oid			ftype = PQftype(result, i);
-
-		switch (ftype)
-		{
-			case INT2OID:
-			case INT4OID:
-			case INT8OID:
-			case FLOAT4OID:
-			case FLOAT8OID:
-			case NUMERICOID:
-			case OIDOID:
-			case XIDOID:
-			case CIDOID:
-			case CASHOID:
-				align = 'r';
-				break;
-			default:
-				align = 'l';
-				break;
-		}
-
 		printTableAddHeader(&cont, PQfname(result, i),
-							opt->translate_header, align);
+							opt->translate_header,
+							column_type_alignment(PQftype(result, i)));
 	}
 
 	/* set cells */
@@ -3356,6 +3335,31 @@ printQuery(const PGresult *result, const printQueryOpt *opt,
 	printTableCleanup(&cont);
 }
 
+char
+column_type_alignment(Oid ftype)
+{
+	char		align;
+
+	switch (ftype)
+	{
+		case INT2OID:
+		case INT4OID:
+		case INT8OID:
+		case FLOAT4OID:
+		case FLOAT8OID:
+		case NUMERICOID:
+		case OIDOID:
+		case XIDOID:
+		case CIDOID:
+		case CASHOID:
+			align = 'r';
+			break;
+		default:
+			align = 'l';
+			break;
+	}
+	return align;
+}
 
 void
 setDecimalLocale(void)
diff --git a/src/bin/psql/print.h b/src/bin/psql/print.h
index fd565984..218b185 100644
--- a/src/bin/psql/print.h
+++ b/src/bin/psql/print.h
@@ -175,7 +175,7 @@ extern FILE *PageOutput(int lines, const printTableOpt *topt);
 extern void ClosePager(FILE *pagerpipe);
 
 extern void html_escaped_print(const char *in, FILE *fout);
-
+extern char column_type_alignment(Oid);
 extern void printTableInit(printTableContent *const content,
 			   const printTableOpt *opt, const char *title,
 			   const int ncolumns, const int nrows);
diff --git a/src/bin/psql/settings.h b/src/bin/psql/settings.h
index 1885bb1..6069024 100644
--- a/src/bin/psql/settings.h
+++ b/src/bin/psql/settings.h
@@ -90,6 +90,9 @@ typedef struct _psqlSettings
 
 	char	   *gfname;			/* one-shot file output argument for \g */
 	char	   *gset_prefix;	/* one-shot prefix argument for \gset */
+	bool		crosstabview_output;  /* one-shot request to print results in crosstab */
+	char		*crosstabview_col_V;  /* one-shot \crosstabview 1st argument */
+	char		*crosstabview_col_H;  /* one-shot \crosstabview 2nd argument */
 
 	bool		notty;			/* stdin or stdout is not a tty (as determined
 								 * on startup) */
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index b58ec14..07ba26c 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -935,7 +935,8 @@ psql_completion(const char *text, int start, int end)
 	};
 
 	static const char *const backslash_commands[] = {
-		"\\a", "\\connect", "\\conninfo", "\\C", "\\cd", "\\copy", "\\copyright",
+		"\\a", "\\connect", "\\conninfo", "\\C", "\\cd", "\\copy",
+		"\\copyright", "\\crosstabview",
 		"\\d", "\\da", "\\db", "\\dc", "\\dC", "\\dd", "\\ddp", "\\dD",
 		"\\des", "\\det", "\\deu", "\\dew", "\\dE", "\\df",
 		"\\dF", "\\dFd", "\\dFp", "\\dFt", "\\dg", "\\di", "\\dl", "\\dL",
