select based on specific values - sql

I have this table:
ID NO.
111 6
222 7
333 9
111 8
333 4
222 3
111 7
222 5
333 2
I want to select only 2 ID numbers from table where NO. column equal specific values.
For example i tried this query but i didn't get the expected result:
SELECT top 2 * FROM mytable where NO. in
(select NO. from mytable )
Expected result:
111 6
111 8
222 7
222 3
333 9
333 3

You seem to want to select two rows in the table for each id, based on a condition on the No column. For this, one method uses row_number():
select t.*
from (select t.*, row_number() over (partition by id order by id) as seqnum
from mytable t
where <condition goes here>
) t
where seqnum <= 2;

I'm guessing (333,3) is a mistake and you expect (333,2). If not I have no idea.
SELECT
ua.ID
, ua.[NO.]
FROM (
SELECT
ROW_NUMBER() OVER (PARTITION BY ID ORDER BY t.[NO.] ASC) AS RowNum
, t.ID
, t.[NO.]
FROM dbo.t1 AS t
UNION ALL
SELECT
ROW_NUMBER() OVER (PARTITION BY ID ORDER BY t.[NO.] DESC)
, ID
, t.[NO.]
FROM dbo.t1 AS t
) ua
WHERE ua.RowNum = 1
ORDER BY ID, ua.[NO.] DESC
If you're just trying to get top 2 values for each group, you need something to define the order, ie. a third column. Then you don't need UNION ALL, just use WHERE ua.RowNum < 3.

/*Select 2 random rows per id where the number of rows per id can vary between 1 and infinity
A good article for this:-*/
--https://www.mssqltips.com/sqlservertip/3157/different-ways-to-get-random-data-for-sql-server-data-sampling/
DECLARE #TABLE TABLE(ID INT,NO INT)
INSERT INTO #TABLE
VALUES
(111, 6),
(222, 7),
(333 , 9),
(111 , 8),
(333 , 4),
(222 , 3),
(111 , 7),
(222 , 5),
(333 , 2)
select t.* from
(
Select s.* ,ROW_NUMBER() OVER(PARTITION BY ID ORDER BY randomnumber) ROWNUMBER
from
(
SELECT ID,NO,
(ABS(CHECKSUM(NEWID())) % 100001) + ((ABS(CHECKSUM(NEWID())) % 100001) * 0.00001) [randomnumber]
FROM #TABLE
) s
) t
where t.rownumber < 3

Related

GROUP by Largest String for all the substrings

I have a table like this where some rows have the same grp but different names. I want to group them by name such that all the substrings after removing nonalphanumeric characters are aggregated together and grouped by the largest string. The null value is considered the substring of all the strings.
grp
name
value
1
ab&c
10
1
abc d e
56
1
ab
21
1
a
23
1
xy
34
1
[null]
1
2
fgh
87
Desired result
grp
name
value
1
abcde
111
1
xy
34
2
fgh
87
My query-
Select grp,
regexp_replace(name,'[^a-zA-Z0-9]+', '', 'g') name, sum(value) value
from table
group by grp,
regexp_replace(name,'[^a-zA-Z0-9]+', '', 'g');
Result
grp
name
value
1
abc
10
1
abcde
56
1
ab
21
1
a
23
1
xy
34
1
[null]
1
2
fgh
87
What changes should I make in my query?
To solve this problem, I did the following (all of the code below is available on the fiddle here).
CREATE TABLE test
(
grp SMALLINT NOT NULL,
name TEXT NULL,
value SMALLINT NOT NULL
);
and populate it using your data + extra for testing:
INSERT INTO test VALUES
(1, 'ab&c', 10),
(1, 'abc d e', 56),
(1, 'ab', 21),
(1, 'a', 23),
(1, NULL, 1000000),
(1, 'r*&%$s', 100), -- added for testing.
(1, 'rs__t', 101),
(1, 'rs__tu', 101),
(1, 'xy', 1111),
(1, NULL, 1000000),
(2, 'fgh', 87),
(2, 'fgh', 13), -- For Charlieface
(2, NULL, 1000000),
(2, 'x', 50),
(2, 'x', 150),
(2, 'x----y', 100);
Then, you can use this query:
WITH t1 AS
(
SELECT
grp, n_str,
LAG(n_str) OVER (PARTITION BY grp ORDER BY grp, n_str),
CASE
WHEN
LAG(n_str) OVER (PARTITION BY grp ORDER BY grp, n_str) IS NULL
OR
POSITION
(
LAG(n_str) OVER (PARTITION BY grp ORDER BY grp, n_str)
IN
n_str
) = 0
THEN 1
ELSE 0
END AS change,
value
FROM
test t1
CROSS JOIN LATERAL
(
VALUES
(
REGEXP_REPLACE(name,'[^a-zA-Z0-9]+', '', 'g')
)
) AS v(n_str)
WHERE n_str IS NOT NULL
), t2 AS
(
SELECT
ROW_NUMBER() OVER (PARTITION BY grp, s_change ORDER BY grp, n_str DESC) AS rn,
grp, n_str,
SUM(value) OVER (PARTITION BY grp, s_change) AS s_val,
MAX(LENGTH(n_str)) OVER (PARTITION BY grp) AS max_nom
FROM
(
SELECT
grp, n_str, change,
SUM(change) OVER (ORDER BY grp, n_str) AS s_change,
value
FROM
t1
ORDER BY grp, n_str DESC
) AS sub1
), t3 AS
(
SELECT
grp, SUM(value) AS null_sum
FROM
test
WHERE name IS NULL
GROUP BY grp
)
SELECT x.grp, x.n_str, x.s_val + y.null_sum
FROM t2 x
JOIN t3 y
ON x.max_nom = LENGTH(x.n_str) AND x.grp = y.grp
UNION
SELECT grp, n_str, s_val
FROM
t2 WHERE max_nom != LENGTH(n_str) AND rn = 1
ORDER BY grp, n_str;
Result:
grp n_str ?column?
1 abcde 2000110
1 rstu 302
1 xy 1111
2 fgh 1000100
2 xy 300
A few points to note:
Please always provide a fiddle when you ask questions such as this one with tables and data - it provides a single source of truth for the question and eliminates duplication of effort on the part of those trying to help you!
You haven't been very clear about what, exactly, should happen with NULLs - do the values count towards the SUM()? You can vary the CASE statement as required.
What happens when there's a tie in the number of characters in the string? I've included an example in the fiddle, where you get the draws - but you may wish to sort alphabetically (or some other method)?
There appears to be an error in your provided sums for the values (even taking account of counting or not values for NULL for the name field).
Finally, you don't want to GROUP BY the largest string - you want to GROUP BY the grp fields + the SUM() of the values in the the given grp records and then pick out the longest alphanumeric string in that grouping. It would be interesting to know why you want to do this?

Rows Columns Traverse

I have data in the below format
id idnew
1 2
3 4
2
4 7
6 8
7
Result Should be something like this
ID should be followed by idnew
1
2
3
4
2
4
7
6
8
7
Thanks in advance
This should maintain the order:
SELECT id
FROM (
SELECT id, ROW_NUMBER() OVER (ORDER BY id) AS RowNumber
FROM myTable
UNION ALL
SELECT idnew, ROW_NUMBER() OVER (ORDER BY idnew) +
(SELECT COUNT(*) FROM dbo.myTable) AS RowNumber
FROM myTable
WHERE idnew IS NOT NULL
) a
ORDER BY RowNumber
I am assuming the id column is NOT NULL-able.
NOTE: If you want to keep the NULL values from the idnew column AND maintain the order, then remove the WHERE clause and ORDER BY id in the second select:
SELECT id
FROM (
SELECT id, ROW_NUMBER() OVER (ORDER BY id) AS RowNumber
FROM myTable
UNION ALL
SELECT idnew, ROW_NUMBER() OVER (ORDER BY id) +
(SELECT COUNT(*) FROM dbo.myTable) AS RowNumber
FROM myTable
) a
ORDER BY RowNumber
This is fully tested, try it here: https://rextester.com/DVZXO21058
Setting up the table as you described:
CREATE TABLE myTable (id INT, idnew INT);
INSERT INTO myTable (id, idnew)
VALUES (1, 2),
(3, 4),
(2, NULL),
(4, 7),
(6, 8),
(7, NULL);
SELECT * FROM myTable;
Here is the query to do the trick:
SELECT mixed_id FROM
(
SELECT ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) AS row_num,
id,
idnew
FROM myTable
) AS x
UNPIVOT
(
mixed_id for item in (id, idnew)
) AS y
WHERE mixed_id IS NOT NULL
ORDER BY row_num, mixed_id;
In order not to further complicate the query, this is taking advantage of 'id' would rank ahead of 'idnew' as a string. I believe string ranking is not the key issue here.
Using Cross Apply
;WITH CTE (id,idnew)
AS
(
SELECT 1,2 UNION ALL
SELECT 3,4 UNION ALL
SELECT 2,NULL UNION ALL
SELECT 4,7 UNION ALL
SELECT 6,8 UNION ALL
SELECT 7,NULL
)
SELECT New
FROM CTE
CROSS APPLY ( VALUES (id),(idnew))AS Dt (New)
WHERE dt.New IS NOT NULL
Result
New
---
1
2
3
4
2
4
7
6
8
7

How to group nodes with relationships in SQL

I have the following table which lists related nodes:
;WITH CTE AS
( SELECT *
FROM (VALUES (1,2)
,(2,1)
,(3,4)
,(3,5)
,(4,3)
,(4,5)
,(5,3)
,(5,4)
,(6,NULL)
,(7,NULL)
,(8,9)
,(9,8)
) AS ValuesTable(ID,RelatedID)
)
SELECT *
FROM CTE
How can I assign unique IDS (GUID or integer GroupID) to each group, So, 1 and 2 will be on the same group, 3, 4, 5 on a different group, 6 is alone in it's group and so is 7, and 8 and 9 are one more group?
My answer so far seems very cumbersome:
;WITH CTE AS
( SELECT *
FROM (VALUES (1,2)
,(2,1)
,(3,4)
,(3,5)
,(4,3)
,(4,5)
,(5,3)
,(5,4)
,(6,NULL)
,(7,NULL)
,(8,9)
,(9,8)
) AS ValuesTable(ID,RelatedID)
)
SELECT DENSE_RANK() OVER(ORDER BY CA.IDList) AS GroupID,
ID,
RelatedID
FROM CTE
CROSS APPLY (SELECT STUFF((SELECT ',' + CONVERT(NVARCHAR(255), ID)
FROM CTE AS CTEInner
WHERE CTEInner.ID = CTE.ID
OR CTEInner.ID = CTE.RelatedID
OR CTEInner.RelatedID = CTE.RelatedID
OR CTEInner.RelatedID = CTE.ID
FOR XML PATH(''),TYPE).value('(./text())[1]','NVARCHAR(MAX)'),1,1,'') AS IDList) AS CA
But it provides the correct answer:
GroupID ID RelatedID
1 1 2
1 2 1
2 3 4
2 3 5
2 4 3
2 4 5
2 5 3
2 5 4
3 6 NULL
4 7 NULL
5 8 9
5 9 8
Adding a unique number for each group is not hard but it does require a few steps.
The first step would be to select unique values for the groups - so for instance the group with (1, 2) and (2, 1) will contain only a single record - (1, 2).
The next step is to get rid of the records that creates multiple paths for the same relationship - in this case - (3, 4), (4, 5), (3, 5) - means that 5 is the related to both 3 and 4, but for the recursive cte to work, we only need a single relationship path - so either (3, 4), (4, 5) or (3, 4), (3, 5) but not both.
The next step is to create a recursive cte based on these unique values, so that each group can get it's unique number.
After that, you can select from the original cte joined to the recursive cte and get the unique group numbers:
;WITH CTE AS
( SELECT *
FROM (VALUES (1,2)
,(2,1)
,(3,4)
,(3,5)
,(4,3)
,(4,5)
,(5,3)
,(5,4)
,(6,NULL)
,(7,NULL)
,(8,9)
,(9,8)
) AS ValuesTable(ID,RelatedID)
)
, PreUniqueValues AS
(
SELECT MIN(ID) AS ID,
MAX(RelatedID) As RelatedID
FROM CTE AS B
GROUP BY (ID + ISNULL(RelatedID, 0)) + (ID * ISNULL(RelatedID, 0))
)
, UniqueValues AS
(
SELECT ID, MIN(RelatedID) As RelatedID
FROM PreUniqueValues
GROUP BY ID
)
, Recursive AS
(
SELECT ID, RelatedId, DENSE_RANK() OVER(ORDER BY ID) As GroupID
FROM UniqueValues AS T0
WHERE NOT EXISTS
(
SELECT 1
FROM UniqueValues AS T1
WHERE T1.ID = T0.RelatedID
)
UNION ALL
SELECT UV.ID, UV.RelatedID, GroupID
FROM UniqueValues As UV
JOIN Recursive As Re
ON UV.ID = Re.RelatedId
)
SELECT CTE.ID, CTE.RelatedID, GroupID
FROM CTE
JOIN Recursive
ON CTE.ID = Recursive.ID OR CTE.ID = ISNULL(Recursive.RelatedID, 0)
ORDER BY ID
Results:
ID RelatedID GroupID
1 2 1
2 1 1
4 3 2
4 5 2
5 3 2
5 4 2
6 NULL 3
7 NULL 4
8 9 5
9 8 5
This is a graph-walking problem and you would seem to need recursive CTEs. The logic looks like this:
WITH t AS (
SELECT *
FROM (VALUES (1,2)
,(2,1)
,(3,4)
,(3,5)
,(4,3)
,(4,5)
,(5,3)
,(5,4)
,(6,NULL)
,(7,NULL)
,(8,9)
,(9,8)
) AS ValuesTable(ID,RelatedID)
),
cte as (
select distinct id, id as relatedId, ',' + convert(varchar(max), id) + ',' as relatedIds
from t
union all
select cte.id, t.relatedId, cte.relatedIds + convert(varchar(max), t.relatedId) + ','
from cte join
t
on cte.relatedId = t.id
where cte.relatedId is not null and
cte.relatedIds not like '%,' + convert(varchar(max), t.relatedId) + ',%'
)
SELECT id, min(relatedId) as grp,
dense_rank() over (order by min(relatedId)) as grp_number
FROM cte
GROUP BY id;
Here is a db<>fiddle.

How can I select distinct by one column?

I have a table with the columns below, and I need to get the values if COD is duplicated, get the non NULL on VALUE column. If is not duplicated, it can get a NULL VALUE. Like the example:
I'm using SQL SERVER.
This is what I get:
COD ID VALUE
28 1 NULL
28 2 Supermarket
29 1 NULL
29 2 School
29 3 NULL
30 1 NULL
This is what I want:
COD ID VALUE
28 2 Supermarket
29 2 School
30 1 NULL
What I'm tryin' to do:
;with A as (
(select DISTINCT COD,ID,VALUE from CodId where ID = 2)
UNION
(select DISTINCT COD,ID,NULL from CodId where ID != 2)
)select * from A order by COD
You can try this.
DECLARE #T TABLE (COD INT, ID INT, VALUE VARCHAR(20))
INSERT INTO #T
VALUES(28, 1, NULL),
(28, 2 ,'Supermarket'),
(29, 1 ,NULL),
(29, 2 ,'School'),
(29, 3 ,NULL),
(30, 1 ,NULL)
;WITH CTE AS (
SELECT *, RN= ROW_NUMBER() OVER (PARTITION BY COD ORDER BY VALUE DESC) FROM #T
)
SELECT COD, ID ,VALUE FROM CTE
WHERE RN = 1
Result:
COD ID VALUE
----------- ----------- --------------------
28 2 Supermarket
29 2 School
30 1 NULL
Another option is to use the WITH TIES clause in concert with Row_Number()
Example
Select top 1 with ties *
from YourTable
Order By Row_Number() over (Partition By [COD] order by Value Desc)
Returns
COD ID VALUE
28 2 Supermarket
29 2 School
30 1 NULL
I would use GROUP BY and JOIN. If there is no NOT NULL value for a COD than it should be resolved using the OR in JOIN clause.
SELECT your_table.*
FROM your_table
JOIN (
SELECT COD, MAX(value) value
FROM your_table
GROUP BY COD
) gt ON your_table.COD = gt.COD and (your_table.value = gt.value OR gt.value IS NULL)
If you may have more than one non null value for a COD this will work
drop table MyTable
CREATE TABLE MyTable
(
COD INT,
ID INT,
VALUE VARCHAR(20)
)
INSERT INTO MyTable
VALUES (28,1, NULL),
(28,2,'Supermarket'),
(28,3,'School'),
(29,1,NULL),
(29,2,'School'),
(29,3,NULL),
(30,1,NULL);
WITH Dups AS
(SELECT COD FROM MyTable GROUP BY COD HAVING count (*) > 1 )
SELECT MyTable.COD,MyTable.ID,MyTable.VALUE FROM MyTable
INNER JOIN dups ON MyTable.COD = Dups.COD
WHERE value IS NOT NULL
UNION
SELECT MyTable.COD,MyTable.ID,MyTable.VALUE FROM MyTable
LEFT JOIN dups ON MyTable.COD = Dups.COD
WHERE dups.cod IS NULL

SQL Server - How to query the set of maximum numbers from a list of numbers from top to bottom

Best way to explain this would be through an example. Let's say I have this simple 2 column table:
Id | Score
1 | 10
2 | 5
3 | 20
4 | 15
5 | 20
6 | 25
7 | 30
8 | 30
9 | 10
10 | 40
The query should return the IDs of each item where the max score changed. So, from the top, 10 would be the top score since item 1 has 10 the first time through but then on item 3 it has a score of 20 so it just had a new max score and this continues until the bottom of the table. So eventually, the query will result to:
1, 3, 6, 7, 10
I tried doing a Cursor and loop through the table but I was wondering if there was a much simple way of doing this.
Thanks
Solution (SQL2012+):
SELECT v.MaxScore, MIN(v.Id) AS FirstId
FROM (
SELECT *, MAX(t.Score) OVER(ORDER BY t.Id ASC) AS MaxScore
FROM #Table AS t
) v
GROUP BY v.MaxScore
Demo
one more version,works for versions >= 2008,you can remove apply to make it work for 2005 as well
;with cte
(Id , Score)
as
(
select 1 , 10 union all
select 2 , 5 union all
select 3 , 20 union all
select 4 , 15 union all
select 5 , 20 union all
select 6 , 25 union all
select 7 , 30 union all
select 8 , 30 union all
select 9 , 10 union all
select 10 , 40
)
select min(id)
from
cte c2
cross apply
(select case when score -(select max(score) from cte c1 where c1.id<=c2.id )=0
then 1 else 0 end) b(val)
where val=1
group by Score
Output:
1
3
6
7
10
I think you can just do a MIN on the id with a GROUP BY Score. Like this:
SELECT MIN(Id) FROM table GROUP BY Score
Using LAG function, that returns prev value of score:
DECLARE #Table TABLE(Id int, Score int)
INSERT INTO #Table
VALUES
(1 , 10),
(2 , 10),
(3 , 20),
(4 , 20),
(5 , 20),
(6 , 25),
(7 , 30),
(8 , 30),
(9 , 30),
(10 , 40)
SELECT *
FROM
(
SELECT
*,
LAG(t.Score, 1, NULL) OVER (ORDER BY t.Id) AS PrevScore
FROM #Table AS t
) AS p
WHERE p.Score <> p.PrevScore OR p.PrevScore IS NULL
Try This
declare #scores varchar(max)
select #scores = isnull(#scores+',','')+convert(varchar,min(id))
from #temp group by score
select #scores