Select query select based on a priority - sql

Someone please change my title to better reflect what I am trying to ask.
I have a table like
Table (id, value, value_type, data)
ID is NOT unique. There is no unique key.
value_type has two possible values, let's say A and B.
Type B is better than A, but often not available.
For each id if any records with value_type B exists, I want all the records with that id and value_type B.
If no record for that id with value_Type B exists I want all records with that id and value_type A.
Notice that if B exists for that id I don't want records with type A.
I currently do this with a series of temp tables. Is there a single select statement (sub queries OK) that can do the job?
Thanks so much!
Additional details:
SQL Server 2005

RANK, rather than ROW_NUMBER, because you want ties (those with the same B value) to have the same rank value:
WITH summary AS (
SELECT t.*,
RANK() OVER (PARTITION BY t.id
ORDER BY t.value_type DESC) AS rank
FROM TABLE t
WHERE t.value_type IN ('A', 'B'))
SELECT s.id,
s.value,
s.value_type,
s.data
FROM summary s
WHERE s.rank = 1
Non CTE version:
SELECT s.id,
s.value,
s.value_type,
s.data
FROM (SELECT t.*,
RANK() OVER (PARTITION BY t.id
ORDER BY t.value_type DESC) AS rank
FROM TABLE t
WHERE t.value_type IN ('A', 'B')) s
WHERE s.rank = 1
WITH test AS (
SELECT 1 AS id, 'B' AS value_type
UNION ALL
SELECT 1, 'B'
UNION ALL
SELECT 1, 'A'
UNION ALL
SELECT 2, 'A'
UNION ALL
SELECT 2, 'A'),
summary AS (
SELECT t.*,
RANK() OVER (PARTITION BY t.id
ORDER BY t.value_type DESC) AS rank
FROM test t)
SELECT *
FROM summary
WHERE rank = 1
I get:
id value_type rank
----------------------
1 B 1
1 B 1
2 A 1
2 A 1

SELECT *
FROM table
WHERE value_type = B
UNION ALL
SELECT *
FROM table
WHERE ID not in (SELECT distinct id
FROM table
WHERE value_type = B)

The shortest query to do the job I can think of:
SELECT TOP 1 WITH TIES *
FROM #test
ORDER BY Rank() OVER (PARTITION BY id ORDER BY value_type DESC)
This is about 50% worse on CPU as OMG Ponies' and Christoperous 5000's solutions, but the same number of reads. It's the extra sort that is making it take more CPU.
The best-performing original query I've come up with so far is:
SELECT *
FROM #test
WHERE value_type = 'B'
UNION ALL
SELECT *
FROM #test T1
WHERE NOT EXISTS (
SELECT *
FROM #test T2
WHERE
T1.id = T2.id
AND T2.value_type = 'B'
)
This consistently beats all the others presented on CPU by about 1/3rd (the others are about 50% more) but has 3x the number of reads. The duration on this query is often 2/3rds the time of all the others. I consider it a good contender.
Indexes and data types could change everything.

declare #test as table(
id int , value [nvarchar](255),value_type [nvarchar](255),data int)
INSERT INTO #test
SELECT 1, 'X', 'A',1 UNION
SELECT 1, 'X', 'A',2 UNION
SELECT 1, 'X', 'A',3 UNION
SELECT 1, 'X', 'A',4 UNION
SELECT 2, 'X', 'A',5 UNION
SELECT 2, 'X', 'B',6 UNION
SELECT 2, 'X', 'B',7 UNION
SELECT 2, 'X', 'A',8 UNION
SELECT 2, 'X', 'A',9
SELECT * FROM #test x
INNER JOIN
(SELECT id, MAX(value_type) as value_type FROM
#test GROUP BY id) as y
ON x.id = y.id AND x.value_type = y.value_type

Try this (MSSQL).
Select id, value_typeB, null
from myTable
where value_typeB is not null
Union All
Select id, null, value_typeA
from myTable
where value_typeB is null and value_typeA is not null

Perhaps something like this:
select * from mytable
where id in (select distinct id where value_type = "B")
union
select * from mytable
where id in (select distinct id where value_type = "A"
and id not in (select distinct id where value_type = "B"))

This uses a union, combining all records of value B with all records that have only A values:
SELECT *
FROM mainTable
WHERE value_type = B
GROUP BY value_type UNION SELECT *
FROM mainTable
WHERE value_type = A
AND id NOT IN(SELECT *
FROM mainTable
WHERE value_type = B);

Related

Oracle SQL - Count based on a condition to include distinct rows with zero matches

Is there a "better" way to refactor the query below that returns the number occurrences of a particular value (e.g. 'A') for each distinct id? The challenge seems to be keeping id = 2 in the result set even though the count is zero (id = 2 is never related to 'A'). It has a common table expression, NVL function, in-line view, distinct, and left join. Is all of that really needed to get this job done? (Oracle 19c)
create table T (id, val) as
select 1, 'A' from dual
union all select 1, 'B' from dual
union all select 1, 'A' from dual
union all select 2, 'B' from dual
union all select 2, 'B' from dual
union all select 3, 'A' from dual
;
with C as (select id, val, count(*) cnt from T where val = 'A' group by id, val)
select D.id, nvl(C.cnt, 0) cnt_with_zero from (select distinct id from T) D left join C on D.id = C.id
order by id
;
ID CNT_WITH_ZERO
---------- -------------
1 2
2 0
3 1
A simple way is conditional aggregation:
select id,
sum(case when val = 'A' then 1 else 0 end) as num_As
from t
group by id;
If you have another table with one row per id, you I would recommend:
select i.id,
(select count(*) from t where t.id = i.id and t.val = 'A') as num_As
from ids i;

How to compare column in one table with array from another table in BigQuery?

Just continue from the answer for my previous question.
I want to get all values from table b (in rows) if there is any difference between values in arrays from table a by same ids
WITH a as (SELECT 1 as id, ['123', 'abc', '456', 'qaz', 'uqw'] as value
UNION ALL SELECT 2, ['123', 'wer', 'thg', '10', '200']
UNION ALL SELECT 3, ['200']
UNION ALL SELECT 4, null
UNION ALL SELECT 5, ['140']),
b as (SELECT 1 as id, '123' as value
UNION ALL SELECT 1, 'abc'
UNION ALL SELECT 1, '456'
UNION ALL SELECT 1, 'qaz'
UNION ALL SELECT 1, 'uqw'
UNION ALL SELECT 2, '123'
UNION ALL SELECT 2, 'wer'
UNION ALL SELECT 2, '10'
UNION ALL SELECT 3, null
UNION ALL SELECT 4, 'wer'
UNION ALL SELECT 4, '234'
UNION ALL SELECT 5, '140'
UNION ALL SELECT 5, '121'
)
SELECT * EXCEPT(flag)
FROM (
SELECT b.*, COUNTIF(b.value IS NULL) OVER(PARTITION BY id) flag
FROM a LEFT JOIN a.value
FULL OUTER JOIN b
USING(id, value)
)
WHERE flag > 0
AND NOT id IS NULL
It works well for all ids except 5.
In my case I need to return all values if there is any difference.
In example array with id 5 from table a has only one value is '140' while there are two rows with values by id 5 from table b. So in this case all values by id 5 from table b also must appear in expected output
How need to modify this query to get what I want?
UPDATED
Seems like it works for me. But I can not be sure for 100%
SELECT * EXCEPT(flag)
FROM (
SELECT b.*, COUNTIF((b.value IS NULL AND a.value IS NOT NULL) OR (b.value IS NOT NULL AND a.value IS NULL)) OVER(PARTITION BY id) flag
FROM a LEFT JOIN a.value
FULL OUTER JOIN b
USING(id, value)
)
WHERE flag > 0
AND NOT id IS NULL
#standardSQL
SELECT *
FROM table_b
WHERE id IN (
SELECT id FROM table_a a
JOIN table_b b USING(id)
GROUP BY id
HAVING STRING_AGG(IFNULL(b.value, 'NULL') ORDER BY b.value) !=
IFNULL(ANY_VALUE((SELECT STRING_AGG(IFNULL(value, 'NULL') ORDER BY value) FROM a.value)), 'NULL')
)

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>.

how to select first non-duplicate data in sql

I have table(Id, Name, Type) in sql.
Id, Name, Type:
1, AA, 1
2, BB, 2
3, CC, 4
4, DD, 2
5, EE, 3
6, FF, 3
I want select the first non-duplicate data. Result:
Id, Name, Type:
1, AA, 1
2, BB, 2
3, CC, 4
6, FF, 3
I use DISTINCT and GROUP BY, but not working, I have select all row not select Type with DISTINCT or GROUP BY.
select DISTINCT Type
from tbltest
I like CTE's and ROW_NUMBER since it allows to change it easily to delete the duplicates.
Presuming that you want to remove duplicate Types and first means according to the ID:
WITH CTE AS(
SELECT Id, Name, Type,
RN = ROW_NUMBER() OVER ( PARTITION BY Type ORDER BY ID )
FROM dbo.Table1
)
SELECT Id, Name, Type FROM CTE WHERE RN = 1
You can do this in several ways. My preference is row_number():
select id, name, type
from (select t.*, row_number() over (partition by type order by id) as seqnum
from tbltest t
) t
where seqnum = 1;
EDIT:
Performance of the above should be reasonable. However, the following might be faster with an index on type, id:
selct id, name, type
from tbltest t
where not exists (select 1 from tbltest t2 where t2.type = t.type and t2.id < t.id);
That is, select the rows that have no lower id for the same type.

Interesting SQL issue

I have a SQL problem I am trying to digest. I am using SQL Server 2005.
In a table I have data as such:
ID Type
1 A
2 A
3 A
3 B
4 B
I need to find all of the IDs that have a Type of both A and B.
Use the INTERSECT operator:
SELECT DISTINCT ID FROM [Table] WHERE Type = 'A'
INTERSECT
SELECT DISTINCT ID FROM [Table] WHERE Type = 'B'
select distinct a.id
from table a
join table b on a.id=b.id
where a.type='A'
and b.type='B';
With a semi-join (no sorting, only index seek on B):
select a.id from table a
where a.type = 'A'
and exists (select * from table b where a.id = b.id and b.type = 'B')
If you want to abstract the problem a little bit and find cases where rows with the same id contain different values in the type column, you can check for <> like this:
DECLARE #TestTable TABLE (thisid int, thisval varchar(1))
INSERT INTO #TestTable VALUES (1, 'A')
INSERT INTO #TestTable VALUES (2, 'A')
INSERT INTO #TestTable VALUES (3, 'A')
INSERT INTO #TestTable VALUES (3, 'B')
INSERT INTO #TestTable VALUES (4, 'B')
SELECT DISTINCT thisid
FROM #TestTable a
WHERE EXISTS
( SELECT *
FROM #TestTable b
WHERE a.thisid=b.thisid AND a.thisval<>b.thisval)
-- www.caliberwebgroup.com
This returns:
3
select id, count(type = 'A') as a_count, count(type = 'B') as b_count
from your_table
group by 1
having a_count > 0 and b_count > 0;
At least, this works in sane SQL environments. Dunno if it works in yours.
I was not looking at other answers, but still posting. lol
SELECT distinct t1.ID
FROM table1 AS t1
WHERE exists
(select t2.ID from table1 t2 where t2.type="A" and t2.ID=t1.ID)
and exists
(select t3.ID from table1 t3 where t3.type="B" and t3.ID=t1.ID);
SELECT Id FROM tableX AS x, tableX AS y
WHERE x.id = y.id AND x.type = 'A' AND y.type = 'B'
This is very simple
Declare #t table([ID] INT, [Type] VARCHAR(2))
INSERT INTO #t SELECT 1, 'A' UNION ALL SELECT 2,'A' UNION ALL SELECT 3,'A'
UNION ALL SELECT 3,'B' UNION ALL SELECT 4,'B' UNION ALL SELECT 5,'A' UNION ALL SELECT 5,'A'
;WITH CTE AS
(
SELECT Rn = Row_NUMBER() OVER(PARTITION BY [ID],[TYPE] ORDER BY [ID])
,*
FROM #t
)
SELECT ID
FROM CTE
WHERE Rn =1 AND ([Type]='A' or [Type]='B')
GROUP BY [ID]
HAVING (COUNT([ID])>1)
Output:
id
3
this would help if there are "unknown" amounts of types and you want to find all IDs which have all of types
select id from yourtable group by id having count(*)=(select count(distinct type) from yourtable)
select id
from idtypedata
group by id
having
sum(
case type
when 'A' then 1
when 'B' then 2
-- when 'C' then 4
-- when 'D' then 8
end
) & 1 = 1
And
sum(
case type
when 'A' then 1
when 'B' then 2
-- when 'C' then 4
-- when 'D' then 8
end
) & 2 = 2