Display only the first row per match - sql

I have a table (AreaPartners), and I want to match only the first "Name" record in each group, order by "ID", grouped by "Area". So for the table below:
Area Name ID
AB ISmith 748
AB AWood 750
AB HArcher 751
AB DMunslow 753
AB DCornelius 754
BH MLee 301
BH NMcClean 307
BH DMiles 309
BH LPayze 325
BH MPinnock 329
I'd want to return the results ISmith for AB and MLee for BH.
How do I go about doing this? I believe it's something to do with the Group By function, but I can't for the life of me get it to work.

Try this:
SELECT yourTable.Area, yourTable.Name
FROM yourTable INNER JOIN (
SELECT MIN(Id) AS MinId
FROM yourTable
GROUP BY Area) M ON yourTable.Id = M.MinId

Update because of comment (There is no table variable and Partition over is not a MS access statement). You can also do it with an IN statement:
SELECT
yourTable.Area,
yourTable.Name
FROM yourTable
WHERE yourTable.Id IN
(
SELECT
MIN(tbl.Id) AS MinId
FROM
yourTable as tbl
GROUP BY
tbl.Area
)
In MSSQL you can write this:
DECLARE #tbl TABLE
(
Area VARCHAR(100),
Name VARCHAR(100),
ID INT
)
INSERT INTO #tbl
SELECT 'AB','ISmith',748
UNION ALL
SELECT 'AB','AWood',750
UNION ALL
SELECT 'AB','HArcher',751
UNION ALL
SELECT 'AB','DMunslow',753
UNION ALL
SELECT 'AB','DCornelius',754
UNION ALL
SELECT 'BH','MLee',301
UNION ALL
SELECT 'BH','NMcClean',307
UNION ALL
SELECT 'BH','DMiles',309
UNION ALL
SELECT 'BH','LPayze',325
UNION ALL
SELECT 'BH','MPinnock',325
;WITH CTE
AS
(
SELECT
RANK() OVER(PARTITION BY tbl.Area ORDER BY ID) AS iRank,
tbl.ID,
tbl.Area,
tbl.Name
FROM
#tbl AS tbl
)
SELECT
*
FROM
CTE
WHERE
CTE.iRank=1

Related

How to get last record from Master-Details tables

I have a table that has 3 columns.
create table myTable
(
ID int Primary key,
Detail_ID int references myTable(ID) null, -- reference to self
Master_Value varchar(50) -- references to master table
)
this table has the follow records:
insert into myTable select 100,null,'aaaa'
insert into myTable select 101,100,'aaaa'
insert into myTable select 102,101,'aaaa'
insert into myTable select 103,102,'aaaa' ---> last record
insert into myTable select 200,null,'bbbb'
insert into myTable select 201,200,'bbbb'
insert into myTable select 202,201,'bbbb' ---> last record
the records is saved In the form of relational with ID and Detail_ID columns.
I need to select the last record each Master_Value column. follow output:
lastRecordID Master_Value Path
202 bbbb 200=>201=>202
103 aaaa 100=>101=>102=>103
tips:
The records are not listed in order in the table.
I can not use the max(ID) keyword. beacuse data is not sorted.(may
be the id column updated manually.)
attempts:
I was able to Prepare follow query and is working well:
with Q as
(
select ID ,Detail_ID, Master_Value , 1 RowOrder, CAST(id as varchar(max)) [Path] from myTable where Detail_ID is null
union all
select R.id,R.Detail_ID , r.Master_Value , (q.RowOrder + 1) RowOrder , (q.[Path]+'=>'+CAST(r.id as varchar(max))) [Path] from myTable R inner join Q ON Q.ID=R.Detail_ID --where r.Dom_ID_RowType=1010
)
select * into #q from Q
select Master_Value, MAX(RowOrder) lastRecord into #temp from #Q group by Master_Value
select
q.ID lastRecordID,
q.Master_Value,
q.[Path]
from #temp t
join #q q on q.RowOrder = t.lastRecord
where
q.Master_Value = t.Master_Value
but I need to simple way (one select) and optimal method.
Can anyone help me?
One method uses a correlated subquery to get the last value (which is how I interpreted your question):
select t.*
from mytable t
where not exists (select 1
from mytable t2
where t2.master_value = t.master_value and
t2.id = t.detail_id
);
This returns rows that are not referred to by another row.
For the path, you need a recursive CTE:
with cte as (
select master_value, id as first_id, id as child_id, convert(varchar(max), id) as path, 1 as lev
from mytable t
where detail_id is null
union all
select cte.master_value, cte.first_id, t.id, concat(path, '->', t.id), lev + 1
from cte join
mytable t
on t.detail_id = cte.child_id and t.master_value = cte.master_value
)
select cte.*
from (select cte.*, max(lev) over (partition by master_value) as max_lev
from cte
) cte
where max_lev = lev
Here is a db<>fiddle.

Can I delete multiple of nth rows in a single query from a table in SQL?

I want to delete multiple of 4 from my table which have thousands of record. How can I do it?
Ex:-
Table1
1 a
2 b
3 c
4 d
5 e
6 f
7 g
8 h
9 i
I want to delete every 4th row.
I don't want to use a loop or cursor.
Delete A from
(
Select *,Row_Number() Over(Order By Id) as RN from TableA
) A
where RN%4=0
SQL Fiddle Link
Try this...
delete from table_name where (col1 % 4) = 0
Use a CTE.
WITH cte AS (
SELECT t.*, ROW_NUMBER() OVER (ORDER BY t.rowfield) AS rank
FROM Table1 t)
SELECT rowfield, fielda
FROM cte
WHERE rank%4 != 0
Output
rowfield fielda
1 a
2 b
3 c
5 e
6 f
7 g
9 i
SQL Fiddle: http://sqlfiddle.com/#!6/c9540b/13/0
Once you are happy with the output use DELETE FROM.
This can use an index if one exists and uses numbers table
;with cte
as
(select n from numbers
where n%4=0
)
delete t
from table1 t
join
cte c
on c.n=t.id
Try this
DECLARE #nvalToDelete varchar(100)='4,7,3,8,9'-- just give values to delete from table
DECLARE #temp TABLE
(
valtodelete VARCHAR(100)
)
DECLARE #Deletetemp TABLE
(
valtodelete INT
)
INSERT INTO #temp
SELECT #nvalToDelete
INSERT INTO #Deletetemp
SELECT split.a.value('.', 'VARCHAR(1000)') AS valToDelete
FROM (SELECT Cast('<S>' + Replace(valtodelete, ',', '</S><S>')
+ '</S>' AS XML) AS valToDelete
FROM #temp) AS A
CROSS apply valtodelete.nodes('/S') AS Split(a)
DECLARE #Table1 TABLE
(
ID INT,
val varchar(10)
)
INSERT INTO #Table1
SELECT 1,'a' UNION ALL
SELECT 2,'b' UNION ALL
SELECT 3,'c' UNION ALL
SELECT 4,'d' UNION ALL
SELECT 5,'e' UNION ALL
SELECT 6,'f' UNION ALL
SELECT 7,'g' UNION ALL
SELECT 8,'h' UNION ALL
SELECT 9,'i'
SELECT *
FROM #Table1;
WITH cte
AS (SELECT *,
RN = Row_number()
OVER (
ORDER BY id )
FROM #Table1)
DELETE FROM #Table1
WHERE id IN(SELECT id FROM cte
WHERE rn IN (SELECT CASt(valToDelete AS INT) FROM #Deletetemp)
)
SELECT *
FROM #Table1

Select unique field

I have this table:
TableA
----------------
ID (pk) Name
1 A
2 B
3 C
4 A
5 D
6 A
7 B
8 A
9 D
10 C
....
I need to randomly extract with a SELECT TOP 5 ID, Name FROM TableA
with Name that must be unique within the 5 records.
I'm trying :
;WITH group
AS
(
SELECT ID, Name,
ROW_NUMBER() OVER (PARTITION BY Name ORDER BY NewId()) rn
FROM TableA
)
SELECT ID, Name
FROM group
WHERE rn = 1
but every time I have quite the same results.
I need to select between all the values for ID at random, assuring that Name will always be different for each record.
I hope the problem is understandable. Any ideas?
Found a solution. It seems to work!
;WITH group
AS (
SELECT ID, Name, ROW_NUMBER() OVER (PARTITION BY Name ORDER BY NewId()) rn FROM TableA )
SELECT top 5 ID, Name, NewId() [NewId]
FROM group
WHERE rn = 1
ORDER BY [newid]
Perhaps the problem is that although newid() is random, it may tend to be sequential. Does this fix the problem?
WITH g as (
SELECT ID, Name,
ROW_NUMBER() OVER (PARTITION BY Name ORDER BY RAND(CHECKSUM(NewId()))) as rn
FROM TableA
)
SELECT ID, Name
FROM g
WHERE rn = 1;
CREATE TABLE #test(ID INT ,Name VARCHAR(1)) INSERT INTO #test(ID ,Name )
SELECT 1,'A' UNION ALL SELECT 2,'B' UNION ALL SELECT 3,'C' UNION ALL
SELECT 4,'A' UNION ALL SELECT 5,'D'UNION ALL SELECT 6,'A' UNION ALL
SELECT 7,'B' UNION ALL SELECT 8,'A'UNION ALL SELECT 9,'D' UNION ALL
SELECT 10,'C'
SELECT T1.ID ,T1.Name FROM #test T1
JOIN ( SELECT TOP 5 Name FROM #test T2 ORDER BY NEWID()
) A ON T1.Name = A.Name ORDER BY A.Name
;WITH group
AS
(
SELECT ID, Name,
ROW_NUMBER() OVER (PARTITION BY Name ORDER BY NewId()) rn
FROM TableA
)
SELECT top 5 ID, Name, NewId() [NewId]
FROM group
WHERE rn = 1
ORDER BY [newid]

Group By and get distinct value that occurs most often

I want to group by a varchar column and find the foreignkey value that occurs the most. The problem is that multiple fiModel can be assigned to the same TAC(first 8 digits of a 15-digit value called SSN_Number).
Here is a simplified model and query with sample-data:
create table #data(
SSN_Number varchar(15),
fiModel int
)
insert into #data
SELECT '351806038155151',451 UNION ALL SELECT '353797028764243',232 UNION ALL SELECT '353797028764243',438 UNION ALL SELECT '353797028764243',438 UNION ALL SELECT '353797028764243',447 UNION ALL SELECT '358372015611578',318 UNION ALL SELECT '352045039834626',279 UNION ALL SELECT '352045031234567',279 UNION ALL SELECT '351806035647381',451 UNION ALL SELECT '352045037654321',207
--- following query returns all records(10)
select * from #data Order By SSN_Number
--- following query gives the distinct TAC's+fiModel, but TACs can repeat (9)
select substring(ssn_number,1,8)as TAC,fiModel,count(*) from #data
group by substring(ssn_number,1,8),fiModel
Order By substring(ssn_number,1,8),fiModel
--- following query gives the correct(distinct) TAC's (4),
--- but i need the fiModel that occurs most often with the assigned TAC
--- if the number is the same, it doesn't matter what to take
select substring(ssn_number,1,8)as TAC,count(*) from #data
group by substring(ssn_number,1,8)
Order By substring(ssn_number,1,8)
drop table #data
So this is the desired result:
TAC fiModel
35180603 451
35204503 279
35379702 438
35837201 318
This should do the trick (CTE's to the rescue!):
;with cte as (
select substring(ssn_number,1,8) as TAC, fiModel, ROW_NUMBER() OVER (PARTITION BY substring(ssn_number,1,8) ORDER BY count(*) desc) as row
from #data
group by substring(ssn_number,1,8),fiModel
)
select TAC, fiModel
from cte
where row = 1
As subquery:
Select TAC,fiModel
from(
Select substring(ssn_number,1,8)as TAC, fiModel
,ROW_NUMBER() OVER (PARTITION BY substring(ssn_number,1,8) ORDER BY count(*) desc) as row
from #data
group by substring(ssn_number,1,8),fiModel
)as data
where row=1

SQLite select next and previous row based on a where clause

I want to be able to get the next and previous row using SQLite.
id statusid date
168 1 2010-01-28 16:42:27.167
164 1 2010-01-28 08:52:07.207
163 1 2010-01-28 08:51:20.813
161 1 2010-01-28 07:10:35.373
160 1 2010-01-27 16:09:32.550
46 2 2010-01-30 17:13:45.750
145 2 2010-01-30 17:13:42.607
142 2 2010-01-30 16:11:58.020
140 2 2010-01-30 15:45:00.543
For example:
Given id 46 I would like to return ids 160 (the previous one) and 145 (the next one)
Given id 160 I would like to return ids 161 (the previous one) and 46 (the next one)
etc...
Be aware that the data is ordered by statusId then dateCreated DESC and HAS to work using SQLite.
select * from #t order by statusId, dateCreated desc
Test data created in sql server...
set nocount on; set dateformat ymd;
declare #t table(id int, statusId int, dateCreated datetime)
insert into #t
select 168,1,'2010-01-28 16:42:27.167' union
select 164,1,'2010-01-28 08:52:07.207' union
select 163,1,'2010-01-28 08:51:20.813' union
select 161,1,'2010-01-28 07:10:35.373' union
select 160,1,'2010-01-27 16:09:32.550' union
select 46,2,'2010-01-30 17:13:45.750' union
select 145,2,'2010-01-30 17:13:42.607' union
select 142,2,'2010-01-30 16:11:58.020' union
select 140,2,'2010-01-30 15:45:00.543'
Using SQL server 2005+ this would be fairly trivial!
EDIT:
Here's the same test data script but for SQLite which is the focus of the question.
create table t (id int, statusId int, dateCreated datetime);
insert into t
select 168,1,'2010-01-28 16:42:27.167' union
select 164,1,'2010-01-28 08:52:07.207' union
select 163,1,'2010-01-28 08:51:20.813' union
select 161,1,'2010-01-28 07:10:35.373' union
select 160,1,'2010-01-27 16:09:32.550' union
select 46,2,'2010-01-30 17:13:45.750' union
select 145,2,'2010-01-30 17:13:42.607' union
select 142,2,'2010-01-30 16:11:58.020' union
select 140,2,'2010-01-30 15:45:00.543';
EDIT 2 Please note that the data is not a good example so I have change the id 146 to 46
This problem is a lot more complicated than it first appears. The two order by fields have to be handled separately and then combined with a union and grab the appropriate result. To get both previous and next, we need another union, so we end up with a union with sub-unions.
This works with the supplied data. I tested many inputs and got the right previous/next outputs. When using, make sure you get ALL instances of 146 to replace.
SELECT *
FROM
(
SELECT t1.*
FROM t t1,
(
SELECT *
FROM t
WHERE id = 146
) t2
WHERE t1.statusid = t2.statusid
AND t1.dateCreated >= t2.dateCreated
AND t1.id <> 146
UNION
SELECT t1.*
FROM t t1,
(
SELECT *
FROM t
WHERE id = 146
) t2
WHERE t1.statusid < t2.statusid
ORDER BY
t1.statusid DESC,
t1.dateCreated
LIMIT 1
)
UNION
SELECT *
FROM
(
SELECT t1.*
FROM t t1,
(
SELECT *
FROM t
WHERE id = 146
) t2
WHERE t1.statusid = t2.statusid
AND t1.dateCreated <= t2.dateCreated
AND t1.id <> 146
UNION
SELECT t1.*
FROM t t1,
(
SELECT *
FROM t
WHERE id = 146
) t2
WHERE t1.statusid > t2.statusid
ORDER BY
t1.statusid,
t1.dateCreated DESC
LIMIT 1
)
ORDER BY
statusid,
dateCreated DESC
;
select id from theTable where id>#id order by id desc limit 1
union
select id from theTable where id<#id order by id desc limit 1
You might want to look into using SQLIte primitives such as rowid (https://www.sqlitetutorial.net/sqlite-primary-key/)
OR
creating your own set of rowid's based on the order that you output (possibly based on a sub-select count(*) query).
You can then do a select based on rowid-1 or rowid+1.
EDIT:
Some code. Mostly for my own reference.
-- create a new table with the desired order
DROP TABLE IF EXISTS tt;
CREATE TABLE tt AS
SELECT * FROM t ORDER BY statusId, dateCreated DESC;
-- remove all primary's (rowid's)
VACUUM;
-- create new table from ordered one, and add ascending rowid
DROP TABLE IF EXISTS ttt;
CREATE TABLE ttt AS
SELECT rowid, * FROM tt;
-- create a new table with the row before and after the desired row
-- still need to work out how to do this with a variable for easily changing the id
SELECT t1.*,t3.*,t4.*
FROM ttt t1
LEFT JOIN ttt t3 ON t3.rowid=((SELECT CAST(rowid AS INT) FROM ttt WHERE ttt.id=46)-1)
LEFT JOIN ttt t4 ON t4.rowid=((SELECT CAST(rowid AS INT) FROM ttt WHERE ttt.id=46)+1)
WHERE t1.id=46;