SQL query to select union of properties - sql

There is a table:
ID INDEX PROPERTY VALUE
-----------------------------
1 1 p1 v1
2 1 p3 v3
3 2 p2 v2
4 2 p3 v3a
5 3 p1 v1a
6 3 p2 v2a
7 3 p3 v3b
I need to select union of all PROPERTY where INDEX=1 or INDEX=2 (INDEX=3 is out of intereset). At the same time VALUE of PROPERTY should be selected from INDEX=2 if it exists otherwise - from INDEX=1 i.e. I expect 3 properties in result set: p1=v1, p2=v2, p3=v3a
How to compose SQL query (SQL Server and Oracle) for such task without using full outer join?

Adding to the suggested answer to select union of all PROPERTY you can write as:
;with cte as
(
select row_number() over ( partition by PROPERTY order by [INDEX] desc)
as rownum,
PROPERTY,VALUE
from Test
where [INDEX] in (1,2)
)
SELECT top 1 STUFF(
(SELECT ',' + cte1.PROPERTY + '=' + cte1.VALUE
FROM cte AS cte1
WHERE cte1.rownum = cte.rownum
FOR XML PATH('')), 1, 1, '') AS PROPERTY
FROM cte
where rownum = 1
DEMO

I see. You want the result set to have one row for each Property, only from indexes 1 and 2, with preference given to 2 when there are duplicates.
You can do this using window functions:
select t.*
from (select t.*,
row_number() over (partition by property order by index desc) as seqnum
from table t
where index in (1, 2)
) t
where seqnum = 1;
You can also do this using union all:
select *
from table t
where index = 2
union all
select *
from table t
where index = 1 and
not exists (select 1 from table t2 where t2.property = t.property and t2.index = 2);
By the way, index is a lousy name for a column because it is a reserved word.

Related

Select first missing id above 0

I have in my columns (ID) values
5
6
9
I want to select first missing IDfrom above 0. My desire select value will be 1.(if 1 exists then it will selects 2 and so on...).
I'm using this code:
SELECT MIN(id) As MinMissingId FROM table1 where id>=0
But my result is first existing ID and not missing
This will return the next unused id starting with 1, works in all cases, e.g. table is empty or there's no gap:
WITH cte AS
(
SELECT id FROM tab
UNION ALL
SELECT 0
)
SELECT MIN(id) + 1
FROM cte
WHERE NOT EXISTS
(
SELECT *
FROM tab
WHERE tab.id = cte.id + 1
)
You can do this by building on the answer in your related question.
select (case when min(id) > 0 then 1 else min(id) + 1 end)
from table1 t
where not exists (select 1
from table1 t2
where t2.id = t.id + 1
);
The idea is to find the first id that is missing. If it is bigger

concatenate recursive cross join

I need to concatenate the name in a recursive cross join way. I don't know how to do this, I have tried a CTE using WITH RECURSIVE but no success.
I have a table like this:
group_id | name
---------------
13 | A
13 | B
19 | C
19 | D
31 | E
31 | F
31 | G
Desired output:
combinations
------------
ACE
ACF
ACG
ADE
ADF
ADG
BCE
BCF
BCG
BDE
BDF
BDG
Of course, the results should multiply if I add a 4th (or more) group.
Native Postgresql Syntax:
SqlFiddleDemo
WITH RECURSIVE cte1 AS
(
SELECT *, DENSE_RANK() OVER (ORDER BY group_id) AS rn
FROM mytable
),cte2 AS
(
SELECT
CAST(name AS VARCHAR(4000)) AS name,
rn
FROM cte1
WHERE rn = 1
UNION ALL
SELECT
CAST(CONCAT(c2.name,c1.name) AS VARCHAR(4000)) AS name
,c1.rn
FROM cte1 c1
JOIN cte2 c2
ON c1.rn = c2.rn + 1
)
SELECT name as combinations
FROM cte2
WHERE LENGTH(name) = (SELECT MAX(rn) FROM cte1)
ORDER BY name;
Before:
I hope if you don't mind that I use SQL Server Syntax:
Sample:
CREATE TABLE #mytable(
ID INTEGER NOT NULL
,TYPE VARCHAR(MAX) NOT NULL
);
INSERT INTO #mytable(ID,TYPE) VALUES (13,'A');
INSERT INTO #mytable(ID,TYPE) VALUES (13,'B');
INSERT INTO #mytable(ID,TYPE) VALUES (19,'C');
INSERT INTO #mytable(ID,TYPE) VALUES (19,'D');
INSERT INTO #mytable(ID,TYPE) VALUES (31,'E');
INSERT INTO #mytable(ID,TYPE) VALUES (31,'F');
INSERT INTO #mytable(ID,TYPE) VALUES (31,'G');
Main query:
WITH cte1 AS
(
SELECT *, rn = DENSE_RANK() OVER (ORDER BY ID)
FROM #mytable
),cte2 AS
(
SELECT
TYPE = CAST(TYPE AS VARCHAR(MAX)),
rn
FROM cte1
WHERE rn = 1
UNION ALL
SELECT
[Type] = CAST(CONCAT(c2.TYPE,c1.TYPE) AS VARCHAR(MAX))
,c1.rn
FROM cte1 c1
JOIN cte2 c2
ON c1.rn = c2.rn + 1
)
SELECT *
FROM cte2
WHERE LEN(Type) = (SELECT MAX(rn) FROM cte1)
ORDER BY Type;
LiveDemo
I've assumed that the order of "cross join" is dependent on ascending ID.
cte1 generate DENSE_RANK() because your IDs contain gaps
cte2 recursive part with CONCAT
main query just filter out required length and sort string
The recursive query is a bit simpler in Postgres:
WITH RECURSIVE t AS ( -- to produce gapless group numbers
SELECT dense_rank() OVER (ORDER BY group_id) AS grp, name
FROM tbl
)
, cte AS (
SELECT grp, name
FROM t
WHERE grp = 1
UNION ALL
SELECT t.grp, c.name || t.name
FROM cte c
JOIN t ON t.grp = c.grp + 1
)
SELECT name AS combi
FROM cte
WHERE grp = (SELECT max(grp) FROM t)
ORDER BY 1;
The basic logic is the same as in the SQL Server version provided by #lad2025, I added a couple of minor improvements.
Or you can use a simple version if your maximum number of groups is not too big (can't be very big, really, since the result set grows exponentially). For a maximum of 5 groups:
WITH t AS ( -- to produce gapless group numbers
SELECT dense_rank() OVER (ORDER BY group_id) AS grp, name AS n
FROM tbl
)
SELECT concat(t1.n, t2.n, t3.n, t4.n, t5.n) AS combi
FROM (SELECT n FROM t WHERE grp = 1) t1
LEFT JOIN (SELECT n FROM t WHERE grp = 2) t2 ON true
LEFT JOIN (SELECT n FROM t WHERE grp = 3) t3 ON true
LEFT JOIN (SELECT n FROM t WHERE grp = 4) t4 ON true
LEFT JOIN (SELECT n FROM t WHERE grp = 5) t5 ON true
ORDER BY 1;
Probably faster for few groups. LEFT JOIN .. ON true makes this work even if higher levels are missing. concat() ignores NULL values. Test with EXPLAIN ANALYZE to be sure.
SQL Fiddle showing both.

Select max key from joined keys

I have a table that contains keys that have been changed to a different key. These are laid out like this:
origkey newkey
1 2
2 3
4 5
6 7
7 8
8 9
9 10
What I'm trying to accomplish is a query that takes the origkey and finds the max newkey for each one. In the example above, the results would look like:
origkey maxkey
1 3
4 5
6 10
If I knew the maximum amount of times that the key could have been changed, I would just add that amount of self joins and get it from there. Unfortunately, I don't know how many times it could have changed in the past. Is there a way to keep self joining until it finds a null? The following query will return the changed keys into new columns, but I think I'm going down the wrong road here since this will get the 1 -> 3 change, but not the 6 -> 10 change.
select a.origkey
,a.newkey
,b.newkey newkey1
,c.newkey newkey2
from changedkeys a
Left Outer Join changedkeys b on a.newkey=b.origkey
Left Outer Join changedkeys c on b.newkey=c.origkey
There is a way. It is called a recursive CTE:
with cte as (
select origkey, newkey, 1 as lev
from table1
union all
select cte.origkey, t1.newkey, lev + 1
from cte join
table1 t1
on cte.newkey = t1.origkey
)
select origkey, newkey as newestkey
from (select cte.*, row_number() over (partition by origkey order by lev desc) as seqnum
from cte
) t
where seqnum = 1;
Note that this assumes that there are no cycles in the key definitions, as in the example in your question. If this is a possibility, the recursive CTE can be modified to handle this.
EDIT:
If you have potential cycles in the data, then try this:
with cte as (
select origkey, newkey, 1 as lev, ',' + cast(newkey as varchar(8000)) + ',' as keys
from table1
union all
select cte.origkey, t1.newkey, cte.lev + 1, keys + cast(t1.newkey as varchar(8000)) + ','
from cte join
table1 t1
on cte.newkey = t1.origkey
where ',' + t1.keys + ',' not like '%,' + cast(t1.newkey as varchar(8000)) + '%,'
)
select origkey, newkey as newestkey
from (select cte.*, row_number() over (partition by origkey order by lev desc) as seqnum
from cte
) t
where seqnum = 1;

Moving Average / Rolling Average

I have 2 columns in MS SQL one is Serial no. and other is values. I need the thrird column which gives me the sum of the value in that row and the next 2.
Ex
SNo values
1 2
2 3
3 1
4 2
5 6
7 9
8 3
9 2
So I need third column which has sum of 2+3+1, 3+1+2 and So on, so the 8th and 9th row will not have any values:
1 2 6
2 3 6
3 1 4
4 2 5
5 1 6
7 2 7
8 3
9 2
Can the Solution be generic so that I can Varry the current window size of adding 3 numbers to a bigger number say 60.
Here is the SQL Fiddle that demonstrates the following query:
WITH TempS as
(
SELECT s.SNo, s.value,
ROW_NUMBER() OVER (ORDER BY s.SNo) AS RowNumber
FROM MyTable AS s
)
SELECT m.SNo, m.value,
(
SELECT SUM(s.value)
FROM TempS AS s
WHERE RowNumber >= m.RowNumber
AND RowNumber <= m.RowNumber + 2
) AS Sum3InRow
FROM TempS AS m
In your question you were asking to sum 3 consecutive values. You modified your question saying the number of consecutive records you need to sum could change. In the above query you simple need to change the m.RowNumber + 2 to what ever you need.
So if you need 60, then use
m.RowNumber + 59
As you can see it is very flexible since you only have to change one number.
In case the sno field is not sequential, you can use row_number() with aggregation:
with ss as (
select sno, values, row_number() over (order by sno) as seqnum
from s
)
select s1.sno, s1.values,
(case when count(s2.values) = 3 then sum(s2.values) end) as avg3
from ss s1 left outer join
ss s2
on s2.seqnum between s1.seqnum - 2 and s1.seqnum
group by s1.sno, s1.values;
select one.sno, one.values, one.values+two.values+three.values as thesum
from yourtable as one
left join yourtable as two
on one.sno=two.sno-1
left join yourtable as three
on one.sno=three.sno-2
Or, as requested in your comment, you could do this:
select sno, sum(values)
over (
order by sno
rows between current row and 3 following
)
from yourtable
If you need a fully generic solution, where you can sum, for example, current row + next row + 5th following row:
Step 1: Create an table listing the offsets needed. 0 = current row, 1 = next row, -1 = prev row, etc
SELECT * FROM (VALUES
(0),(1),(2)
) o(offset)
Step 2: Use that offset table in this template (via CTE or an actual table):
WITH o AS (SELECT * FROM (VALUES (0),(1),(2) ) o(offset))
SELECT
t1.sno,
t1.value,
SUM(t2.Value)
FROM #t t1
INNER JOIN #t t2 CROSS JOIN o
ON t2.sno = t1.sno + o.offset
GROUP BY t1.sno,t1.value
ORDER BY t1.sno
Also, if SNo is not sequential, you can fetch ROW_NUMBER() and join on that instead.
WITH
o AS (SELECT * FROM (VALUES (0),(1),(2) ) o(offset)),
t AS (SELECT *,ROW_NUMBER() OVER(ORDER BY sno) i FROM #t)
SELECT
t1.sno,
t1.value,
SUM(t2.Value)
FROM t t1
INNER JOIN t t2 CROSS JOIN o
ON t2.i = t1.i + o.offset
GROUP BY t1.sno,t1.value
ORDER BY t1.sno

Duplicate Counts - TSQL

I want to get All records that has duplicate values for SOME of the fields (i.e. Key columns).
My code:
CREATE TABLE #TEMP (ID int, Descp varchar(5), Extra varchar(6))
INSERT INTO #Temp
SELECT 1,'One','Extra1'
UNION ALL
SELECT 2,'Two','Extra2'
UNION ALL
SELECT 3,'Three','Extra3'
UNION ALL
SELECT 1,'One','Extra4'
SELECT ID, Descp, Extra FROM #TEMP
;WITH Temp_CTE AS
(SELECT *
, ROW_NUMBER() OVER (PARTITION BY ID, Descp ORDER BY (SELECT 0))
AS DuplicateRowNumber
FROM #TEMP
)
SELECT * FROM Temp_cte
DROP TABLE #TEMP
The last column tells me how many times each row has appeared based on ID and Descp values.
I want that row but I ALSO need another column* that indicates both rows for ID = 1 and Descp = 'One' has showed up more than once.
So an extra column* (i.e. MultipleOccurances (bool)) which has 1 for two rows with ID = 1 and Descp = 'One' and 0 for other rows as they are only showing up once.
How can I achieve that? (I want to avoid using Count(1)>1 or something if possible.
Edit:
Desired output:
ID Descp Extra DuplicateRowNumber IsMultiple
1 One Extra1 1 1
1 One Extra4 2 1
2 Two Extra2 1 0
3 Three Extra3 1 0
SQL Fiddle
You say "I want to avoid using Count" but it is probably the best way. It uses the partitioning you already have on the row_number
SELECT *,
ROW_NUMBER() OVER (PARTITION BY ID, Descp
ORDER BY (SELECT 0)) AS DuplicateRowNumber,
CASE
WHEN COUNT(*) OVER (PARTITION BY ID, Descp) > 1 THEN 1
ELSE 0
END AS IsMultiple
FROM #Temp
And the execution plan just shows a single sort
Well, I have this solution, but using a Count...
SELECT T1.*,
ROW_NUMBER() OVER (PARTITION BY T1.ID, T1.Descp ORDER BY (SELECT 0)) AS DuplicateRowNumber,
CASE WHEN T2.C = 1 THEN 0 ELSE 1 END MultipleOcurrences FROM #temp T1
INNER JOIN
(SELECT ID, Descp, COUNT(1) C FROM #TEMP GROUP BY ID, Descp) T2
ON T1.ID = T2.ID AND T1.Descp = T2.Descp