select "step" records - sql

Given the following table
grp | ind | val
----------------------
a | 1 | 1
a | 2 | 1
a | 3 | 1
a | 4 | 2
a | 5 | 2
a | 6 | 4
a | 7 | 2
b | 1 | 1
b | 2 | 1
b | 3 | 1
b | 4 | 3
b | 5 | 3
b | 6 | 4
I need to select the following:
grp | ind | val
----------------------
a | 1 | 1
a | 4 | 2
a | 6 | 4
a | 7 | 2
b | 1 | 1
b | 4 | 3
b | 6 | 4
That is for each 'grp', each record where the 'val' is different to the proceeding 'val' (ordered by 'index') So each record where the 'value' "steps".
what would be the most efficient way to achieve this?
thanks.
Here is a script to create the test case:
create temp table test_table
(
grp character varying,
ind numeric,
val numeric
);
insert into test_table values
('a', 1 , 1),
('a', 2 , 1),
('a', 3 , 1),
('a', 4 , 2),
('a', 5 , 2),
('a', 6 , 4),
('a', 7 , 2),
('b', 1 , 1),
('b', 2 , 1),
('b', 3 , 1),
('b', 4 , 3),
('b', 5 , 3),
('b', 6 , 4);

select grp,
ind,
val
from (
select grp,
ind,
val,
lag(val,1,0::numeric) over (partition by grp order by ind) - val as diff
from test_table
) t
where diff <> 0;

DROP SCHEMA tmp CASCADE;
CREATE SCHEMA tmp ;
SET search_path=tmp;
CREATE TABLE ztable
( zgroup CHAR(1)
, zindex int
, zvalue INTEGER
);
INSERT INTO ztable(zgroup,zindex,zvalue) VALUES
('a', 1, 1)
,('a', 2, 1)
,('a', 3, 1)
,('a', 4, 2)
,('a', 5, 2)
,('a', 6, 4)
,('b', 1, 1)
,('b', 2, 1)
,('b', 3, 1)
,('b', 4, 3)
,('b', 5, 3)
,('b', 6, 4)
;
WITH agg AS (
SELECT zgroup
, zindex
, zvalue
, row_number() OVER (PARTITION BY zgroup ORDER BY zindex) AS zrank
FROM ztable
)
SELECT t1.zgroup,t1.zindex,t1.zvalue
FROM agg t1
LEFT JOIN agg t0 ON t0.zgroup = t1.zgroup AND 1+t0.zrank = t1.zrank
WHERE t0.zvalue <> t1.zvalue OR t0.zrank IS NULL
;

select group,min(index) as index,value from table
group by group,value

Related

Join on existing aggregate query to pivot result without id

On a Sql-Server instance, I have three tables:
ActionItem
Id
Name
1
Fish
2
Gravy
3
Pants
ActionData
Id
ActionId
Group
Field
Value
1
1
1
1
100
2
1
1
2
200
3
1
1
3
300
4
1
1
4
NULL
5
1
1
5
NULL
6
1
2
6
"Some Text"
7
2
1
1
50
8
2
1
2
60
9
2
1
3
70
Costing
Id
ActionId
Break
Cost
1
1
Normal
11.3
2
1
Sub
54
3
1
Premium
0.4
4
3
Normal
22
5
3
Premium
0.67
I have a query that sums the cost for each ActionItem:
select
ai.Id,
ai.Name,
sum(c.Cost)
from ActionItem ai
left join Costing c on ai.Id = c.ActionId
group by
ai.Id,
ai.Name
Nice and straight-forward:
Id
Name
(No column name)
1
Fish
65.7
2
Gravy
NULL
3
Pants
22.67
I created a pivot too:
select * from
(select [ActionId], [Group], [Field], [Value] from ActionData) src
pivot (max([Value]) for [ActionId] in ([1],[2],[3],[4])) ppp
Which gets me data in the right format:
Group
Field
1
2
3
4
1
1
100
50
NULL
NULL
1
2
200
60
NULL
NULL
1
3
300
70
NULL
NULL
1
4
NULL
NULL
NULL
NULL
1
5
NULL
NULL
NULL
NULL
2
6
"Some Text"
NULL
NULL
NULL
But I cannot join these two queries together because that PIVOT doesn't contain the ActionId ... even though I use Select * from - how can I get the ActionId col to show on my pivoted data, so I can join it to the rest of my original query?
I could not get sqlfiddle.com to work for MS SQL SERVER today but here are create and inserts if anyone's interested:
CREATE TABLE ActionItem
([Id] int, [Name] varchar(5));
INSERT INTO ActionItem
([Id], [Name])
VALUES
(1, 'Fish'),
(2, 'Gravy'),
(3, 'Pants');
CREATE TABLE ActionData
([Id] int, [ActionId] int, [Group] int, [Field] int, [Value] varchar(11));
INSERT INTO ActionData
([Id], [ActionId], [Group], [Field], [Value])
VALUES
(1, 1, 1, 1, '100'),
(2, 1, 1, 2, '200'),
(3, 1, 1, 3, '300'),
(4, 1, 1, 4, NULL),
(5, 1, 1, 5, NULL),
(6, 1, 2, 6, '"Some Text"'),
(7, 2, 1, 1, '50'),
(8, 2, 1, 2, '60'),
(9, 2, 1, 3, '70')
;
CREATE TABLE Costing (
[Id] int,
[ActionId] int,
[Break] VARCHAR(9),
[Cost] FLOAT);
INSERT INTO Costing
([Id], [ActionId], [Break], [Cost])
VALUES
('1', '1', 'Normal', '11.3'),
('2', '1', 'Sub', '54'),
('3', '1', 'Premium', '0.4'),
('4', '3', 'Normal', '22'),
('5', '3', 'Premium', '0.67');
Not sure what output you expect.
But here's an attempt to join the two queries in 1 pivot.
select pvt.*
from
(
select d.ActionId, ai.Name
--, d.[Group]
, cast(d.[Field] as varchar(30)) as [Col]
, try_cast(d.[Value] as float) as [Value]
from ActionData d
left join ActionItem ai on ai.Id = d.ActionId
where isnumeric(d.[Value]) = 1
union all
select c.ActionId, ai.Name
--, 1 as [Group]
, c.[Break] as [Col]
, sum(c.Cost) as TotalCost
from Costing c
left join ActionItem ai
on ai.Id = c.ActionId
group by c.ActionId, ai.Name, c.[Break]
) src
pivot (
max([Value])
for [Col] in ([1],[2],[3],[4],[Normal],[Premium],[Sub])
) pvt
GO
ActionId | Name | 1 | 2 | 3 | 4 | Normal | Premium | Sub
-------: | :---- | ---: | ---: | ---: | ---: | -----: | ------: | ---:
1 | Fish | 100 | 200 | 300 | null | 11.3 | 0.4 | 54
2 | Gravy | 50 | 60 | 70 | null | null | null | null
3 | Pants | null | null | null | null | 22 | 0.67 | null
db<>fiddle here

How to change a value rank in a column MS SQL

I've a table with a column which is defining a rank value for display position:
Unid | Rank | Name
10 | 1 | A
20 | 2 | B
30 | 3 | C
40 | 4 | D
50 | 5 | E
How to update the table for have Name E on the top of the list and followed by the A, B , C , D names ?
One possible solution is to use ROW_NUMBER() with appropriate ORDER BY clause:
Table:
CREATE TABLE Data (
[Unid] int,
[Rank] int,
[Name] varchar(1)
)
INSERT INTO Data ([Unid], [Rank], [Name])
VALUES
(10, 1, 'A'),
(20, 2, 'B'),
(30, 3, 'C'),
(40, 4, 'D'),
(50, 5, 'E')
Statement:
UPDATE d
SET d.[Rank] = d.[NewRank]
FROM (
SELECT
[Rank],
ROW_NUMBER() OVER (ORDER BY CASE WHEN [Name] = 'E' THEN 0 ELSE 1 END, [Name]) AS [NewRank]
FROM Data
) d
Result:
Unid Rank Name
10 2 A
20 3 B
30 4 C
40 5 D
50 1 E

Correlative by group with special criteria

We need to get the result of the column NEED, need the correlative grouped by the GROUP column in the order of the ORDER column and that increases when the FLAG column changes.
GROUP ORDER FLAG NEED
1111 1 0 1
1111 2 0 1
1111 3 1 2
1111 4 1 2
1111 5 1 2
1111 6 1 2
1111 7 1 2
1111 8 0 3
1111 9 1 4
1111 10 1 4
1111 11 0 5
1111 12 0 5
1111 13 0 5
6666 1 0 1
6666 2 0 1
6666 3 1 2
6666 4 1 2
We try the following code, but we need something cleaner with support for SQL Server 2008
if object_id('tempdb..#temp2','u') is not NULL
drop table #temp2
SELECT *
,ROW_NUMBER() OVER(oRDER BY (SELECT NULL)) RN
INTO #temp2
FROM DBO.PRUEBA​
SELECT T1.*
,SUM(CASE WHEN T1.NUM_GROUP = T2.NUM_GROUP and t1.NUM_FLAG = t2.NUM_FLAG THEN 0 ELSE 1 END) OVER (PARTITION BY T1.NUM_GROUP ORDER BY T1.rn)[Rank]
FROM #temp2 T1
LEFT JOIN #temp2 T2 ON T1.rn = T2.rn+1
order by t1.NUM_GROUP, t1.NUM_ORDER
I share the creation of tables and records
CREATE TABLE DBO.PRUEBA
(
NUM_GROUP INT,
NUM_ORDER INT,
NUM_FLAG INT
)
INSERT INTO DBO.PRUEBA VALUES (1111, 1, 0)
INSERT INTO DBO.PRUEBA VALUES (1111, 2, 0)
INSERT INTO DBO.PRUEBA VALUES (1111, 3, 1)
INSERT INTO DBO.PRUEBA VALUES (1111, 4, 1)
INSERT INTO DBO.PRUEBA VALUES (1111, 5, 1)
INSERT INTO DBO.PRUEBA VALUES (1111, 6, 1)
INSERT INTO DBO.PRUEBA VALUES (1111, 7, 1)
INSERT INTO DBO.PRUEBA VALUES (1111, 8, 0)
INSERT INTO DBO.PRUEBA VALUES (1111, 9, 1)
INSERT INTO DBO.PRUEBA VALUES (1111, 10, 1)
INSERT INTO DBO.PRUEBA VALUES (1111, 11, 0)
INSERT INTO DBO.PRUEBA VALUES (1111, 12, 0)
INSERT INTO DBO.PRUEBA VALUES (1111, 13, 0)
INSERT INTO DBO.PRUEBA VALUES (6666, 1, 0)
INSERT INTO DBO.PRUEBA VALUES (6666, 2, 0)
INSERT INTO DBO.PRUEBA VALUES (6666, 3, 1)
INSERT INTO DBO.PRUEBA VALUES (6666, 4, 1)
SELECT * FROM DBO.PRUEBA
A possible optimalization is to first create the temporary table.
Instead of using a SELECT INTO.
And with a primary key that benefits the self-join that's used to get the previous NUM_FLAG.
CREATE TABLE DBO.PRUEBA
(
NUM_GROUP INT NOT NULL,
NUM_ORDER INT NOT NULL,
NUM_FLAG INT NOT NULL,
PRIMARY KEY (NUM_GROUP, NUM_ORDER)
);
GO
INSERT INTO DBO.PRUEBA
(NUM_GROUP, NUM_ORDER, NUM_FLAG)
VALUES
(1111, 1, 0)
,(1111, 2, 0)
,(1111, 3, 1)
,(1111, 4, 1)
,(1111, 5, 1)
,(1111, 6, 1)
,(1111, 7, 1)
,(1111, 8, 0)
,(1111, 9, 1)
,(1111, 10, 1)
,(1111, 11, 0)
,(1111, 12, 0)
,(1111, 13, 0)
,(6666, 1, 0)
,(6666, 2, 0)
,(6666, 3, 1)
,(6666, 4, 1)
IF OBJECT_ID('tempdb..#tmpPRUEBA', 'U') IS NOT NULL
DROP TABLE #tmpPRUEBA;
CREATE TABLE #tmpPRUEBA
(
NUM_GROUP INT NOT NULL,
RN_GROUP INT NOT NULL,
NUM_ORDER INT NOT NULL,
NUM_FLAG INT NOT NULL,
PRIMARY KEY (NUM_GROUP, RN_GROUP)
);
GO
INSERT INTO #tmpPRUEBA
(NUM_GROUP, NUM_ORDER, NUM_FLAG, RN_GROUP)
SELECT NUM_GROUP, NUM_ORDER, NUM_FLAG
, ROW_NUMBER() OVER (
PARTITION BY NUM_GROUP
ORDER BY NUM_ORDER) AS RN_GROUP
FROM DBO.PRUEBA;
GO
SELECT
t1.NUM_GROUP,
t1.NUM_ORDER,
t1.NUM_FLAG,
SUM(CASE
WHEN t1.NUM_FLAG = t2.NUM_FLAG
THEN 0
ELSE 1
END)
OVER (PARTITION BY t1.NUM_GROUP
ORDER BY t1.RN_GROUP) AS [Rank]
FROM #tmpPRUEBA t1
LEFT JOIN #tmpPRUEBA t2
ON t2.NUM_GROUP = t1.NUM_GROUP
AND t2.RN_GROUP = t1.RN_GROUP - 1;
GO
NUM_GROUP | NUM_ORDER | NUM_FLAG | Rank
--------: | --------: | -------: | ---:
1111 | 1 | 0 | 1
1111 | 2 | 0 | 1
1111 | 3 | 1 | 2
1111 | 4 | 1 | 2
1111 | 5 | 1 | 2
1111 | 6 | 1 | 2
1111 | 7 | 1 | 2
1111 | 8 | 0 | 3
1111 | 9 | 1 | 4
1111 | 10 | 1 | 4
1111 | 11 | 0 | 5
1111 | 12 | 0 | 5
1111 | 13 | 0 | 5
6666 | 1 | 0 | 1
6666 | 2 | 0 | 1
6666 | 3 | 1 | 2
6666 | 4 | 1 | 2
db<>fiddle here

Match rows that include one of each at least once in SQL

I have a users table:
ID Name OID TypeID
1 a 1 1
2 b 1 2
3 c 1 3
4 d 2 1
5 e 2 1
6 f 2 2
7 g 3 2
8 h 3 2
9 i 3 2
for this table, I want to filter by OID and TypeID so that I get the rows that it is filtered by OID and that includes all 1, 2, and 3 in TypeID.
For example, where OID=1, we have 1, 2, and 3 in TypeID but I shouldn't get the rows with IDs 4-6 because for IDs 4-6, OIDs are the same but TypeID does not include all of each(1, 2, and 3).
You can do :
select oid
from table t
where typeid in (1,2,3)
group by oid
having count(*) = 3;
If, oid contain duplicate typeid then you can use count(distinct typeid) instead.
you could use exists
select oid from table t1
where exists ( select 1 from table t1 where t1.oid=t2.oid
group by t2.oid
having (distinct TypeID)=3
)
Asume TypeID 1,2,3
if you are using sql-server, you can try this.
DECLARE #SampleData TABLE(ID INT, Name VARCHAR(5), OID INT, TypeID INT)
INSERT INTO #SampleData VALUES
(1 , 'a', 1, 1),
(2 , 'b', 1, 2),
(3 , 'c', 1, 3),
(4 , 'd', 2, 1),
(5 , 'e', 2, 1),
(6 , 'f', 2, 2),
(7 , 'g', 3, 2),
(8 , 'h', 3, 2),
(9 , 'i', 3, 2)
SELECT * FROM #SampleData D
WHERE NOT EXISTS (
SELECT * FROM #SampleData D1
RIGHT JOIN (VALUES (1),(2),(3)) T(TypeID) ON D1.TypeID = T.TypeID
AND D.OID = D1.OID
WHERE D1.TypeID IS NULL
)
Result:
ID Name OID TypeID
----------- ----- ----------- -----------
1 a 1 1
2 b 1 2
3 c 1 3

Query to omit an entire group which has one record with a certain value in an attribute

I need to create a personal table based on particular criteria from another table. The records associated with the data are grouped together (based on distance from each other). I have another field that is populated with a particular code. The result of the query should only include all attributes from all groups that DO NOT have a certain codes (in this case 2 and 3):
|groupid | id | code | stuff |
----------------------------------|
| a | 1 | 1 | data |
| a | 2 | 1 | data |
| b | 3 | 1 | data |
| b | 4 | 2 | data |
| c | 5 | 1 | data |
| c | 6 | 3 | data |
| d | 7 | 2 | data |
| d | 8 | 4 | data |
| e | 9 | 4 | data |
| e | 10 | 4 | data |
-----------------------------------
In this case I need to create a personal table that contains all the records and attributes where the results are as follows:
|groupid | id | code | stuff |
----------------------------------|
| a | 1 | 1 | data |
| a | 2 | 1 | data |
| e | 9 | 4 | data |
| e | 10 | 4 | data |
-----------------------------------
Because codes 2 and 3 were unreliable for the study, the whole group that contains any of these values cannot be analyzed. The query should be a select * as I need all the attributes (there are more than 4). Thank you.
select *
from your_table t1
where not exists (
select 1 from your_table t2
where t1.group = t2.group
and t2.code in (2, 3) -- exclusion list here
);
Pretty much as you describe it in English
Select * from table a
Where Not exists
(Select * from table
where groupid = a.groupId
and code in (2,3))
test case:
declare #t table
(groupid char(1) not null,
id int not null, code int not null,
stff varchar(10) not null)
insert #t(groupid, id, code, stff)values
('a', 1, 1, 'data'),
('a', 2, 1, 'data'),
('b', 3, 1, 'data'),
('b', 4, 2, 'data'),
('c', 5, 1, 'data'),
('c', 6, 3, 'data'),
('d', 7, 2, 'data'),
('d', 8, 4, 'data'),
('e', 9, 4, 'data'),
('e', 10, 4, 'data')
select * from #t
Select * from #t a
Where Not exists
(Select * from #t
where groupid = a.groupId
and code in (2,3))
results:
a 1 1 data
a 2 1 data
e 9 4 data
e 10 4 data
This can be done with analytic functions, so that the base table is read just once - resulting in better performance. This is pretty much what analytic functions were created for.
If you have too many columns and don't want to type their names twice (although that is the "best practice"), you may select * in the outer query if you don't mind keeping the ct column (where all values will be 0), and in the inner query you may select <table_name>.*, count(....).... In the inner query you must qualify * with the table name, since you are also selecting an additional "column", ct.
with
test_data ( groupid, id, code, stuff ) as (
select 'a', 1, 1, 'data' from dual union all
select 'a', 2, 1, 'data' from dual union all
select 'b', 3, 1, 'data' from dual union all
select 'b', 4, 2, 'data' from dual union all
select 'c', 5, 1, 'data' from dual union all
select 'c', 6, 3, 'data' from dual union all
select 'd', 7, 2, 'data' from dual union all
select 'd', 8, 4, 'data' from dual union all
select 'e', 9, 4, 'data' from dual union all
select 'e', 10, 4, 'data' from dual
)
-- end of test data; the solution (SQL query) begins below this line
select groupid, id, code, stuff
from ( select groupid, id, code, stuff,
count(case when code in (2, 3) then 1 end)
over (partition by groupid) as ct
from test_data
)
where ct = 0
order by groupid, id -- order by is optional
;
GROUPID ID CODE STUFF
------- ---- ------ -----
a 1 1 data
a 2 1 data
e 9 4 data
e 10 4 data
4 rows selected.