I have the following query:
select 'junior' as type, value
from mytable
union
select 'intermediate' as type, value
from mytable
union
select 'senior' as type, value
from mytable
Which returns the following data:
type value
Intermediate 10
Junior 5
Senior 1
I just need to reorder it so it looks like this
Junior 5
Intermediate 10
Senior 1
I can't figure out which order by clause to use to achieve ordering by custom specific values, how would I achieve this?
You can either sort by adding a sort key column or add a simple case statement based on the values.
-- Sort with Case statement
with sourceData as
(
select 'junior' type, 5 value
union all
select 'intermediate' type, 10 value
union all
select 'senior' type, 1 value
)
select *
from sourceData
order by
case type
when 'junior' then 0
when 'intermediate' then 1
when 'senior' then 2
else null
end
SQL Fiddle for testing.
You need to add a third column, called e.g. sortorder. Then, assign the proper integer value to it,
select 'junior' as type, value, 1 as sortorder
from mytable
union
select 'intermediate' as type, value, 2 as sortorder
from mytable
union
select 'senior' as type, value, 3 as sortorder
from mytable
order by 3
I don't know if understood you well but I would extract one more column e.g. "sequence" with integer values. It will provide to the ability of applying custom order.
As an example:
select results.type, results.value, results.sequence
from (
select 'junior' as type, value, 0 as sequence
from mytable
union
select 'intermediate' as type, value, 1 as sequence
from mytable
union
select 'senior' as type, value, 2 as sequence
from mytable
) as results order by results.sequence
Try this one.
select TYPE, value from
(
select type,value , row_number() over ( order by type ) as rowno
from #mytable
union
select type, value, row_number() over ( order by type ) as rowno
from #mytable
union
select type,value, row_number() over ( order by type ) as rowno
from #mytable
)a order by 2 desc
Related
I have ant an Oracle v11 database, and whilst I do not have the schema definition of the tables, I have illustrated what I am trying to achieve below.
This is what the table looks like
I am trying to transform the data by selecting only the latest rows, the table keeps an history of changes, I am not interested in the changes only the latest value for every present issue
This is what I have so far.
select issueno,
case (when fieldname = 'name' then string_value end) name,
case (when fieldname = 'point' then string_value end) point
from issues
where issueno = 1234
The issue with the query above is that it returns 4 rows, I would like to return only a single row.
You can get the latest date by using LAST ORDER BY clause within the MAX() KEEP (..) values for transition_date(or load_date column, depending on which you mean replace within the query) such as
WITH i AS
(
SELECT CASE WHEN fieldname = 'name' THEN
MAX(string_value) KEEP (DENSE_RANK LAST ORDER BY transition_date)
OVER (PARTITION BY issue_no, fieldname)
END AS name,
CASE WHEN fieldname = 'point' THEN
MAX(string_value) KEEP (DENSE_RANK LAST ORDER BY transition_date)
OVER (PARTITION BY issue_no, fieldname)
END AS point
FROM issues
)
SELECT MAX(name) AS name, MAX(point) AS point
FROM i
But, if ties(equal values) occur for the related date values, then consider using DENSE_RANK() function in order to compute the values returning equal to 1 along with ROW_NUMBER() to be able to use with the JOIN clause in the main query such as
WITH i AS
(
SELECT i.*,
DENSE_RANK() OVER ( PARTITION BY issue_no, fieldname
ORDER BY transition_date DESC) AS dr,
ROW_NUMBER() OVER ( PARTITION BY issue_no, fieldname
ORDER BY transition_date DESC) AS rn
FROM issues i
)
SELECT i1.string_value AS name, i2.string_value AS point
FROM ( SELECT string_value, rn FROM i WHERE dr = 1 AND fieldname = 'name' ) i1
FULL JOIN ( SELECT string_value, rn FROM i WHERE dr = 1 AND fieldname = 'point' ) i2
ON i2.rn = i1.rn
Demo
Assuming that you want to have the latest record by the column load_date
select issueno,
case (when fieldname = 'name' then string_value end) name,
case (when fieldname = 'point' then string_value end) point
from issues
where issueno = 1234 and
(fieldname , load_date) in (select fieldname ,max(load_date) from issues where issueno=1234 group by fieldname)
I would use a subquery + window function to achieve what you asked for (assuming you use are basing load_date to determine the latest record)
select issueno,
case (when fieldname = 'name' then string_value end) name,
case (when fieldname = 'point' then string_value end) point
from
(
SELECT name, point, ROW_NUMBER() OVER(PARTITION BY ISSUENO, FIELDNAME ORDER BY LOAD_DATE DESC) RN
FROM issues
)
where issueno = 1234
AND RN = 1
The syntax ROW_NUMBER() OVER ([query_partition_clause] order_by_clause) is actually a window function that assign a ranking to each rows governed by how you declare the rule in [query_partition_clause] order_by_clause
See whether something like this helps; read comments within code.
SQL> with issues (issueno, fieldname, string_value,
2 transition_date, transition_id, load_date)
3 as
4 -- sample data; you have it in a table, don't type that
5 (select 1234, 'name', null , date '2021-01-01', 1, date '2021-01-02' from dual union all
6 select 1234, 'name', 'Tom', date '2021-02-11', 2, date '2021-02-12' from dual union all
7 select 1234, 'point', '0' , date '2021-02-04', 3, date '2021-02-05' from dual union all
8 select 1234, 'point', '5' , date '2021-02-10', 5, date '2021-02-11' from dual
9 ),
10 -- query you need begins here
11 temp as
12 -- rank values partitioned by ISSUENO and FIELDNAME, sorted by TRANSITION_ID
13 (select issueno, fieldname, string_value,
14 row_number() over (partition by issueno, fieldname
15 order by transition_id desc) rn
16 from issues
17 )
18 select issueno,
19 max(case when fieldname = 'name' then string_value end) name,
20 max(case when fieldname = 'point' then string_value end) point
21 from temp
22 where rn = 1
23 group by issueno;
ISSUENO NAME POINT
---------- ---------- ----------
1234 Tom 5
SQL>
How to find all column values are same in Group by of rows in table
CREATE TABLE #Temp (ID int,Value char(1))
insert into #Temp (ID ,Value ) ( Select 1 ,'A' union all Select 1 ,'W' union all Select 1 ,'I' union all Select 2 ,'I' union all Select 2 ,'I' union all Select 3 ,'A' union all Select 3 ,'B' union all Select 3 ,'1' )
select * from #Temp
Sample Table:
How to find all column value of 'Value' column are same or not if group by 'ID' Column.
Ex: select ID from #Temp group by ID
For ID 1 - Value column records are A, W, I - Not Same
For ID 2 - Value column records are I, I - Same
For ID 3 - Value column records are A, B, 1 - Not Same
I want the query to get a result like below
When all items in the group are the same, COUNT(DISTINCT Value) would be 1:
SELECT Id
, CASE WHEN COUNT(DISTINCT Value)=1 THEN 'Same' ELSE 'Not Same' END AS Result
FROM MyTable
GROUP BY Id
If you're using T-SQL, perhaps this will work for you:
SELECT t.ID,
CASE WHEN MAX(t.RN) > 1 THEN 'Same' ELSE 'Not Same' END AS GroupResults
FROM(
SELECT *, ROW_NUMBER() OVER(PARTITION BY ID, VALUE ORDER BY ID) RN
FROM #Temp
) t
GROUP BY t.ID
Usally that's rather easy: Aggregate per ID and count distinct values or compare minimum and maximum value.
However, neither COUNT(DISTINCT value) nor MIN(value) nor MAX(value) take nulls into consideration. So for an ID having value 'A' and null, these would detect uniqueness. Maybe this is what you want or nulls don't even occur in your data.
But if you want nulls to count as a value, then select distinct values first (where null gets a row too) and count then:
select id, case when count(*) = 1 then 'same' else 'not same' end as result
from (select distinct id, value from #temp) dist
group by id
order by id;
Rextester demo: http://rextester.com/KCZD88697
I have a unique requirement to return number of result rows in multiples of 10. Example, if actual data rows are 3, I must add another 7 blank rows to make it 10. If actual data rows are 16, I must add another 4 blank rows to make it 20, and so on.
Without using a procedure, is it possible to achieve this using SELECT statement?
The blank rows can simply contain NULL values or spaces or zeroes.
You can assume any simple query for data rows; the objective is to understand how to return rows dynamically in multiples of 10.
Example:
Select EmpName FROM Employees
If there are 3 employees, I should still return 10 rows, with the balance 7 rows containing either NULL value or blanks.
I am using SQL Server 2012.
This is very raw idea how it can be achieved:
WITH data(r) AS (
SELECT 1 r FROM dual
UNION ALL
SELECT r+1 r FROM data WHERE r < 10
)
SELECT sd.*
FROM data d
left join some_data sd on d.r = sd.id
This is dual table structure:
create table dual (dummy varchar(1));
insert into dual values ('x');
Fiddle: http://sqlfiddle.com/#!6/5ffcc/4
One of the possible options is this:
WITH data(r) AS (
SELECT 1 r FROM dual
UNION ALL
SELECT r+1 r FROM data WHERE r < 10
)
SELECT sd.*
FROM
(select r, row_number() over (order by r) rn from data) d
left join (
select id, name, row_number() over (order by id) rn from some_data sd
) sd
on d.rn = sd.rn
The obvious disadvantages of this colutions:
'r' value generation rule most probably is not as simple in your
case.
Number of rows must be known before query execution.
But maybe it will help you to find better solution.
Here's another, fairly easy, way to handle it...
IF OBJECT_ID('tempdb..#TestData', 'U') IS NOT NULL
DROP TABLE #TestData;
CREATE TABLE #TestData (
EmpID INT NOT NULL,
EmpName VARCHAR(20) NOT NULL
);
INSERT #TestData(EmpID, EmpName) VALUES
(47, 'Bob'),(33, 'Mary'), (88, 'Sue');
-- data as it exists...
SELECT
td.EmpID,
td.EmpName
FROM
#TestData td;
-- the desired output...
WITH
cte_AddRN AS (
SELECT
td.EmpID,
td.EmpName,
RN = ROW_NUMBER() OVER (ORDER BY td.EmpName)
FROM
#TestData td
),
cte_TenRows AS (
SELECT n.RN FROM ( VALUES (1),(2),(3),(4),(5),(6),(7),(8),(9),(10) ) n (RN)
)
SELECT
ar.EmpID,
ar.EmpName
FROM
cte_TenRows tr
LEFT JOIN cte_AddRN ar
ON tr.RN = ar.RN
ORDER BY
tr.RN;
Results...
-- data as it exists...
EmpID EmpName
----------- --------------------
47 Bob
33 Mary
88 Sue
-- the desired output...
EmpID EmpName
----------- --------------------
47 Bob
33 Mary
88 Sue
NULL NULL
NULL NULL
NULL NULL
NULL NULL
NULL NULL
NULL NULL
NULL NULL
Based on the above 2 answers, here is what I did:
WITH DATA AS
(SELECT EmpName FROM Employees),
DataSummary AS
(SELECT COUNT(*) AS NumDataRows FROM DATA),
ReqdDataRows AS
(SELECT CEILING(NumDataRows/10.0)*10 AS NumRowsReqd FROM DataSummary),
FillerRows AS
(
SELECT 1 AS SLNO, '00000' AS FillerCol
UNION ALL
SELECT 2 AS SLNO, '00000' AS FillerCol
UNION ALL
SELECT 3 AS SLNO, '00000' AS FillerCol
UNION ALL
SELECT 4 AS SLNO, '00000' AS FillerCol
UNION ALL
SELECT 5 AS SLNO, '00000' AS FillerCol
UNION ALL
SELECT 6 AS SLNO, '00000' AS FillerCol
UNION ALL
SELECT 7 AS SLNO, '00000' AS FillerCol
UNION ALL
SELECT 8 AS SLNO, '00000' AS FillerCol
UNION ALL
SELECT 9 AS SLNO, '00000' AS FillerCol
UNION ALL
SELECT 10 AS SLNO, '00000' AS FillerCol
)
SELECT * FROM DATA
--UNION ALL
--SELECT CONVERT(VARCHAR(10), NumDataRows) FROM DataSummary
--UNION ALL
--SELECT CONVERT(VARCHAR(10), NumRowsReqd) FROM ReqdDataRows
UNION ALL
SELECT FillerCol FROM FillerRows
WHERE (SELECT NumDataRows FROM DataSummary) + SLNO <= (SELECT NumRowsReqd FROM ReqdDataRows)
This gives me the output what I want. This avoids use of ROW_NUMBER and ORDERing. The table FillerRows can be further simplified using SELECT * FROM (VALUES...), and the 2nd and 3rd table DataSummary and ReqdDataRows can be merged into a single SELECT statement.
This approach is a step by step approach and easy to understand and debug, like:
Get the actual data rows
Get count of the data rows
Calculate required no. data rows
UNION the actual data rows with filler rows
Any suggestions on further simplifying this are welcome.
I am trying to extract the first not null value from a column of values based on timestamp. Can somebody share your thoughts on this. Thank you.
What have i tried so far?
FIRST_VALUE( column ) OVER ( PARTITION BY id ORDER BY timestamp)
Input :-
id,column,timestamp
1,NULL,10:30 am
1,NULL,10:31 am
1,'xyz',10:32 am
1,'def',10:33 am
2,NULL,11:30 am
2,'abc',11:31 am
Output(expected) :-
1,'xyz',10:30 am
1,'xyz',10:31 am
1,'xyz',10:32 am
1,'xyz',10:33 am
2,'abc',11:30 am
2,'abc',11:31 am
You can modify your sql like this to get the data you want.
FIRST_VALUE( column )
OVER (
PARTITION BY id
ORDER BY
CASE WHEN column IS NULL then 0 ELSE 1 END DESC,
timestamp
)
Try this old trick of string manipulation:
Select
ID,
Column,
ttimestamp,
LTRIM(Right(CColumn,20)) as CColumn,
FROM
(SELECT
ID,
Column,
ttimestamp,
MIN(Concat(RPAD(IF(Column is null, '9999999999999999',STRING(ttimestamp)),20,'0'),LPAD(Column,20,' '))) OVER (Partition by ID) CColumn
FROM (
SELECT
*
FROM (Select 1 as ID, STRING(NULL) as Column, 0.4375 as ttimestamp),
(Select 1 as ID, STRING(NULL) as Column, 0.438194444444444 as ttimestamp),
(Select 1 as ID, 'xyz' as Column, 0.438888888888889 as ttimestamp),
(Select 1 as ID, 'def' as Column, 0.439583333333333 as ttimestamp),
(Select 2 as ID, STRING(NULL) as Column, 0.479166666666667 as ttimestamp),
(Select 2 as ID, 'abc' as Column, 0.479861111111111 as ttimestamp)
))
As far as I know, Big Query has no options like 'IGNORE NULLS' or 'NULLS LAST'. Given that, this is the simplest solution I could come up with. I would like to see even simpler solutions.
Assuming the input data is in table "original_data",
select w2.id, w1.column, w2.timestamp
from
(select id,column,timestamp
from
(select id,column,timestamp, row_number()
over (partition BY id ORDER BY timestamp) position
FROM original_data
where column is not null
)
where position=1
) w1
right outer join
original_data as w2
on w1.id = w2.id
SELECT id,
(SELECT top(1) column FROM test1 where id=1 and column is not null order by autoID desc) as name
,timestamp
FROM yourTable
Output :-
1,'xyz',10:30 am
1,'xyz',10:31 am
1,'xyz',10:32 am
1,'xyz',10:33 am
2,'abc',11:30 am
2,'abc',11:31 am
I have a master table with a number of IDs in it:
ID ...
0 ...
1 ...
And multiple tables (say vtbl1, vtbl2, vtbl3) with a foreign key to master, a timestamp and a value:
ID Timestamp Value
0 01/01/01.. 2
1 01/01/02.. 7
0 01/01/03.. 5
I would like to get one or more entries for each ID in master with an entry (or null if no entries exist) containing the most recent entry in each v... table (grouped by timestamps):
ID Timestamp vtbl1.Value vtbl2.Value vtbl3.value
0 01/01/03.. 5 2
0 01/01/01.. 4
1 01/01/02.. 7 4 9
I'm sure this is fairly simple but my SQL is rusty and I've been going in circles. Any help would be appreciated.
Clarification
These values come from one or more sensors able to read one or more of the values. So the latest value in each value table for the ID is to be considered the current system state for that ID. If the timestamps match they are considered one update.
I need the minimal set of updates required for each ID to give a full data set for the current state.
Also the values can be of different types.
If I understand your question correctly, one option is to use conditional aggregation and union all:
select id, timestamp,
max(case when tbl = 'tbl1' then value end) t1value,
max(case when tbl = 'tbl2' then value end) t2value,
max(case when tbl = 'tbl3' then value end) t3value
from (
select id, timestamp, value, 'tbl1' tbl
from tbl1
union all
select id, timestamp, value, 'tbl2' tbl
from tbl2
union all
select id, timestamp, value, 'tbl3' tbl
from tbl3
) t
group by id, timestamp
Or if you have multiple records per id and you want the highest value per by timestamp, you can include row_number() in your subquery:
select id, timestamp,
max(case when tbl = 'tbl1' then value end) t1value,
max(case when tbl = 'tbl2' then value end) t2value,
max(case when tbl = 'tbl3' then value end) t3value
from (
select id, timestamp, value, 'tbl1' tbl,
row_number() over (partition by id order by timestamp desc) rn
from tbl1
union all
select id, timestamp, value, 'tbl2' tbl,
row_number() over (partition by id order by timestamp desc) rn
from tbl2
union all
select id, timestamp, value, 'tbl3' tbl,
row_number() over (partition by id order by timestamp desc) rn
from tbl3
) t
where rn = 1
group by id, timestamp
This can get difficult though if max(timestamp) values aren't the same in each of the child tables. Which do you join on at that point?
select m.*, v1.value as t1_val, v2.value as t2_val, v3.value as t3_val
from master m
left join (select x.*
from vtbl1 x
join (select id, max(timestamp) as last_ts
from vtbl1
group by id) y
on x.id = y.id
and x.timestamp = y.last_ts) v1
on m.id = v1.id
left join (select x.*
from vtbl2 x
join (select id, max(timestamp) as last_ts
from vtbl2
group by id) y
on x.id = y.id
and x.timestamp = y.last_ts) v2
on m.id = v2.id
left join (select x.*
from vtbl3 x
join (select id, max(timestamp) as last_ts
from vtbl3
group by id) y
on x.id = y.id
and x.timestamp = y.last_ts) v3
on m.id = v3.id
The fastest query technique depends on the distribution of values. DISTINCT ON would be a simple solution in Postgres, ideal for just a few values per id in each child table. But guessing from your description I expect many rows per id, so I suggest a solution with LATERAL joins. Requires Postgres 9.3+:
Optimize GROUP BY query to retrieve latest record per user
One more complication for your already-not-so-simple case:
Also the values can be of different types
Alternative 1
Cast all values to text. Every data type can be cast to text.
Base query
SELECT m.id, v.timestamp, 1 AS tbl, v.value -- simple int as table id
FROM master m
, LATERAL (
SELECT timestamp, value::text -- cast to text
FROM vtbl1
WHERE id = m.id -- lateral reference
ORDER BY timestamp DESC NULLS LAST
LIMIT 1
) v
UNION ALL
SELECT m.id, v.timestamp, 2 AS tbl, v.value -- ascending without gaps
FROM master m
, LATERAL (
SELECT timestamp, value::text
FROM vtbl2
WHERE id = m.id
ORDER BY timestamp DESC NULLS LAST
LIMIT 1
) v
UNION ALL
SELECT m.id, v.timestamp, 3 AS tbl, value
FROM ...
;
All you need for this to be fast is an index on (id, timestamp) for each child table. Best in this form (adding value is only useful if you get index-only scans out of it):
CREATE INDEX vtbl1_combo_idx ON vtbl1 (id, timestamp DESC NULLS LAST, value)
1a. Aggregate (pseudo-crosstab)
To format as desired use aggregate functions on CASE expressions in Postgres 9.3 or older (like demonstrated by #sgeddes) or (better) the new aggregate FILTER clause in Postgres 9.4+:
How can I simplify this game statistics query?
SELECT id, timestamp
, max(value) FILTER (WHERE tbl = 1) AS val1
, max(value) FILTER (WHERE tbl = 2) AS val2
, ...
FROM ( <query frm above> ) t
GROUP BY 1, 2;
1b. Crosstab
Actual cross tabulation (also called "pivot" in other RDBMS) should be considerably faster. You need the additional module tablefunc installed, instructions below.
The special difficulty here: we have a composite "row name" (id, timestamp), but the function expects a single column as row name. So we substitute with row_number(), but do not display that surrogate key in the result:
SELECT id, timestamp, val1, val2, val3, ...
-- normally SELECT * is enough; explicit list to filter rn
FROM crosstab(
$$
SELECT row_number() OVER (ORDER BY id, timestamp DESC NULLS LAST) AS rn
, id, timestamp, tbl, value
FROM ( <query from above> ) t
ORDER BY 1
$$
, 'SELECT generate_series(1,3)' -- replace 3 with highest table nr.
) AS ct (
rn int, id int, timestamp date
, val1 text, val2 text, val3 text, ...);
Closely related:
Postgres - Transpose Rows to Columns
Relevant basics:
PostgreSQL Crosstab Query
Pivot on Multiple Columns using Tablefunc
Alternative 2
Simple, but may be just as fast and preserves original data types:
SELECT id, timestamp
, max(val1) AS val1, max(val2) AS val2, max(val3) AS val3, ...
FROM (
SELECT m.id, v.timestamp
, v.value AS val1, NULL::int AS val2, NULL::numeric AS val3, ...
-- list all values with actual data type
FROM master m
, LATERAL (
SELECT timestamp, value
FROM vtbl1
WHERE id = m.id
ORDER BY timestamp DESC NULLS LAST
LIMIT 1
) v
UNION ALL
SELECT m.id, v.timestamp
, NULL, v.value, NULL, ... -- column names & data types defined in first SELECT
FROM master m
, LATERAL (
SELECT timestamp, value
FROM vtbl2
WHERE id = m.id
ORDER BY timestamp DESC NULLS LAST
LIMIT 1
) v
UNION ALL
SELECT m.id, v.timestamp
, NULL, NULL, v.value, ...
FROM ...
) t
GROUP BY 1, 2
ORDER BY 1, 2;
Aside: Never use basic type names or reserved words (in standard SQL) like timestamp as identifier.