Postgres convert values to columns - sql

I have postgres table with structure:
|key| position | date |
|---|----------|------------|
| 1 | 5 | 2017-07-01 |
|---|----------|------------|
| 1 | 9 | 2017-07-02 |
|---|----------|------------|
| 2 | 4 | 2017-07-01 |
|---|----------|------------|
| 2 | 8 | 2017-07-02 |
But I need to have the selected data in a format like this:
| key | 2017-07-01 | 2017-07-02 |
|-----|------------|------------|
| 1 | 5 | 9 |
|-----|------------|------------|
| 2 | 4 | 8 |
How can I do something like this?

If you have one row per key and per date, then one way is conditional aggregation
select
key,
min(case when date = '2017-07-01' then position end) as "2017-07-01",
min(case when date = '2017-07-02' then position end) as "2017-07-02"
from t
group by key

Related

SQL some selections into one (or get two colums from one)

I use PostgreSql, I have two tables (for example)
Let table1 will contain stores, there are 2 types 'candy store' and 'dental store'.
Each row contains information about a customer's purchase in a particular store
In result i want to get money from each type of store group by id and the last date of purchase. Money from candy stores start sum since 2016, but money from dental stores start sum from 2018
table1:
+----+---------+------------------+-------+
| id | store | date of purchase | money |
| 1 | store 1 | 2016-01-01 | 10 |
| 1 | store 5 | 2018-01-01 | 50 |
| 2 | store 2 | 2017-01-20 | 10 |
| 2 | store 3 | 2019-02-20 | 15 |
| 3 | store 2 | 2017-02-02 | 20 |
| 3 | store 6 | 2019-01-01 | 60 |
| 1 | store 1 | 2015-01-01 | 20 |
+----+---------+------------------+-------+
table2 :
+---------+--------+
| store | type |
| store 1 | candy |
| store 2 | candy |
| store 3 | candy |
| store 4 | dental |
| store 5 | dental |
| store 6 | dental |
+---------+--------+
I want my query to return a table like this:
+----+---------------+-----------------+---------------+-----------------+
| id | money( candy) | the last date c | money(dental) | the last date d |
| 1 | 10 | 2016-01-01 | 50 | 2018-01-01 |
| 2 | 25 | 2019-02-20 | - | - |
| 3 | 20 | 2017-02-02 | 60 | 2019-01-01 |
+----+---------------+-----------------+---------------+-----------------+
if I understand correctly , this is what you want to do :
select id
, sum(money) filter (where ty.type = 'candy') candymoney
, max(purchasedate) filter (where ty.type = 'candy') candylastdate
, sum(money) filter (where ty.type = 'dental') dentalmoney
, max(purchasedate) filter (where ty.type = 'dental') dentallastdate
from table t
join storetype table st on t.store = ty.store
group by id

output difference of two values same column to another column

Can anhone help me out or point me in the right direction? What is simplest way to get from current table to output table??
Current Table
ID | type | amount |
2 | A | 19 |
2 | B | 6 |
3 | A | 5 |
3 | B | 11 |
4 | A | 1 |
4 | B | 23 |
Desires output
ID | type | amount | change |
2 | A | 19 | 13 |
2 | B | 6 | -6 |
3 | A | 5 | -22 |
3 | B | 11 | |
4 | A | 1 | |
4 | B | 23 | |
I don't get how the values are put on rows. You can, for instance, subtract the "B" value from the "A" value for any given id. For instance:
select t.*,
(case when type = 'A'
then amount - max(amount) filter (type = 'B') over (partition by id)
end) as diff_a_b
from t;

SQL generate unique ID from rolling ID

I've been trying to find an answer to this for the better part of a day with no luck.
I have a SQL table with measurement data for samples and I need a way to assign a unique ID to each sample. Right now each sample has an ID number that rolls over frequently. What I need is a unique ID for each sample. Below is a table with a simplified dataset, as well as an example of a possible UID that would do what I need.
| Row | Time | Meas# | Sample# | UID (Desired) |
| 1 | 09:00 | 1 | 1 | 1 |
| 2 | 09:01 | 2 | 1 | 1 |
| 3 | 09:02 | 3 | 1 | 1 |
| 4 | 09:07 | 1 | 2 | 2 |
| 5 | 09:08 | 2 | 2 | 2 |
| 6 | 09:09 | 3 | 2 | 2 |
| 7 | 09:24 | 1 | 3 | 3 |
| 8 | 09:25 | 2 | 3 | 3 |
| 9 | 09:25 | 3 | 3 | 3 |
| 10 | 09:47 | 1 | 1 | 4 |
| 11 | 09:47 | 2 | 1 | 4 |
| 12 | 09:49 | 3 | 1 | 4 |
My problem is that rows 10-12 have the same Sample# as rows 1-3. I need a way to uniquely identify and group each sample. Having the row number or time of the first measurement on the sample would be good.
One other complication is that the measurement number doesn't always start with 1. It's based on measurement locations, and sometimes it skips location 1 and only has locations 2 and 3.
I am going to speculate that you want a unique number assigned to each sample, where now you have repeats.
If so, you can use lag() and a cumulative sum:
select t.*,
sum(case when prev_sample = sample then 0 else 1 end) over (order by row) as new_sample_number
from (select t.*,
lag(sample) over (order by row) as prev_sample
from t
) t;

Selecting latest consecutive records that match a condition with PostgreSQL

I am looking for a PostgreSQL query to find the latest consecutive records that match a condition. Let me explain it better with an example:
| ID | HEATING STATE | DATE |
| ---- | --------------- | ---------- |
| 1 | ON | 2018-02-19 |
| 2 | ON | 2018-02-20 |
| 3 | OFF | 2018-02-20 |
| 4 | OFF | 2018-02-21 |
| 5 | ON | 2018-02-21 |
| 6 | OFF | 2018-02-21 |
| 7 | ON | 2018-02-22 |
| 8 | ON | 2018-02-22 |
| 9 | ON | 2018-02-22 |
| 10 | ON | 2018-02-23 |
I need to find all the recent consecutive records with date >= 2018-02-20 and heating_state ON, i.e. the ones with ID 7, 8, 9, 10. My main issue is with the fact that they must be consecutive.
For further clarification, if needed:
ID 1 is excluded because older than 2018-02-20
ID 2 is excluded because followed by ID 3 which has heating state OFF
ID 3 is excluded because it has heating state OFF
ID 4 is excluded because it is followed by ID 5, which has heating OFF
ID 5 is excluded because it has heating state OFF
ID 6 is excluded because it has heating state OFF
I think this is best solved using windows functions and a filtered aggregate.
For each row, add the number of later rows that have state = 'OFF', then use only the rows where that count is 0.
You need a subquery because you cannot use a window function result in the WHERE condition (WHERE is evaluated before window functions).
SELECT id, state, date
FROM (SELECT id, state, date,
count(*) FILTER (WHERE state = 'OFF')
OVER (ORDER BY date DESC, state DESC) AS later_off_count
FROM tab) q
WHERE later_off_count = 0;
id | state | date
----+-------+------------
10 | ON | 2018-02-23
9 | ON | 2018-02-22
8 | ON | 2018-02-22
7 | ON | 2018-02-22
(4 rows)
Use the LEAD function with a CASE expression.
SQL Fiddle
Query 1:
SELECT id,
heating_state,
dt
FROM (SELECT t.*,
CASE
WHEN dt >= timestamp '2018-02-20'
AND heating_state = 'ON'
AND LEAD(heating_state, 1, heating_state)
OVER (
ORDER BY dt ) = 'ON' THEN 1
ELSE 0
END on_state
FROM t) s
WHERE on_state = 1
Results:
| id | heating_state | dt |
|----|---------------|----------------------|
| 7 | ON | 2018-02-22T00:00:00Z |
| 8 | ON | 2018-02-22T00:00:00Z |
| 9 | ON | 2018-02-22T00:00:00Z |
| 10 | ON | 2018-02-23T00:00:00Z |

Oracle SQL Count of a field Grouped by another field

I have a small query below which i want to have a Count of each HOUSE_ID and Grouped By LOCATION_ID however it is not grouping by LOCATION_ID because the HOUSE_ID's are different. I want it to count HOUSE_ID's by LOCATION_ID's regardless of the HOUSE_ID.
QUERY
SELECT
COUNT(HOUSE_ID) AS Count,
LOCATION_ID,
ZONE,
AREA
FROM TABLE
WHERE SITE_ID = 'ABC'
AND LOCATION_ID NOT LIKE ('%LAND%')
GROUP BY LOCATION_ID, HOUSE_ID, ZONE, AREA
Expected Result
_____________________________
|Count|LOCATION_ID|ZONE|AREA|
|¯¯¯¯¯|¯¯¯¯¯¯¯¯¯¯¯|¯¯¯¯|¯¯¯¯|
| 4 | LOCA | 2 | 1 |
| 7 | LOCB | 6 | 2 |
| 3 | LOCC | 3 | 1 |
| 9 | LOCD | 5 | 7 |
| 6 | LOCE | 7 | 4 |
| 2 | LOCF | 2 | 1 |
| 8 | LOCG | 7 | 5 |
| 7 | LOCH | 9 | 1 |
¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
Actual Result
_____________________________
|Count|LOCATION_ID|ZONE|AREA|
|¯¯¯¯¯|¯¯¯¯¯¯¯¯¯¯¯|¯¯¯¯|¯¯¯¯|
| 1 | LOCA | 2 | 1 |
| 1 | LOCA | 6 | 2 |
| 1 | LOCA | 3 | 1 |
| 1 | LOCA | 5 | 7 |
| 1 | LOCA | 7 | 4 |
| 1 | LOCA | 2 | 1 |
| 1 | LOCA | 7 | 5 |
| 1 | LOCA | 9 | 1 |
¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
You have to remove HOUSE_ID from group by clause.
Without source data I can only guess that you also need agregate functions for ZONE and AREA column, MAX fro example. Try below solution:
SELECT
COUNT(HOUSE_ID) AS Count,
LOCATION_ID,
MAX(ZONE),
MAX(AREA)
FROM TABLE
WHERE SITE_ID = 'ABC'
AND LOCATION_ID NOT LIKE ('%LAND%')
GROUP BY LOCATION_ID
Got it, Needed to Count(*)!!
SELECT
COUNT(*) AS Count,
SUM(AREA) AS AREA
LOCATION_ID,
ZONE,
FROM TABLE
WHERE SITE_ID = 'ABC'
AND LOCATION_ID NOT LIKE ('%LAND%')
GROUP BY LOCATION_ID, ZONE