SELECT First Group - sql

Problem Definition
I have an SQL query that looks like:
SELECT *
FROM table
WHERE criteria = 1
ORDER BY group;
Result
I get:
group | value | criteria
------------------------
A | 0 | 1
A | 1 | 1
B | 2 | 1
B | 3 | 1
Expected Result
However, I would like to limit the results to only the first group (in this instance, A). ie,
group | value | criteria
------------------------
A | 0 | 1
A | 1 | 1
What I've tried
Group By
SELECT *
FROM table
WHERE criteria = 1
GROUP BY group;
I can aggregate the groups using a GROUP BY clause, but that would give me:
group | value
-------------
A | 0
B | 2
or some aggregate function of EACH group. However, I don't want to aggregate the rows!
Subquery
I can also specify the group by subquery:
SELECT *
FROM table
WHERE criteria = 1 AND
group = (
SELECT group
FROM table
WHERE criteria = 1
ORDER BY group ASC
LIMIT 1
);
This works, but as always, subqueries are messy. Particularly, this one requires specifying my WHERE clause for criteria twice. Surely there must be a cleaner way to do this.

You can try following query:-
SELECT *
FROM table
WHERE criteria = 1
AND group = (SELECT MIN(group) FROM table)
ORDER BY value;

If your database supports the WITH clause, try this. It's similar to using a subquery, but you only need to specify the criteria input once. It's also easier to understand what's going on.
with main_query as (
select *
from table
where criteria = 1
order by group, value
),
with min_group as (
select min(group) from main_query
)
select *
from main_query
where group in (select group from min_group);
-- this where clause should be fast since there will only be 1 record in min_group

Use DENSE_RANK()
DECLARE #yourTbl AS TABLE (
[group] NVARCHAR(50),
value INT,
criteria INT
)
INSERT INTO #yourTbl VALUES ( 'A', 0, 1 )
INSERT INTO #yourTbl VALUES ( 'A', 1, 1 )
INSERT INTO #yourTbl VALUES ( 'B', 2, 1 )
INSERT INTO #yourTbl VALUES ( 'B', 3, 1 )
;WITH cte AS
(
SELECT i.* ,
DENSE_RANK() OVER (ORDER BY i.[group]) AS gn
FROM #yourTbl AS i
WHERE i.criteria = 1
)
SELECT *
FROM cte
WHERE gn = 1
group | value | criteria
------------------------
A | 0 | 1
A | 1 | 1

Related

SQL query for fetching a single column with multiple values

Consider the below table:
Table1
id | status
------------
1 | A
2 | B
3 | C
1 | B
4 | B
5 | C
4 | A
Desired output is 1 and 4 as they are having status as both 'A' and 'B'.
Can we write an query for this? I tried to query it using conditions like 'AND', 'UNION' and 'OR', but it did not return me the desired result.
If you want the ids with more than 1 statuses:
select id
from tablename
group by id
having count(distinct status) > 1
You can use aggregation:
select id
from t
where status in ('A', 'B')
group by id
having count(*) = 2;
If the table allows duplicates, then use count(distinct status) = 2.
Try this one, you can do without using having() as well
select
id
from
(
select
id,
count(distinct status) as cnt
from yourTable
group by
id
) val
where cnt > 1

Two rows with the same id and two different values, getting the second value into another column

I have two rows with the same id but different values. I want a query to get the second value and display it in the first row.
There are only two rows for each productId and 2 different values.
I've tried looking for this for the solution everywhere.
What I have, example:
+-----+-------+
| ID | Value |
+-----+-------+
| 123 | 1 |
| 123 | 2 |
+-----+-------+
What I want
+------+-------+---------+
| ID | Value | Value 1 |
+------+-------+---------+
| 123 | 1 | 2 |
+------+-------+---------+
Not sure whether order matters to you. Here is one way:
SELECT MIN(Value), MAX(Value), ID
FROM Table
GROUP BY ID;
This is a self-join:
SELECT a.ID, a.Value, b.Value
FROM table a
JOIN table b on a.ID = b.ID
and a.Value <> b.Value
You can use a LEFT JOIN instead if there are IDs that only have one value and would be lost by the above JOIN
May be you may try this
DECLARE #T TABLE
(
Id INT,
Val INT
)
INSERT INTO #T
VALUES(123,1),(123,2),
(456,1),(789,1),(789,2)
;WITH CTE
AS
(
SELECT
RN = ROW_NUMBER() OVER(PARTITION BY Id ORDER BY Val),
*
FROM #T
)
SELECT
*
FROM CTE
PIVOT
(
MAX(Val)
FOR
RN IN
(
[1],[2]--Add More Numbers here if there are more values
)
)Q

SQL - Calculate next row based on previous in the same column

I have spent hours trying to solve this with loops, the lag function but it doesn't solve my problem. I have a table where the first row of a particular field is populated, the next row is calculated based on a subtraction of the previous row of data from 2 columns, the next row is then based on the result of this. The example is below of the original table and the result set:
a b a b
502.5 33.85 502.5 33.85
25.46 468.65 25.46
20.83 443.19 20.83
133.07 422.36 133.07
144.65 289.29 144.65
144.65 144.64 144.65
I have tried several different methods with stored procedures and can get the 2nd row result set but I can't get it to continue and calculate the rest of the fields, it's easy in excel but not so in SQL. Any suggestions?
If your RDBMS supports windowed aggregate functions:
Assuming you have an id or some such thing that is determining the order of your rows (as you indicated there is a first).
You can use the max() over() (in this case min() works instead of max() as well) and sum() over() windowed aggregate functions
select
id
, max(a) over (order by id) - (sum(b) over (order by id) - b) as a
, b
from t
rextester demo: http://rextester.com/MGKM17497
returns:
+----+--------+--------+
| id | a | b |
+----+--------+--------+
| 1 | 502,50 | 33,85 |
| 2 | 468,65 | 25,46 |
| 3 | 443,19 | 20,83 |
| 4 | 422,36 | 133,07 |
| 5 | 289,29 | 144,65 |
| 6 | 144,64 | 144,65 |
+----+--------+--------+
In case, as I saw data before editing )
This solution also assumes that you have id column and order depends on this column
with t(id, a, b) as(
select 1, 502.5, 33.85 union all
select 2, 25.46, null union all
select 3, 20.83, null union all
select 4, 133.07, null union all
select 5, 144.65, null union all
select 6, 144.65, null
)
select case when id = 1 then a else b end as a, case when id = 1 then (select b from t order by id offset 0 rows fetch next 1 rows only) else a end as b from (
select id, a, lag((select a from t order by id offset 0 rows fetch next 1 rows only)-s) over(order by id) as b from (
select id, a, sum(case when b is null then a else b end ) over(order by id) s
from t
) tt
) ttt

How to order an already ordered subquery

Creating this table:
CREATE TABLE #Test (id int, name char(10), list int, priority int)
INSERT INTO #Test VALUES (1, 'One', 1, 1)
INSERT INTO #Test VALUES (2, 'Two', 2, 1)
INSERT INTO #Test VALUES (3, 'Three', 3, 2)
INSERT INTO #Test VALUES (4, 'Four', 4, 1)
INSERT INTO #Test VALUES (5, 'THREE', 3, 1)
and ordering it by, list and priority:
SELECT * FROM #Test ORDER BY list, priority
1 | One | 1 | 1
2 | Two | 2 | 1
5 | THREE | 3 | 1
3 | Three | 3 | 2
4 | Four | 4 | 1
However I want to step through rows one by one selecting the top one for each list ordered by priority, and start over when I get to the end.
For example with this simpler table:
1 | One | 1 | 1
2 | Two | 2 | 1
3 | Three | 3 | 1
4 | Four | 4 | 1
and this query:
SELECT TOP 1 * FROM #Test ORDER BY (CASE WHEN list>#PreviousList THEN 1 ELSE 2 END)
If #PreviousList is the list for the previous row I got, then this will select the next row and gracefully jump to the top when I have selected the last row.
But there are rows that will have the same list only ordered by priority - like my first example:
1 | One | 1 | 1
2 | Two | 2 | 1
5 | THREE | 3 | 1
3 | Three | 3 | 2
4 | Four | 4 | 1
Here id=3 should be skipped because id=5 have the same list ordering and a better priority. The only way I can think of doing this is simply by first order the entire list by list and priority, and then run the order by that goes through the rows one by one, like this:
SELECT TOP 1 * FROM (
SELECT * FROM #Test ORDER BY list, priority
) ORDER BY (CASE WHEN list>#PreviousList THEN 1 ELSE 2 END)
But of course I cannot order by an already ordered subquery and get the error:
The ORDER BY clause is invalid in views, inline functions, derived tables,
subqueries, and common table expressions, unless TOP or FOR XML is also
specified.
Are there any ways and can get past this problem or get the query down to a single query and order by?
Another possible solution is to use a subquery to select the min priority grouped by list and join it back to the table for the rest of the details
SELECT T2.*
FROM (SELECT MIN(priority) as priority, list
FROM #Test
GROUP BY list) AS T1
INNER JOIN #Test T2 ON T1.list = T2.list AND T1.priority = T2.priority
ORDER BY T1.list, T1.priority
I want to step through rows one by one selecting the top one for each
list ordered by priority, and start over when I get to the end.
You can use the built in ROW_NUMBER function that is designed for these scenarios with OVER(PARTITION BY name ORDER BY priority) to do this directly:
WITH CTE
AS
(
SELECT *, ROW_NUMBER() OVER(PARTITION BY name ORDER BY priority) AS RN
FROM #Test
)
SELECT *
FROM CTE
WHERE RN = 1;
Live DEMO
The ranking number rn generated by ROW_NUMBER() OVER(PARTITION BY name ORDER BY priority) will rank each group of rows that has the same name ordered by priority then when you filtered by WHERE rn = 1 it will remove all the duplicate with the same name and left only the first priority.
SELECT TOP 1 * FROM (
SELECT * FROM #Test
) ORDER BY (CASE WHEN list>#PreviousList THEN 1 ELSE 2 END)
Try this, because Order By is not allowed in CTE.
Perhaps I am missing the requirement that makes this harder than I realize, but what about a nice simple join to select highest priority for the list. To scale, performance would require an index on list.
select t.*
, ttop.id as firstid
from #test t
JOIN #test ttop on ttop.id = (SELECT TOP 1 ID
FROM #TEST tbest
WHERE t.list = tbest.list order by priority)
and ttop.id = t.id -- this does the trick!

SQL Server: Select only one row of rows that has the same ID on some coulmn

I have a table that has 3 columns:
- ID
- FROM
- TO
And i have data like that
-----------------------
ID | FROM | TO
1 | 2 | 1
2 | 5 | 1
3 | 7 | 1
4 | 2 | 1
5 | 2 | 1
6 | 9 | 1
7 | 3 | 1
8 | 4 | 1
9 | 5 | 1
I would like to create a query that selects all rows where TO = 1 and i don't want to display rows that was previously retrieved, for example i have multiple rows where FROM = 2 and TO = 1, i just need to retrieve that row only once.
My table doesn't really look like this but i am giving a small example because my aim is to collect all FROM numbers but without any redundancy.
use distinct keyword
select distinct m.from,m.to from mytable as m;
Use DISTINCT
SELECT DISTINCT from,to FROM yourTable WHERE to = 1
You just have to group by the columns you want to display:
select [from] from mytable group by [from]
If you want to see how many froms you have all you have to do is:
select [from], count(*) from mytable group by [from]
You could use distinct but it would slower than group by but require more memory.
Please read here if you want an explanation on the difference between group by and distinct:
Huge performance difference when using group by vs distinct
Not sure what exactly you meant select distinct [FROM] from TableName where [TO] = 1
OR
may be you need single row for every distinct [FROM] value for given [TO] ?
;with cte as (
select ID, [FROM], [TO],
rn = row_number() over (partition by [FROM] order by ID)
from TableName
where [TO] = 1
)
select ID, [FROM], [TO]
from cte
where rn=1