Creating Postgres table out of variable table data - sql

I have a Postgres table of data that looks like this (ex.):
╔════╦═══════════╦════════════╗
║ ID ║ FieldName ║ FieldValue ║
╠════╬═══════════╬════════════╣
║ 66 ║ PO# ║ 11111111 ║
║ 66 ║ Zip ║ 01810 ║
║ 66 ║ Badge ║ 22222222 ║
║ 67 ║ PO# ║ 7777777 ║
║ 67 ║ Zip ║ 02144 ║
║ 67 ║ Badge ║ 99999999 ║
╚════╩═══════════╩════════════╝
My question is how to transform this table into a new table that looks like this:
╔════╦══════════╦════════╦══════════╗
║ ID ║ Field1 ║ Field2 ║ Field3 ║
╠════╬══════════╬════════╬══════════╣
║ 66 ║ 11111111 ║ 01810 ║ 22222222 ║
║ 66 ║ 7777777 ║ 02144 ║ 99999999 ║
╚════╩══════════╩════════╩══════════╝
I need to do it entirely with Postgres syntax. Ideally I would be able to also dynamically figure out how many fields there were but that is a secondary need. For now, the assumption is that there would be three fields and I need to essentially transpose them into these new columns called Field#.

Use the crosstab() function from the tablefunc module. Detailed instruction and links here (read first!):
PostgreSQL Crosstab Query
Your query could look like:
SELECT *
FROM crosstab(
'SELECT "ID", "FieldName", "FieldValue"
FROM tbl
ORDER BY 1'
,$$VALUES ('PO#'::text), ('Zip'), ('Badge')$$
) AS ct ("Section" text, "Field1" int, "Field2" int, "Field3" int);
To dynamically figure out how many fields there were, you would have to build the above statement dynamically. SQL demands to know the return type. Much more details under this related question:
Dynamic alternative to pivot with CASE and GROUP BY

Related

Breaking SQL columns into their own rows in a SELECT statement

How would I go about doing this in SQL? I have a table that has two columns holding data that I need separate rows for. An example probably works best here.
This is the current structure of my table:
╔═══════════╦═════════╦═══════╦════════╗
║ CustID ║ Title ║ Plays ║ Shares ║
╠═══════════╬═════════╬═══════╬════════╣
║ Sony ║ Movie 1 ║ 123 ║ 224 ║
║ Sony ║ Movie 2 ║ 344 ║ 766 ║
║ Universal ║ Movie 3 ║ 334 ║ 866 ║
╚═══════════╩═════════╩═══════╩════════╝
What I need is to split the 'plays' and 'shares' events into separate rows in my select statement, like so:
╔═══════════╦═════════╦════════╦═══════╗
║ CustID ║ Title ║ Events ║ Type ║
╠═══════════╬═════════╬════════╬═══════╣
║ Sony ║ Movie 1 ║ 123 ║ play ║
║ Sony ║ Movie 1 ║ 224 ║ share ║
║ Sony ║ Movie 2 ║ 334 ║ play ║
║ Sony ║ Movie 2 ║ 766 ║ share ║
║ Universal ║ Movie 3 ║ 334 ║ play ║
║ Universal ║ Movie 3 ║ 866 ║ share ║
╚═══════════╩═════════╩════════╩═══════╝
I'm essentially doing a SQL pivot, but instead of taking multiple rows and pivoting them into a single one, I'm trying to go the other way. Is there a good way to do this? I'm using Postgres 9.x for what it's worth.
You can use a cross join to do the unpivot:
select t.cust_id, t.title, x.event, x.type
from test t
cross join lateral (values (plays, 'plays'), (shares, 'shares')) as x(event, type)
order by t.cust_id, t.title;
Online example
One approach, which should work on any database (including Postgres), would be to use a series of unions to unpivot your data:
SELECT CustID, Title, Plays AS Events, 'play' AS "Type" FROM yourTable
UNION ALL
SELECT CustID, Title, Shares, 'share' FROM yourTable
ORDER BY CustID, Title, Events;
Demo

PostgreSQL: Can COPY command be used to copy columns from multiple tables?

I want to copy values from columns in multiple tables to a csv file, not sure if posgres COPY command support copying column from multiple tables.
Here's a simplified example of the kind of thing I want to do using the tables below:
environment
╔════════╦═══════════╦══════╦══════╗
║ env_id ║ placem_id ║ humd ║ wind ║
╠════════╬═══════════╬══════╬══════╣
║ 104║ 4 ║ 48 ║ 119 ║
║ 68 ║ 9 ║ 39 ║ 141 ║
╚════════╩═══════════╩══════╩══════╝
placement
╔═══════════╦════════╦═══════════════╦══════════════════════════╗
║ placem_id ║ loc_id ║ description ║ date ║
╠═══════════╬════════╬═══════════════╬══════════════════════════╣
║ 4 ║ 64 ║ description_1 ║ 2019-03-12T20:40:35.967Z ║
║ 7 ║ 5 ║ description_2 ║ 2019-03-12T20:56:51.319Z ║
╚═══════════╩════════╩═══════════════╩══════════════════════════╝
location
╔════════╦═══════════╦═══════════╦════════════════════╗
║ loc_id ║ log ║ lat ║ address ║
╠════════╬═══════════╬═══════════╬════════════════════╣
║ 64 ║ 13.3986 ║ 52.5547 ║ Bosebrucke Einkauf ║
║ 71 ║ 21.150122 ║ -6.607044 ║ Charlotte Court ║
╚════════╩═══════════╩═══════════╩════════════════════╝
I want to select records from environment table and location table using the placem_id and loc_id in the placement table.
It is possible to copy these columns coming from more than one table using the COPY function syntax below:
\copy products TO '/tmp/products.csv' CSV DELIMITER ','
EDIT:
I'm interested in columns humd and wind from environment taable; then columns log lat and address from location table.
You could send a query instead of using the console :
Copy (Select humd, wind, log, lat, address From location natural join placement natural join environment) To '/tmp/test.csv' With CSV DELIMITER ',';

How to group by an expression in TSQL and capture the result?

How can I include the results of an expression in a GROUP BY clause and also select the output of the expression ?
Say I have this table:
╔════════════════════════╦═══════════╦═══════╗
║ Forest ║ Animal ║ Count ║
╠════════════════════════╬═══════════╬═══════╣
║ Tongass ║ Hyena ║ 600 ║
║ Tongass ║ Bear ║ 1200 ║
║ Mount Baker-Snoqualmie ║ Wolf ║ 30 ║
║ Mount Baker-Snoqualmie ║ Bunny ║ 2 ║
║ Ozark-St. Francis ║ Pigeon ║ 100 ║
║ Ozark-St. Francis ║ Ostrich ║ 1 ║
║ Bitterroot ║ Tarantula ║ 9001 ║
╚════════════════════════╩═══════════╩═══════╝
I need a row with the count of carnivores in each forest and a row for the count of non-carnivores (if there are any). This is the output I'm looking for in this example:
╔════════════════════════╦═══════════════╦═══════════════╗
║ Forest ║ AnimalsOfType ║ AreCarnivores ║
╠════════════════════════╬═══════════════╬═══════════════╣
║ Tongass ║ 1800 ║ 1 ║
║ Mount Baker-Snoqualmie ║ 2 ║ 0 ║
║ Mount Baker-Snoqualmie ║ 30 ║ 1 ║
║ Ozark-St. Francis ║ 101 ║ 0 ║
║ Bitterroot ║ 9001 ║ 1 ║
╚════════════════════════╩═══════════════╩═══════════════╝
The information for whether or not an animal is carnivorous is encoded in the expression.
What I'd like to do is include the expression in the group-by and reference its result in the select clause:
SELECT TOP (1000)
[Forest],
SUM([COUNT]) AS AnimalsOfType,
AreCarnivores
FROM [Tinker].[dbo].[ForestAnimals]
GROUP BY
Forest,
CASE WHEN ForestAnimals.Animal IN ('Pigeon', 'Ostrich', 'Bunny') THEN 0 ELSE 1 END AS AreCarnivores
However, this is not valid TSQL syntax.
If I include the Animal column in the GROUP BY clause to allow me to rerun the function in the SELECT, I'll get one row per animal type, which is not the desired behavior.
Doing separate selects into temp tables and unioning the results is undesirable because the real version of this query features a large number of expressions which need this behavior in the same result set, which would make for an extremely awkward stored procedure.
Use a CTE:
WITH X AS (
SELECT Forest, Animal, Count,
CASE WHEN ForestAnimals.Animal IN ('Pigeon', 'Ostrich', 'Bunny')
THEN 0
ELSE 1 END AS AreCarnivores
FROM [Tinker].[dbo].[ForestAnimals]
)
SELECT Forest, SUM(Count) AS AnimalsOfType, AreCarnivores
FROM X
Group by Forest, AreCarnivores;
Or be more verbose about it and repeat yourself:
SELECT Forest, SUM(Count) AS AnimalsOfType,
CASE WHEN ForestAnimals.Animal IN ('Pigeon', 'Ostrich', 'Bunny')
THEN 0
ELSE 1 END AS AreCarnivores
FROM [Tinker].[dbo].[ForestAnimals]
GROUP BY Forest, CASE WHEN ForestAnimals.Animal IN ('Pigeon', 'Ostrich', 'Bunny')
THEN 0
ELSE 1 END;
They're equivalent queries to the optimizer.

SQL Junction Query - How To Get Exact Match

I would like the answer to this question to be DBMS agnostic, but if it is relevant I am using Access SQL.
Please keep note that this is a simplified version of what I am trying to do.
Now, consider I have the following three tables.
My main fruits table(tblFruits):
╔═════════╦═══════════╦
║ fruitID ║ fruitName ║
╠═════════╬═══════════╬
║ 1 ║ Apple ║
║ 2 ║ Orange ║
║ 3 ║ Grapefruit║
╚═════════╩═══════════╩
A junction table to link many tags to 1 fruit(tblFruitTagJunc):
╔════════════════╦═════════╦═════════════╗
║ fruitTagJuncID ║ fruitID ║ tagID ║
╠════════════════╬═════════╬═════════════╣
║ 1 ║ 1 ║ 1 ║
║ 2 ║ 1 ║ 2 ║
║ 3 ║ 1 ║ 4 ║
║ 4 ║ 1 ║ 5 ║
║ 5 ║ 2 ║ 3 ║
║ 6 ║ 3 ║ 3 ║
║ 7 ║ 3 ║ 6 ║
╚════════════════╩═════════╩═════════════╝
And finally a tag table to tag my fruits(tblTag):
╔═════════╦═══════════╗
║ tagID ║ tag ║
╠═════════╬═══════════╣
║ 1 ║ Tasty ║
║ 2 ║ Red ║
║ 3 ║ Orange ║
║ 4 ║ Shiny ║
║ 5 ║ Delicious ║
║ 6 ║ Awful ║
╚═════════╩═══════════╝
Thanks to This Blog Post for letting me be lazy)
This essentially says that:
Apples are (Red, Shiny, Tasty, Delicious)
Oranges are (Orange)
Grapefruits are (Orange, Awful)
Now say that I want to select those fruits that have the tag 'Orange' and no others. With the data presented, that would be only the one with fruitName = 'Orange'. I am currently doing this:
SELECT F.fruitName
FROM tblFruits F
INNER JOIN tblFruitTagJunc AS FTJ on F.fruitID = FTJ.fruitID
INNER JOIN tbltag as T ON FTJ.tagID = T.tagID
WHERE T.tag in('Orange')
GROUP BY F.fruitName
HAVING count(T.tag) = 1
This would return both Orange AND Grapfruit in the result, but I only wanted Orange.
The reason I am doing the SQL statement this way is that different types of fruits may have the same name but different tags OR different fruits may have all but one of the same tags.
EDIT:
SQLFiddle as requested.
You are on the right track, but you need conditional aggregation in the having clause rather than a where clause. When you use where, you never see the other tags.
So:
SELECT F.fruitName
FROM tblFruits as F INNER JOIN
tblFruitTagJunc AS FTJ
on F.fruitID = FTJ.fruitID INNER JOIN
tbltag as T
ON FTJ.tagID = T.tagID
GROUP BY F.fruitName
HAVING SUM(iif(t.tag in ('Orange'), 1, 0) > 0 AND
COUNT(t.tag) = 1;
Note that the "right" way to express conditionality is using CASE rather than IIF(). Also Access usually requires lots of ugly parentheses around joins, which I am also leaving out.

Subquery multiple rows oracle

I have a table containing these values:
╔════╦═══════════════╗
║ ID ║ TYPE ║
╠════╬═══════════════╣
║ 1 ║ FUEL PUMP ║
║ 2 ║ FIRE ALARM ║
║ 3 ║ FIRE PUMP ║
║ 4 ║ SAFETY SHOWER ║
╚════╩═══════════════╝
(query is: SELECT DISTINCT TYPE FROM EQUIPMENT)
And a query which returns the following:
╔═════════════╦══════════════╗
║ Room ║ Equipment ID ║
╠═════════════╬══════════════╣
║ Locker Room ║ 1 ║
║ Hallway ║ 1 ║
║ Foyer ║ 2 ║
║ Office 1 ║ 3 ║
║ Office 2 ║ 2 ║
╚═════════════╩══════════════╝
I have tried to make the EQUIPMENT.TYPE field display by using a subquery within the SELECT and WHERE statements of the query which generates the above table. However, I am getting the:ORA-01427: single-row subquery returns more than one row 01427. 00000 - "single-row subquery returns more than one row". I asusme this is because the EQUIPMENT ID value is returned more than once.
Is it possible to do this with a join?
select room, type
from equipment join rooms
on rooms.equipment_id = equipment.id
Assuming the other table is called ROOMS.