Select item that is different in each of group by a column - sql

I have this sample table
+--------+-------------+
| DBName | Description |
+--------+-------------+
| A | Car |
| A | Boat |
| B | Car |
| B | Plane |
| C | Car |
| C | Boat |
| C | Plane |
+--------+-------------+
I want to take only Description that is not exist on every DBName and show what DBName that don't have the Description.
The Result from the query that I want
+--------+-------------+
| DBName | Description |
+--------+-------------+
| A | Plane |
| B | Boat |
+--------+-------------+
Keep in mind it will be more than just A,B,C on DBName.

Interesting issue. Here are a couple of options for solving. There's discussions around these techniques here, along with a few suggestions of other routes for handling scenarios such as this.
SQL Fiddle Example
select DBName, Description
from (
select DBName, Description
from (select distinct DBName from demo) a
cross join (select distinct Description from demo) b
) c
except
select DbName, Description from demo
This solution works by fetching every possible combination (via cross join of distinct values for each column), then excluding all those which already exist via the except clause.
SQL Fiddle Example
select [each].DBName, missing.Description
from (select distinct DBName from demo) [each]
cross join (select distinct Description from demo) [missing]
where not exists
(
select top 1 1
from demo [exists]
where [exists].DbName = [each].DBName
and [exists].Description = [missing].Description
)
This solution is the same as the above, only instead of the except cluase we use where not exists to remove existing combos.

Ideally you should have a master list of data. In case you do not you should deriv3 it from the data and then put checks against them like below:
SQL Fiddle Example
select
masterlistDbname.Dbname,
masterlistDesc.Description
from
(
select distinct Description from yourtable
) masterlistDesc
cross join
(
select distinct Dbname from yourtable
) masterlistDbname
left join
yourtable t1
on t1.Dbname = masterlistDbname.Dbname
and t1.Description = masterlistDesc.Description
where t1.Dbname is NULL

use NOT EXISTS
SELECT *
FROM yourtable t
WHERE NOT EXISTS
(
SELECT *
FROM yourtable x
WHERE x.Description = t.Description
AND x.DBName <> t.DBName
)

you should throw little more Sample data.
Try this,
create table #test(DBName varchar(50),Descriptions varchar(50) )
insert into #test VALUES
('A','Car')
,('A','Boat')
,('B','Car')
,('B','Plane')
,('C','Car')
,('C','Boat')
,('C','Plane')
;
WITH CTE
AS (
SELECT *
,ROW_NUMBER() OVER (
ORDER BY (
SELECT NULL
)
) rn
,ROW_NUMBER() OVER (
PARTITION BY DBName ORDER BY (
SELECT NULL
)
) rn1
FROM #test
)
SELECT t.DBName
,t1.Descriptions
FROM cte t
CROSS APPLY (
SELECT TOP 1 Descriptions
FROM cte t1
WHERE t1.rn > t.rn
AND t.Descriptions <> t1.Descriptions
AND t.dbname <> t1.dbname
ORDER BY t1.rn
) t1
WHERE t.rn1 = 1
drop table #test

Related

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

Select one column data in different columns in SQL Server

I have to match two tables, table1 and table2, for one row of table1 I have two or more rows in table 2 and I want to display them in one row.
Now I have the following:
Select
a.[ID], b.[Description]
From
table1 a, table2 b
Where
a.[ID] = b.[ID];
Output:
[ID] | [Description]
-----+--------------
1 | Fee
1 | Domestic Fee
2 | Fee
2 | International Fee
I want to get the following result
[ID] | [Description1] | [Description2]
-----+----------------+---------------
1 | Fee | Domestic Fee
2 | Fee | International Fee
Thank you in advance :)
If you have multiple descriptions then you can use Pivot as below:
Select * from (
Select *, RowN = Concat('Description', Row_Number() over (partition by Id order by Id)) from #description ) a
pivot (max([Description]) for RowN in ([Description1],[Description2])) p
Output as below:
+----+--------------+-------------------+
| Id | Description1 | Description2 |
+----+--------------+-------------------+
| 1 | Fee | Domestic Fee |
| 2 | Fee | International Fee |
+----+--------------+-------------------+
This is my proposal with cross apply operator
data prepartaion:
create table tst_1 (id int, dsc nvarchar(30))
insert into tst_1 (id, dsc)
values (1,'Fee'),(1,'Domestic Fee'),(2,'Fee'),(2,'International Fee')
Next simple select with jon show data that you looking for:
select t1.id, t1.dsc, x.dsc
from tst_1 t1
cross apply ( select row_number() over (order by id) as lp
,id
,dsc
from tst_1 )x
where x.id = t1.id and x.dsc <> t1.dsc
and lp%2 = 0
tst_1 can be a view based on select ... from you question.
Select
a.[ID],a.[Description], b.[Description]
From
table1 a left outer join table2 b
on
a.[ID] = b.[ID];``
try this:
select ID, DESCRIPTION1, DESCRIPTION2
from
(
Select a.[ID]
,b.[Description]
,'DESCRIPTION' + CAST(ROW_NUMBER() OVER(PARTITION BY a.ID ORDER BY a.ID) AS VARCHAR(255))AS RN
from table1 a
JOIN table2 b ON a.[ID] = b.[ID]
) d
pivot
(
max([Description])
for RN in (DESCRIPTION1,DESCRIPTION2)
) piv;

T-SQL - Get a list of all As which have the same set of Bs

I'm struggling with a tricky SQL query that I'm trying to write. Have a look at the following table:
+---+---+
| A | B |
+---+---+
| 1 | 2 |
| 1 | 3 |
| 2 | 2 |
| 2 | 3 |
| 2 | 4 |
| 3 | 2 |
| 3 | 3 |
| 4 | 2 |
| 4 | 3 |
| 4 | 4 |
+---+---+
Now, from this table, I essentially want a list of all As which have the exact same set of Bs and give each set an incrementing ID.
Hence, the output set for the above would be:
+---+----+
| A | ID |
+---+----+
| 1 | 1 |
| 3 | 1 |
| 2 | 2 |
| 4 | 2 |
+---+----+
Thanks.
Edit: If it helps, I have a list of all distinct values of B that are possible in another table.
Edit: Thank you so much for all the innovative answers. Was able to learn a lot indeed.
Here is mathematical trick to solve your tricky select:
with pow as(select *, b * power(10, row_number()
over(partition by a order by b)) as rn from t)
select a, dense_rank() over( order by sum(rn)) as rn
from pow
group by a
order by rn, a
Fiddle http://sqlfiddle.com/#!3/6b98d/11
This of course will work only for limited distinct count as you will get overflow. Here is more general solution with strings:
select a,
dense_rank() over(order by (select '.' + cast(b as varchar(max))
from t t2 where t1.a = t2.a
order by b
for xml path(''))) rn
from t t1
group by a
order by rn, a
Fiddle http://sqlfiddle.com/#!3/6b98d/29
Something like this:
select a, dense_rank() over (order by g) as id_b
from (
select a,
(select b from MyTable s where s.a=a.a order by b FOR XML PATH('')) g
from MyTable a
group by a
) a
order by id_b,a
Or maybe using a CTE (I avoid them when possible)
Sql Fiddle
As a side note, this is the output of the inner query using the sample data in the question:
a g
1 <b>2</b><b>3</b>
2 <b>2</b><b>3</b><b>4</b>
3 <b>2</b><b>3</b>
4 <b>2</b><b>3</b><b>4</b>
Here's a long winded approach, by finding sets with the same elements (using EXCEPT bidirectionally to eliminate, and just done a half diagonal cartesian product), then pairing equal sets up, stamping each pair with a ROW_NUMBER(), before unpivoting the pairs of A's into to your final output where the equivalent sets are projected as rows which have the same id.
WITH joinedSets AS
(
SELECT t1.A as t1A, t2.A AS t2A
FROM MyTable t1
INNER JOIN MyTable t2
ON t1.B = t2.B
AND t1.A < t2.A
),
equalSets AS
(
SELECT js.t1A, js.t2A, ROW_NUMBER() OVER (ORDER BY js.t1A) AS Id
FROM joinedSets js
GROUP BY js.t1A, js.t2A
HAVING NOT EXISTS ((SELECT mt.B FROM MyTable mt WHERE mt.A = js.t1A)
EXCEPT (SELECT mt.B FROM MyTable mt WHERE mt.A = js.t2A))
AND NOT EXISTS ((SELECT mt.B FROM MyTable mt WHERE mt.A = js.t2A)
EXCEPT (SELECT mt.B FROM MyTable mt WHERE mt.A = js.t1A))
)
SELECT A, Id
FROM equalSets
UNPIVOT
(
A
FOR ACol in (t1A, t2A)
) unp;
SqlFiddle here
As it stands, this solution will only work with pairs of sets, not triples etc. A general NTuple type solution is probably possible (but beyond my brain right now).
Here is a very simple, fast, but approximate solution.
It is possible that CHECKSUM_AGG returns the same checksum for different sets of B.
DECLARE #T TABLE (A int, B int);
INSERT INTO #T VALUES
(1, 2),(1, 3),(2, 2),(2, 3),(2, 4),(3, 2),(3, 3),(4, 2),(4, 3),(4, 4);
SELECT
A
,CHECKSUM_AGG(B) AS CheckSumB
,ROW_NUMBER() OVER (PARTITION BY CHECKSUM_AGG(B) ORDER BY A) AS GroupNumber
FROM #T
GROUP BY A
ORDER BY A, GroupNumber;
Result set
A CheckSumB GroupNumber
-----------------------------
1 1 1
2 5 1
3 1 2
4 5 2
For exact solution group by A and concatenate all B values into a long (binary) string using either FOR XML, CLR, or T-SQL function. Then you can partition ROW_NUMBER by that concatenated string to assign numbers to the groups. As shown in other answers.
EDIT
I am changing the code, but it will get bigger now, took help from
Concatenate many rows into a single text string? for concatinating strings
Select [A],
Left(M.[C],Len(M.[C])-1) As [D] into #tempSomeTable
From
(
Select distinct T2.[A],
(
Select Cast(T1.[B] as VARCHAR) + ',' AS [text()]
From sometable T1
Where T1.[A] = T2.[A]
ORDER BY T1.[A]
For XML PATH ('')
) [C]
From sometable T2
)M
SELECT t.A, DENSE_RANK() OVER(ORDER BY t.[D]) [ID] FROM
#tempSomeTable t
inner join
(SELECT [D] FROM(
SELECT [D], COUNT([A]) [D_A] from
#tempSomeTable t
GROUP BY [D] )P where [C_A]>1)t1 on t1.[D]=t.[D]
Here is an exact, rather than approximate, solution. It uses nothing more advanced than INNER JOIN and GROUP BY (and, of course, the DENSE_RANK() to get the ID you want).
It is also general, in that it allows for B values to be repeated within an A group.
SELECT A,
DENSE_RANK() OVER (ORDER BY MIN_EQUIVALENT_A) AS ID
FROM (
SELECT MATCHES.A1 AS A,
MIN(MATCHES.A2) AS MIN_EQUIVALENT_A
FROM (
SELECT T1.A AS A1,
T2.A AS A2,
COUNT(*) AS NUM_B_VALS_MATCHED
FROM (
SELECT A,
B,
COUNT(*) AS B_VAL_FREQ
FROM MyTable
GROUP BY A,
B
) AS T1
INNER JOIN
(
SELECT A,
B,
COUNT(*) AS B_VAL_FREQ
FROM MyTable
GROUP BY A,
B
) AS T2
ON T1.B = T2.B
AND T1.B_VAL_FREQ = T2.B_VAL_FREQ
GROUP BY T1.A,
T2.A
) AS MATCHES
INNER JOIN
(
SELECT A,
COUNT(DISTINCT B) AS NUM_B_VALS_TOTAL
FROM MyTable
GROUP BY A
) AS CHECK_TOTALS_A1
ON MATCHES.A1 = CHECK_TOTALS_A1.A
AND MATCHES.NUM_B_VALS_MATCHED
= CHECK_TOTALS_A1.NUM_B_VALS_TOTAL
INNER JOIN
(
SELECT A,
COUNT(DISTINCT B) AS NUM_B_VALS_TOTAL
FROM MyTable
GROUP BY A
) AS CHECK_TOTALS_A2
ON MATCHES.A2 = CHECK_TOTALS_A2.A
AND MATCHES.NUM_B_VALS_MATCHED
= CHECK_TOTALS_A2.NUM_B_VALS_TOTAL
GROUP BY MATCHES.A1
) AS EQUIVALENCE_TABLE
ORDER BY 2,1
;

Show elements of where clause that are not present in table

I search a table based on an ID column in my where clause. I have a list of IDs that may or may not be present in this table. A simple query will give me the IDs which exist in that table (if any). Is there a way to also return ID's that were not found ?
Table --
ID
1GH
2BN
3ER
SELECT *
FROM Table
WHERE ID IN (big list 9FG, 1GH, 3UI etc)
--If ID's in above list are not in table, then show those ids.
Desired output -
9FG, 3UI were not found in the table
If I understand correctly what you need you can do it this way
SELECT q.id,
CASE WHEN t.id IS NULL THEN 'no' ELSE 'yes' END id_exists
FROM
(
SELECT '9FG' id UNION ALL
SELECT '1GH' UNION ALL
SELECT '3UI'
) q LEFT JOIN table1 t
ON q.id = t.id
Output:
| ID | ID_EXISTS |
|-----|-----------|
| 9FG | no |
| 1GH | yes |
| 3UI | no |
or if you just need a list of non-existent ids
SELECT q.id
FROM
(
SELECT '9FG' id UNION ALL
SELECT '1GH' UNION ALL
SELECT '3UI'
) q LEFT JOIN table1 t
ON q.id = t.id
WHERE t.id IS NULL
Output:
| ID |
|-----|
| 9FG |
| 3UI |
The trick is to use an OUTER JOIN instead of WHERE condition to filter data from your table and be able to see the mismatches.
Here is SQLFiddle demo
To search you can use
SELECT *
From Mytable
where id in (
select id from (values (1), (2), (3)) as SearchedIds(Id) )
and the opposite to find unamtched:
SELECT id from (values (1), (2), (3)) as SearchedIds(Id)
WHERE id not in (SELECT id From MyTable)
The syntax
Values(...) asSearchedIds(id)
is supported in Sql2008, for Sql2005 you have to do
( SELECT 1 as Id UNION ALL SELECT 2 UNION ALL ...etc ) as SearchedIds
Note: you can rewrite those queries with JOINS (INNER and LEFT)
Maybe something like:
SELECT id FROM my_table WHERE id NOT IN (val1, val2, val3)

MSSQL: Only last entry in GROUP BY (with id)

Following / copying computhomas's question, but adding some twists...
I have the following table in MSSQL2008
id | business_key | result | date
1 | 1 | 0 | 9
2 | 1 | 1 | 8
3 | 2 | 1 | 7
4 | 3 | n | 6
5 | 4 | 1 | 5
6 | 4 | 0 | 4
And now i want to group based on the business_key returning the complete entry with the newest date.
So my expected result is:
id | business_key | result | date
1 | 1 | 0 | 9
3 | 2 | 1 | 7
4 | 3 | n | 6
5 | 4 | 1 | 5
I also bet that there is a way to achieve that, i just can't find / see / think of it at the moment.
edit: sorry about this, I actually meant something else from original question I did. I felt like editing this might be better than accepting a solution and making another question. my original problem was that I am not filtering by id.
SELECT t.*
FROM
(
SELECT *, ROW_NUMBER() OVER
(
PARTITION BY [business_key]
ORDER BY [date] DESC
) AS [RowNum]
FROM yourTable
) AS t
WHERE t.[RowNum] = 1
SELECT
*
FROM
mytable
WHERE
ID IN (SELECT MAX(ID) FROM mytable GROUP BY business_key)
SELECT
MAX(T1.id) AS [id],
T1.business_key,
T1.result
FROM
dbo.My_Table T1
LEFT OUTER JOIN dbo.My_Table T2 ON
T2.business_key = T1.business_key AND
T2.id > T1.id
WHERE
T2.id IS NULL
GROUP BY T1.business_key,
T1.result
ORDER BY MAX(T1.id)
Edited based on clarifications
SELECT M1.*
FROM My_Table M1
INNER JOIN
(
SELECT [business_key], MAX([date]) as MaxDate
FROM My_Table
GROUP BY [business_key]
) M2 ON M1.business_key = M2.business_key AND M1.[date] = M2.MaxDate
ORDER BY M1.[id]
Assuming the combination of business_key & date is unique then....
Working example (3rd time is a charm):
declare #src as table(id int, business_key int,result int,[date] int)
insert into #src
SELECT 1,1,0,9
UNION SELECT 2,1,1,8
UNION SELECT 3,2,1,7
UNION SELECT 4,3,1,6
UNION SELECT 5,4,1,5
UNION SELECT 6,4,0,4
;with bkdate(business_key,[date])
AS
(
select business_key,MAX([date])
from #src
group by business_key
)
select src.* from #src src
inner join bkdate
ON src.[date] = bkdate.date
and src.business_key = bkdate.business_key
order by id
How about (edited after question change):
with latestdate as (
select business_key, maxdate=max(date)
from the_table
group by business_key
), latest as (
select ID = max(id)
from the_table
inner join latestdate
on the_table.business_key=latestdate.business_key
and the_table.date=latestdate.maxdate
group by the_table.business_key
)
select the_table.*
from the_table
inner join latest
on latest.id=the_table.id