How to not display an item in select query? - sql

I feel a little stupid asking this because I feel like this is very easy, but for some reason I'm not able to update a query to not select a specific item based on two criteria.
Let's say I have data like this:
ID Name Variant Count1
110 Bob Type1 0
110 Bob Type2 1
120 John Type1 1
So as you can see we have two BOB rows with same ID but different variant (type1 and type2). I want to be able to only see one of the Bob's.
Desired result:
110 Bob Type2
120 John Type1
So what I've been doing is something like
Select ID, Name, Variant, sum(count1) from tbl1
where (id not in (110) and Variant <> 'type1')
Group by Id,name,variant
Please don't use COUNT as a criteria, because in my example it just so happens that Count=0 for the row that I don't want to see. It can vary.
I have many rows where I can have multiple instances of the same id with a variety of different VARIANTS. I'm looking to exclude certain instances of ID based on Variant value
UPDATE:
It has nothing to do with latest variant, it has to do with a specific variant. So I'm just looking to basically be able to use a clause where i used the ID and VARIANT, in order to remove that particular row.

Aggregating (grouping) the data like you're doing is one way to do it, although the where condition is a little overkill. If all you want to do is see the unique combinations of ID and Name, then another approach is just to use the "distinct" statement.
select distinct Id, Name
from tbl1
If you always want to see data from a specific Variant then just include that condition in your where clause and you don't need to worry about using distinct or aggregates.
select *
from tbl1
where Variant = 'Type 1'
If you always want to see the record associated with the latest Variant, then you can use a window function to do so.
select a.Id, a.Name, a.Variant
from
(
select *, row_number() over (partition by Id order by Variant desc) as RowRank
from tbl1
) a
where RowRank = 1
;
If there is not a predictable pattern for exclusion then you will have to maintain an exclusion list. It's not ideal but if you want to maintain this in the SQL itself then you could have a query like the one below.
select *
from tbl1
-- Define rows to exlcude
where not (Id = 110 and Variant = 'Type 1') -- Your example
and not (Id = 110 and Variant = 'Type 3') -- Theoretical example
;
A better solution would be to create an exclusion reference table to maintain all exclusions within. Then you could simply negative join to that table to retrieve your desired results.

Have you considered using an exclusion table where you can place the ID and Variant combinations that you want to exclude? ( I just used temp tables for this example, you can always use user tables so your exclusion table will always be available)
Here is an example of what I mean based on your example:
if object_id('tempdb..#temp') is not null
drop table #temp
create table #temp (
ID int,
Name varchar(20),
Variant varchar(20),
Count1 int
)
if object_id('tempdb..#tempExclude') is not null
drop table #tempExclude
create table #tempExclude (
ID int,
Variant varchar(20)
)
insert into #temp values
(110,'Bob','Type1',0),
(110,'Bob','Type2',1),
(120,'John','Type1',1),
(120,'John','Type2',1),
(120,'John','Type2',1),
(120,'John','Type2',1),
(120,'John','Type3',1)
insert into #tempExclude values (110,'Type1')
select
t.ID,
t.Name
,t.Variant
,sum(t.Count1) as TotalCount
from
#temp t
left join
#tempExclude te
on t.ID = te.ID
and t.Variant = te.Variant
where
te.id is null
group by
t.ID,
t.Name
,t.Variant
Here are the results:

I think the logic you want is something like:
Select ID, Name, Variant, sum(count1)
from tbl1
where not (id = 110 and variant = 'type1')
Group by Id, name, variant;
For the second condition, just keep adding:
where not (id = 110 and variant = 'type1') and
not (id = 314 and variant = 'popsicle')
You can also express this using a list of exclusions:
select t.ID, Name, t.Variant, sum(t.count1)
from tbl1 t left join
(values (111, 'type1'),
(314, 'popsicle')
) v(id, excluded_variant)
on t.id = v.id and
t.variant = v.excluded_variant
where v.id is not null -- doesn't match an exclusion criterion
group by Id, name, variant;

Related

Finding the id's which include multiple criteria in long format

Suppose I have a table like this,
id
tagId
1
1
1
2
1
5
2
1
2
5
3
2
3
4
3
5
3
8
I want to select id's where tagId includes both 2 and 5. For this fake data set, It should return 1 and 3.
I tried,
select id from [dbo].[mytable] where tagId IN(2,5)
But it takes 2 and 5 into account respectively. I also did not want to keep my table in wide format since tagId is dynamic. It can reach any number of columns. I also considered filtering with two different queries to find (somehow) the intersection. However since I may search more than two values inside the tagId in real life, it sounds inefficient to me.
I am sure that this is something faced before when tag searching. What do you suggest? Changing table format?
One option is to count the number of distinct tagIds (from the ones you're looking for) each id has:
SELECT id
FROM [dbo].[mytable]
WHERE tagId IN (2,5)
GROUP BY id
HAVING COUNT(DISTINCT tagId) = 2
This is actually a Relational Division With Remainder question.
First, you have to place your input into proper table format. I suggest you use a Table Valued Parameter if executing from client code. You can also use a temp table or table variable.
DECLARE #ids TABLE (tagId int PRIMARY KEY);
INSERT #ids VALUES (2), (5);
There are a number of different solutions to this type of question.
Classic double-negative EXISTS
SELECT DISTINCT
mt.Id
FROM mytable mt
WHERE NOT EXISTS (SELECT 1
FROM #ids i
WHERE NOT EXISTS (SELECT 1
FROM mytable mt2
WHERE mt2.id = mt.id
AND mt2.tagId = i.tagId)
);
This is not usually efficient though
Comparing to the total number of IDs to match
SELECT mt.id
FROM mytable mt
JOIN #ids i ON i.tagId = mt.tagId
GROUP BY mt.id
HAVING COUNT(*) = (SELECT COUNT(*) FROM #ids);
This is much more efficient. You can also do this using a window function, it may be more or less efficient, YMMV.
SELECT mt.Id
FROM mytable mt
JOIN (
SELECT *,
total = COUNT(*) OVER ()
FROM #ids i
) i ON i.tagId = mt.tagId
GROUP BY mt.id
HAVING COUNT(*) = MIN(i.total);
Another solution involves cross-joining everything and checking how many matches there are using conditional aggregation
SELECT mt.id
FROM (
SELECT
mt.id,
mt.tagId,
matches = SUM(CASE WHEN i.tagId = mt.tagId THEN 1 END),
total = COUNT(*)
FROM mytable mt
CROSS JOIN #ids i
GROUP BY
mt.id,
mt.tagId
) mt
GROUP BY mt.id
HAVING SUM(matches) = MIN(total)
AND MIN(matches) >= 0;
db<>fiddle
There are other solutions also, see High Performance Relational Division in SQL Server

Rotate rows into columns with column names not coming from the row

I've looked at some answers but none of them seem to be applicable to me.
Basically I have this result set:
RowNo | Id | OrderNo |
1 101 1
2 101 10
I just want to convert this to
| Id | OrderNo_0 | OrderNo_1 |
101 1 10
I know I should probably use PIVOT. But the syntax is just not clear to me.
The order numbers are always two. To make things clearer
And if you want to use PIVOT then the following works with the data provided:
declare #Orders table (RowNo int, Id int, OrderNo int)
insert into #Orders (RowNo, Id, OrderNo)
select 1, 101, 1 union all select 2, 101, 10
select Id, [1] OrderNo_0, [2] OrderNo_1
from (
select RowNo, Id, OrderNo
from #Orders
) SourceTable
pivot (
sum(OrderNo)
for RowNo in ([1],[2])
) as PivotTable
Reference: https://learn.microsoft.com/en-us/sql/t-sql/queries/from-using-pivot-and-unpivot?view=sql-server-2017
Note: To build each row in the result set the pivot function is grouping by the columns not begin pivoted. Therefore you need an aggregate function on the column that is being pivoted. You won't notice it in this instance because you have unique rows to start with - but if you had multiple rows with the RowNo and Id you would then find the aggregation comes into play.
As you say there are only ever two order numbers per ID, you could join the results set to itself on the ID column. For the purposes of the example below, I'm assuming your results set is merely selecting from a single Orders table, but it should be easy enough to replace this with your existing query.
SELECT o1.ID, o1.OrderNo AS [OrderNo_0], o2.OrderNo AS [OrderNo_1]
FROM Orders AS o1
INNER JOIN Orders AS o2
ON (o1.ID = o2.ID AND o1.OrderNo <> o2.OrderNo)
From your sample data, simplest you can try to use min and MAX function.
SELECT Id,min(OrderNo) OrderNo_0,MAX(OrderNo) OrderNo_1
FROM T
GROUP BY Id

Nested query that requires the first result to be returned

I have 2 tables as such
Table ErrorCodes:
type_code desc
01 Error101
02 Error99
03 Error120
Table ErrorXML:
row_index typeCode
1 87
2 02
3 01
The output should be the description(column desc) of the first matched type_code between the 2 tables
Expected output : Error99
I have gotten so far.
select isnull(descript, 'unknown') as DESCRIPTION
from (select top 1 a.stmt_cd as descript
from ErrorCodes a, ErrorXML b
where a.type_cd = b.typecode
order by b.row_index)
But this query doesn't return the string UNKNOWN when there is no common typecode (join condition) between the 2 tables. In this case, im getting null.
How can I resolve this?
This is an interesting question. I believe the following can be an intuitive and beautiful solution (I used desc_ as column name rather than desc which is a reserved word):
select (select desc_ from ErrorCodes x where x.type_code = a.typeCode) desc_
from ErrorXML a
where (select desc_ from ErrorCodes x where x.type_code = a.typeCode) is not null
order by row_index
limit 1;
If you also need to handle the case if query returns no row then for MySQL, following syntax should suffice. For other databases you can use similar encapsulation with isnull, nvl, etc:
select ifnull((select (select desc_ from ErrorCodes x where x.type_code = a.typeCode) desc_ from ErrorXML a where (select desc_ from ErrorCodes x where x.type_code = a.typeCode) is not null order by row_index limit 1), 'UNKNOWN');
To test I used following scripts and seems to work properly:
create database if not exists stackoverflow;
use stackoverflow;
drop table if exists ErrorCodes;
create table ErrorCodes
(
type_code varchar(2),
desc_ varchar(10)
);
insert into ErrorCodes(type_code, desc_) values
('01', 'Error101'),
('02', 'Error99'),
('03', 'Error120');
drop table if exists ErrorXML;
create table ErrorXML
(
row_index integer,
typeCode varchar(2)
);
insert into ErrorXML(row_index, typeCode) values
('1', '87'),
('2', '02'),
('3', '01');
Final-1 quote: While generating your tables try to use same column names as much as possible. I.e. I'd suggest ErrorXML to use type_code rather than typeCode.
Final quote: I choose to use lower letters in SQL since capital letters should be used while emphasizing an important point. I also suggest that style.
What about this: Do a subquery to bring back the first row_index for each type_code.
Do a LEFT OUTER Join on the ErrorCodes table so that you get NULLs as well.
SELECT
ISNULL(ErrorCodes.desc,'unknown') AS description
ErrorXML.row_index
FROM ErrorCodes
LEFT OUTER JOIN (
SELECT type_code, MIN(row_index) AS row_index
FROM ErrorXML
GROUP BY type_code
) AS ErrorXML ON ErrorCodes.type_code = ErrorXML .type_code

Count(*) with 0 for boolean field

Let's say I have a boolean field in a database table and I want to get a tally of how many are 1 and how many are 0. Currently I am doing:
SELECT 'yes' AS result, COUNT( * ) AS num
FROM `table`
WHERE field = 1
UNION
SELECT 'no' AS result, COUNT( * ) AS num
FROM `table`
WHERE field = 0;
Is there an easier way to get the result so that even if there are no false values I will still get:
----------
|yes | 3 |
|no | 0 |
----------
One way would be to outer join onto a lookup table. So, create a lookup table that maps field values to names:
create table field_lookup (
field int,
description varchar(3)
)
and populate it
insert into field_lookup values (0, 'no')
insert into field_lookup values (1, 'yes')
now the next bit depends on your SQL vendor, the following has some Sybase (or SQL Server) specific bits (the outer join syntax and isnull to convert nulls to zero):
select description, isnull(num,0)
from (select field, count(*) num from `table` group by field) d, field_lookup fl
where d.field =* fl.field
you are on the right track, but the first answer will not be correct. Here is a solution that will give you Yes and No even if there is no "No" in the table:
SELECT 'Yes', (SELECT COUNT(*) FROM Tablename WHERE Field <> 0)
UNION ALL
SELECT 'No', (SELECT COUNT(*) FROM tablename WHERE Field = 0)
Be aware that I've checked Yes as <> 0 because some front end systems that uses SQL Server as backend server, uses -1 and 1 as yes.
Regards
Arild
This will result in two columns:
SELECT SUM(field) AS yes, COUNT(*) - SUM(field) AS no FROM table
Because there aren't any existing values for false, if you want to see a summary value for it - you need to LEFT JOIN to a table or derived table/inline view that does. Assuming there's no TYPE_CODES table to lookup the values, use:
SELECT x.desc_value AS result,
COALESCE(COUNT(t.field), 0) AS num
FROM (SELECT 1 AS value, 'yes' AS desc_value
UNION ALL
SELECT 2, 'no') x
LEFT JOIN TABLE t ON t.field = x.value
GROUP BY x.desc_value
SELECT COUNT(*) count, field FROM table GROUP BY field;
Not exactly same output format, but it's the same data you get back.
If one of them has none, you won't get that rows back, but that should be easy enough to check for in your code.

How do I select rows in table (A) sharing the same foreign key (itemId) where multiple rows in table have the values in table B

Sorry about the title, not sure how to describe without example. I trying to implement faceting of attributes in SQL Server 2008.
I have 2 tables. itemAttributes and facetParameters
Assume the following values in itemAttributes
id, itemId, name, value
---------------------------------------
1 1 keywords example1
2 1 keywords example2
3 2 color red
4 2 keywords example1
5 2 keywords example2
6 3 keywords example2
7 3 color red
8 3 color blue
Assume the following values in facetParameters
name value
----------------------
keywords example1
color red
I need to retrieve the (optional: distinct) itemIds where a given itemId has rows that contain all the values in facetParameters.
e.g. given the rows in facetParameters the query should return itemId 2. At the moment I would be using this in a CTE however given that they do not support a number of features I can work around this if there is no solution that works inside a CTE.
I have done a fair bit of sql over the years but this one has really stumped me and the shame is I keep thinking the answer must be simple.
You could join both tables, and use a having clause to ensure that all items match:
select ia.itemid
from #itemAttributes ia
inner join #facetParameters fp
on ia.name = fp.name
and ia.value = fp.value
group by ia.itemid
having count(distinct fp.name) =
(
select count(*) from #facetParameters
)
The count in the having clause assumes that the name uniquely identifies a row in the facetParameters table. If it doesn't, add an identity column to facetParameters, and use count(distinct id_column) instead of count(distinct fp.name).
Here's code to create the data set in the question:
declare #itemAttributes table (id int, itemId int,
name varchar(max), value varchar(max))
insert into #itemAttributes
select 1,1,'keywords','example1'
union all select 2,1,'keywords','example2'
union all select 3,2,'color','red'
union all select 4,2,'keywords','example1'
union all select 5,2,'keywords','example2'
union all select 6,3,'keywords','example2'
union all select 7,3,'color','red'
union all select 8,3,'color','blue'
declare #facetParameters table (name varchar(max), value varchar(max))
insert into #facetParameters
select 'keywords','example1'
union all select 'color','red'