SQL, conditional column values? - sql

One of the columns in my table is "DataSource". Datasource can have two values, lets say "A" or "B". I would like to always take the row when Datasource = "A", however, if there isn't an entry for A I would like to take "B".
How does one do this in SQL Server?
EDIT:
So for a partucular product (Product ID) one Product ID may have two rows, each containing a different DataSource, wheres another ProductID may only have one DataSource:
{ProductID DataSource}
{1 A},
{1 B},
{2 B}
Here I would wish to select the top and bottom row

A few options following your edit...
SELECT
*
FROM
table
WHERE
DataSource = 'A'
OR DataSource = 'B' AND NOT EXISTS (SELECT * FROM table AS lookup WHERE ProductID = table.ProductID AND DataSource = 'A')
SELECT
*
FROM
table
INNER JOIN
(SELECT ProductID, MAX(DataSource) AS DataSource FROM table) AS lookup
ON lookup.ProductID = table.ProductID
AND lookup.DataSource = table.DataSource
WITH
sequenced AS
(
SELECT
*,
ROW_NUMBER() OVER (PARTITION BY ProductID ORDER BY DataSource) AS sequence_id
FROM
table
)
SELECT
*
FROM
sequenced
WHERE
sequence_id = 1

select ProductID from product where DataSource= 'A'
UNION
select id from DataSource where product not in (select ProductID from product where DataSource = 'A')

SELECT top 1 *
from YourTable
ORDER BY DataSource ASC
Else
if the real values are NOT 'A' or 'B', which are just placeholders of #A and #B variables
SELECT top 1 *
from YourTable
ORDER BY CASE DataSource WHEN #A THEN 0 ELSE 1 END ASC
or
if there more variances than AB
SELECT top 1 *
from YourTable
ORDER BY CASE DataSource WHEN #A THEN 0 WHEN #B THEN 1 ELSE 2 END ASC
OR
If you need to group by ProductId
select * from (
SELECT *,
ROW_NUMBER OVER(PARTITION BY ProductId ORDER BY CASE DataSource WHEN #A THEN 0 WHEN #B THEN 1 ELSE 2 END ASC) ordinal
from YourTable
) t
WHERE t.Ordinal = 1

Based on your sample data, this works:
SELECT ProductID, MIN(DataSource)
FROM #tab
GROUP BY ProductID;
And here is my test data:
declare #tab table (ProductID int, DataSource char(1))
insert into #tab values (1, 'A');
insert into #tab values (1, 'B');
insert into #tab values (2, 'B');
If there are more columns in the table than the two you show then:
SELECT T1.*
FROM #tab T1
JOIN
(
SELECT ProductID, MIN(DataSource) AS DataSource
FROM #tab
GROUP BY ProductID
) T2 ON T1.ProductID = T2.ProductID AND T1.DataSource = T2.DataSource

ok, if I understood you requirement correctly then this would work
select TOP 1 * from yourtable where DATASOURCE = ISNULL(SELECT MAX(DATASOURCE) WHERE DATASOURCE="A","B")
The idea here is simple, you want to change DATASOURCE condition based on whether you have any rows of your first preference ("A") are available or not, if yes, then apply "A" condition or apply "B" condition

Related

How to Select one Value for each row after Joining of 2 Tables

I have 2 tables, the first one has 10 distinct values:
,
each GlobalPnID has many values on the second table, I want to join 2 tables and select one random value of PortionKey of the second table that match the condition and move to the next GlobalPnID
SELECT TOP 10 gpnp.PortionKey, tt.GlobalPnID
from #TempTable tt
LEFT JOIN [dbo].[GlobalPartNumberPortions] gpnp ON gpnp.GlobalPnId = tt.GlobalPnID
-- tt is the first table
-- gpnp is the second
SELECT TT.GlobalPnID,X.PortionKey
FROM #TempTable AS TT
CROSS APPLY
(
SELECT TOP 1 R.PortionKey
FROM [dbo].[GlobalPartNumberPortions] AS R
WHERE R.GlobalPnId=TT.GlobalPnID
ORDER BY R.PortionID
)X
You could use Row_Number with a CTE and set the criteria you want, for example:
DECLARE #TempTable TABLE
(
globalpnid INT
)
DECLARE #GlobalPartNumberPortions TABLE
(
portionid INT,
portionkey NVARCHAR(10),
globalpnid INT
)
INSERT INTO #TempTable
(globalpnid)
VALUES (1),(2),(3),(4)
INSERT INTO #GlobalPartNumberPortions
(portionid,
portionkey,
globalpnid)
VALUES (1,'ABC',1),
(2,'XYZ',1),
(3,'AZZ',2),
(4,'QWE',3),
(5,'TYU',4);
WITH cteportion
AS (SELECT portionkey,
globalpnid,
rn = Row_number()
OVER (
partition BY globalpnid
ORDER BY RAND(CHECKSUM(NEWID()))))
FROM #GlobalPartNumberPortions)
SELECT gpnp.portionkey,
tt.globalpnid
FROM #TempTable tt
LEFT JOIN cteportion gpnp
ON tt.globalpnid = gpnp.globalpnid
AND gpnp.rn = 1
This will partition the second table by the globalpnid ordering on ORDER BY RAND(CHECKSUM(NEWID()))) and you can then use this in the join gpnp.rn = 1. In the example I've included, you'll see that GlobalPnID = 1 will alternate between ABC and XYZ.
Edit: as suggested by #Thorsten Kettner in the comment, you can order by RAND(CHECKSUM(NEWID())))

Merge three tables in Select query by rule 3, 2, 1 records from each table

Merge three tables in a Select query by rule 3, 2, 1 records from each table as follows:
TableA: ID, FieldA, FieldB, FieldC,....
TableB: ID, FieldA, FieldB, FieldC,....
TableC: ID, FieldA, FieldB, FieldC,....
ID : auto number in each table
FieldA will be unique in all three tables.
I am looking for a Select query to merge three tables as follows:
TOP three records from TableA sorted by ID
TOP two records from TableB sorted by ID
TOP 1 record from TableC sorted by ID
Repeat this until select all records from all three tables.
If some table has fewer records or does not meet the criteria, ignore that and continue with others.
My attempt:
I did it totally through programming way, like cursors and If conditions inside a SQL Server stored procedure.
It makes delay.
This requires a formula that takes row numbers from each table and transforms it into a series of integers that skips the desired values.
In the query below, I am adding some CTE for the sake of shortening the formula. The real magic is in the UNION. Also, I am adding an additional field for your control. Feel free to get rid of it.
WITH A_Aux as (
SELECT 'A' As FromTable, ROW_NUMBER() OVER (ORDER BY ID) AS RowNum, TableA.*
FROM TableA
), B_Aux AS (
SELECT 'B' As FromTable, ROW_NUMBER() OVER (ORDER BY ID) AS RowNum, TableB.*
FROM TableB
), C_Aux AS (
SELECT 'C' As FromTable, ROW_NUMBER() OVER (Order BY ID) AS RowNum, TableC.*
FROM TableC
)
SELECT *
FROM (
SELECT RowNum+3*FLOOR((RowNum-1)/3) As ColumnForOrder, A_Aux.* FROM A_Aux
UNION ALL
SELECT 3+RowNum+4*FLOOR((RowNum-1)/2), B_Aux.* FROM B_Aux
UNION ALL
SELECT 6*RowNum, C_Aux.* FROM C_Aux
) T
ORDER BY ColumnForOrder
PS: note the pattern Offset + RowNum + (6-N) * Floor((RowNum-1)/N) to group N records together (it of course simplifies a lot for TableC).
PPS: I don't have a SQL server at hand to test it. Let me know if there is a syntax error.
You may try this..
GO
select * into #temp1 from (select * from table1) as t1
select * into #temp2 from (select * from table2) as t2
select * into #temp3 from (select * from table3) as t3
select * into #final from (select col1, col2, col3 from #temp1 where 1=0) as tb
declare #i int
set #i=1
while( (select COUNT(*) from #temp1)>#i)
Begin
;with ct1 as (
select ROW_NUMBER() over (order by id) as Slno, * from #temp1
),ct2 as (
select ROW_NUMBER() over (order by id) as Slno, * from #temp2
),ct3 as (
select ROW_NUMBER() over (order by id) as Slno, * from #temp3
),cfinal as (
select top 3 * from #temp1
union all
select top 2 * from #temp2
union all
select top 1 * from #temp3
)
insert into #final ( col1 , col2, col3 )
select col1, col2, col3 from cfinal
delete from #temp1 where id in (select top 3 ID from #temp1)
delete from #temp2 where id in (select top 2 ID from #temp2)
delete from #temp3 where id in (select top 1 ID from #temp3)
set #i = #i+1
End
Select * from #final
Drop table #temp1
Drop table #temp2
Drop table #temp3
GO
First create temp table for all 3 tables with each insert delete the inserted record and this will result you the desired result, if nothing is missing from my side.
Please see to this if this works.
There is not a lot of information to go with here, but I assume you can use UNION to combine multiple statements.
SELECT * TableA ORDER BY ID DESC OFFSET 3 ROWS
UNION
SELECT * TableB ORDER BY ID DESC OFFSET 2 ROWS
UNION
SELECT * TableC ORDER BY ID DESC OFFSET 1 ROWS
Execute and see if this works.
/AF
From my understanding, I create three temp tables as ta, tb, tc.
select * into #ta from (
select 'A' a
union all
select 'A' a
union all
select 'A' a
union all
select 'A' a
union all
select 'A' a
union all
select 'A' a
union all
select 'A' a
) a
select * into #tb from (
select 'B' b
union all
select 'B'
union all
select 'B'
union all
select 'B'
union all
select 'B'
) b
select * into #tc from (
select 'C' c
union all
select 'C'
union all
select 'C'
union all
select 'C'
union all
select 'C'
) c
If tables match you tables, then the output looks like A,A,A,B,B,C,A,A,A,B,B,C,A,B,C,C,C
T-SQL
declare #TAC int = (select count (*) from #ta) -- Table A Count = 7
declare #TBC int = (select count (*) from #tb) -- Table B Count = 5
declare #TAR int = #TAC % 3 -- Table A Reminder = 1
declare #TBR int = #TBC % 2 -- Table B Reminder = 1
declare #TAQ int = (#TAC - #TAR) / 3 -- Table A Quotient = (7 - 1) / 3 = 2, is will passed on NTILE
-- So we gonna split as two group (111), (222)
declare #TBQ int = (#TBC - #TBR) / 2 -- Table B Quotient = (5 - 1) / 2 = 2, is will passed on NTILE
-- So we gonna split as two group (11), (22)
select * from (
select *, NTILE (#TAQ) over ( order by a) FirstOrder, 1 SecondOrder from (
select top (#TAC - #TAR) * from #ta order by a
) ta -- 6 rows are obtained out of 7.
union all
select *, #TAQ + 1, 1 from (
select top (#TAR) * from #ta order by a desc
) ta -- Remaining one row is obtained. Order by desc is must
-- Here FirstOrder is next value of previous value.
union all
select *, NTILE (#TBQ) over ( order by b), 2 from (
select top (#TBC - #TBR) * from #tb order by b
) tb
union all
select *, #TBQ + 1, 2 from (
select top (#TBR) * from #tb order by b desc
) tb
union all
select *, ROW_NUMBER () over (order by c), 3 from #tc
) abc order by FirstOrder, SecondOrder
Let me explain the T-SQL:
Before that, FYR: NTILE and Row Number
Get the count.
Find the Quotient which will pass to NTILE function.
Order by the NTILE value and static.
Note:
I am using SQL Server 2017.
If T-SQL works fine, then you need to change the column in order by <yourcolumn>.

Get two random records (different in one attribute) from table

Very simple table as an example but no idea how to achieve this:
Example: Table1
ColumnA ColumnB
1 A
1 B
2 C
For two random records: I know I could do like
Select top 2 *
From Table1
order by NewID()
But now I would like to select two random records out but cannot be such a combination that has both '1' for column A, which means the result cannot accept '1 A' together with '1 B', the rest are fine.
Any ideas? Thanks in advance
DROP TABLE #T
CREATE TABLE #T(ID INT
,Vals CHAR(2)
)
INSERT INTO #T VALUES
(1,'A')
,(1,'B')
,(2,'A')
,(2,'C')
,(3,'D')
,(4,'E')
,(5,'E')
SELECT TOP 2
ID,
Vals
FROM
(
SELECT
ID
,VALS
,ROW_NUMBER() OVER(PARTITION BY ID ORDER BY NEWID()) Rnk
FROM
#T) T
WHERE
Rnk = 1
order by NewID()
Here's a way to do it, but it can get expensive if your table is very large:
;With Random As
(
Select *,
Row_Number() Over (Partition By ColumnA Order By NewId()) As RN
From Table1
)
Select Top 2 ColumnA, ColumnB
From Random
Where RN = 1
Order By NewId()

SQL group by if values are close

Class| Value
-------------
A | 1
A | 2
A | 3
A | 10
B | 1
I am not sure whether it is practical to achieve this using SQL.
If the difference of values are less than 5 (or x), then group the rows (of course with the same Class)
Expected result
Class| ValueMin | ValueMax
---------------------------
A | 1 | 3
A | 10 | 10
B | 1 | 1
For fixed intervals, we can easily use "GROUP BY". But now the grouping is based on nearby row's value. So if the values are consecutive or very close, they will be "chained together".
Thank you very much
Assuming MSSQL
You are trying to group things by gaps between values. The easiest way to do this is to use the lag() function to find the gaps:
select class, min(value) as minvalue, max(value) as maxvalue
from (select class, value,
sum(IsNewGroup) over (partition by class order by value) as GroupId
from (select class, value,
(case when lag(value) over (partition by class order by value) > value - 5
then 0 else 1
end) as IsNewGroup
from t
) t
) t
group by class, groupid;
Note that this assumes SQL Server 2012 for the use of lag() and cumulative sum.
Update:
*This answer is incorrect*
Assuming the table you gave is called sd_test, the following query will give you the output you are expecting
In short, we need a way to find what was the value on the previous row. This is determined using a join on row ids. Then create a group to see if the difference is less than 5. and then it is just regular 'Group By'.
If your version of SQL Server supports windowing functions with partitioning the code would be much more readable.
SELECT
A.CLASS
,MIN(A.VALUE) AS MIN_VALUE
,MAX(A.VALUE) AS MAX_VALUE
FROM
(SELECT
ROW_NUMBER()OVER(PARTITION BY CLASS ORDER BY VALUE) AS ROW_ID
,CLASS
,VALUE
FROM SD_TEST) AS A
LEFT JOIN
(SELECT
ROW_NUMBER()OVER(PARTITION BY CLASS ORDER BY VALUE) AS ROW_ID
,CLASS
,VALUE
FROM SD_TEST) AS B
ON A.CLASS = B.CLASS AND A.ROW_ID=B.ROW_ID+1
GROUP BY A.CLASS,CASE WHEN ABS(COALESCE(B.VALUE,0)-A.VALUE)<5 THEN 1 ELSE 0 END
ORDER BY A.CLASS,cASE WHEN ABS(COALESCE(B.VALUE,0)-A.VALUE)<5 THEN 1 ELSE 0 END DESC
ps: I think the above is ANSI compliant. So should run in most SQL variants. Someone can correct me if it is not.
These give the correct result, using the fact that you must have the same number of group starts as ends and that they will both be in ascending order.
if object_id('tempdb..#temp') is not null drop table #temp
create table #temp (class char(1),Value int);
insert into #temp values ('A',1);
insert into #temp values ('A',2);
insert into #temp values ('A',3);
insert into #temp values ('A',10);
insert into #temp values ('A',13);
insert into #temp values ('A',14);
insert into #temp values ('b',7);
insert into #temp values ('b',8);
insert into #temp values ('b',9);
insert into #temp values ('b',12);
insert into #temp values ('b',22);
insert into #temp values ('b',26);
insert into #temp values ('b',67);
Method 1 Using CTE and row offsets
with cte as
(select distinct class,value,ROW_NUMBER() over ( partition by class order by value ) as R from #temp),
cte2 as
(
select
c1.class
,c1.value
,c2.R as PreviousRec
,c3.r as NextRec
from
cte c1
left join cte c2 on (c1.class = c2.class and c1.R= c2.R+1 and c1.Value < c2.value + 5)
left join cte c3 on (c1.class = c3.class and c1.R= c3.R-1 and c1.Value > c3.value - 5)
)
select
Starts.Class
,Starts.Value as StartValue
,Ends.Value as EndValue
from
(
select
class
,value
,row_number() over ( partition by class order by value ) as GroupNumber
from cte2
where PreviousRec is null) as Starts join
(
select
class
,value
,row_number() over ( partition by class order by value ) as GroupNumber
from cte2
where NextRec is null) as Ends on starts.class=ends.class and starts.GroupNumber = ends.GroupNumber
** Method 2 Inline views using not exists **
select
Starts.Class
,Starts.Value as StartValue
,Ends.Value as EndValue
from
(
select class,Value ,row_number() over ( partition by class order by value ) as GroupNumber
from
(select distinct class,value from #temp) as T
where not exists (select 1 from #temp where class=t.class and Value < t.Value and Value > t.Value -5 )
) Starts join
(
select class,Value ,row_number() over ( partition by class order by value ) as GroupNumber
from
(select distinct class,value from #temp) as T
where not exists (select 1 from #temp where class=t.class and Value > t.Value and Value < t.Value +5 )
) ends on starts.class=ends.class and starts.GroupNumber = ends.GroupNumber
In both methods I use a select distinct to begin because if you have a dulpicate entry at a group start or end things go awry without it.
Here is one way of getting the information you are after:
SELECT Under5.Class,
(
SELECT MIN(m2.Value)
FROM MyTable AS m2
WHERE m2.Value < 5
AND m2.Class = Under5.Class
) AS ValueMin,
(
SELECT MAX(m3.Value)
FROM MyTable AS m3
WHERE m3.Value < 5
AND m3.Class = Under5.Class
) AS ValueMax
FROM
(
SELECT DISTINCT m1.Class
FROM MyTable AS m1
WHERE m1.Value < 5
) AS Under5
UNION
SELECT Over4.Class,
(
SELECT MIN(m4.Value)
FROM MyTable AS m4
WHERE m4.Value >= 5
AND m4.Class = Over4.Class
) AS ValueMin,
(
SELECT Max(m5.Value)
FROM MyTable AS m5
WHERE m5.Value >= 5
AND m5.Class = Over4.Class
) AS ValueMax
FROM
(
SELECT DISTINCT m6.Class
FROM MyTable AS m6
WHERE m6.Value >= 5
) AS Over4

Overwrite ID values using UPDATE statement from SELECT in another table SQL Server 2008

I have two tables that have exactly 20 rows in them. I want to overwrite Table2's ID values with the ID values from Table1 so that I can do tests with JOIN queries. How would I go about overwriting the values?
I have found posts instructing how to do an UPDATE using a SELECT statement, however it requires the data to be joined on a column, in my case no columns match.
UPDATE Table2
SET Table2.ID = Table1.ID
FROM Table1
The query above overwrites all ID columns in Table2 with the value of the first ID column in Table1.
For fun (yes I have a warped sense of humour for a Friday night!), here is a query that does it! - I've declare the tables and the top and only used 6 rows in each table, but you'll get the idea:
--Setup test data
declare #table1 table (ID int, Name varchar(10))
declare #table2 table (ID int, Name varchar(10))
insert #table1
select ID = 1, Name = 'Item1'
union select ID = 2, Name = 'Item2'
union select ID = 3, Name = 'Item3'
union select ID = 4, Name = 'Item4'
union select ID = 5, Name = 'Item5'
union select ID = 6, Name = 'Item6'
insert #table2
select ID = 11, Name = 'Item11'
union select ID = 12, Name = 'Item12'
union select ID = 13, Name = 'Item13'
union select ID = 14, Name = 'Item14'
union select ID = 15, Name = 'Item15'
union select ID = 16, Name = 'Item16'
--Do the update
update t1
set
ID = t2.ID
from
#table1 t1 --Assign a row number to each row of table 1
cross apply (select rownum = COUNT(1) from #table1 sub where sub.ID <= t1.ID) x1,
#table2 t2 --Assign a row number to each row of table 2
cross apply (select rownum = COUNT(1) from #table2 sub where sub.ID <= t2.ID) x2
where x1.rownum = x2.rownum --Match the row numbers
Update:
Alternative Update command based on suggestion by #sllev:
update t1
set
ID = t2.ID
from
(select id, rownum = ROW_NUMBER() OVER(order by ID) from #table1) t1
join (select id, rownum = ROW_NUMBER() over (order by id) from #table2) t2
on t1.rownum = t2.rownum
And another solution, this time using CTE's. Table #First gets the id's from table #Second:
CREATE TABLE #First ( ID INT NOT NULL )
INSERT INTO #First VALUES (1), (2), (3), (4), (5)
CREATE TABLE #Second ( ID INT NOT NULL )
INSERT INTO #Second VALUES (6), (7), (8), (9), (10)
GO
WITH first AS (SELECT ID, ROW_NUMBER() OVER (ORDER BY ID) AS RowNum FROM #First)
, second AS (SELECT ID, ROW_NUMBER() OVER (ORDER BY ID) AS RowNum FROM #Second)
UPDATE #First
SET ID = s.ID
FROM second s
JOIN first f ON s.RowNum = f.RowNum
WHERE #First.ID = f.ID
SELECT * FROM #First
DROP TABLE #First;
DROP TABLE #Second;
Since you only have 20 rows in them, I would manually perform the update by typing in your changes. Especially since the records do not correlate to each other as they typically should for an update with a select.
As stated in the comments, if this is a one-off for testing the easiest option for just 20 rows would be to open Table2 in SSMS and type the ID values in.