I'm trying to write a SQL query that will return a user and a specific value based on if at least one record in the column matches the requirement.
I've thrown together this table below to give an example of my problem. I want to get a value of 'TRUE' back for every user who has any rows colored red.
User
Color
1
red
1
blue
1
blue
2
red
3
blue
3
blue
The output should look like this
User
Color
1
TRUE
2
TRUE
3
FALSE
Let me know if there's any elaboration needed. Any help is greatly appreciated.
You have one table, right? Since True is greater than False in a Max() case expression, then this works.
create table my_data (
userid integer,
color varchar(20)
);
insert into my_data values
(1, 'red'),
(1, 'blue'),
(1, 'blue'),
(2, 'red'),
(3, 'blue'),
(3, 'blue'),
(4, null),
(5, null),
(5, 'red');
select userid,
max(case when color = 'red' then 'True' else 'False' end) as has_red
from my_data
group by userid
userid
has_red
1
True
2
True
3
False
4
False
5
True
This is how you can do it in SQL Server:
select u.User, isnull(max(case when c.Color = 'Red' then 'True' else 'False' end), 'False') Color
from users u
left join Color c
on c.user = u.user
group by u.User
order by u.User
This will get you all users in usercolor table.
SELECT DISTINCT User
FROM UserColor
But this won't work -- what if there is a user that isn't in the usercolor table? Then you need to select from a table that has all of them -- like this
SELECT DISTINCT User
FROM UserTable
Then you join that table to user color and when you have a red show it.
SELECT
U.User,
CASE WHEN UserColor.User IS NOT NULL THEN 'True' ELSE 'False' END as COLOR
FROM (
SELECT DISTINCT User
FROM UserTable
) AS U
LEFT JOIN UserColor ON U.User = UserColor.User AND UserColor.Color = 'Red'
Related
Here is a simple example of what I'm trying to achieve:
Essentially I want to take a data set like this:
raw data
Into this:
output
I'm thinking some sort of CASE statement to check for when there are multiple colors for any single fruit, THEN 'Multiple' ELSE "Color", but not sure how to build that logic to check for multiple different values for a single item. Thanks in advance for the help!
declare #table table
( fruit varchar(250),
color varchar(250)
)
insert into #table (fruit, color)
values
('banana', 'yellow')
, ('banana', 'yellow')
, ('apple', 'red')
, ('apple', 'green')
select distinct t.fruit, case when d.color = 1 then t.color else 'multiple' end as color from #table t
left join
(
select fruit, count(distinct color) as color from #table
group by fruit
) d on d.fruit = t.fruit
That one works. Using count distinct to get the amount of colors, then a distinct on the outer select.
select fruit
,case when count(distinct Color) > 1 then 'Multiple' else max(color) end as Color
from t
group by fruit
fruit
Color
Apple
Multiple
Banana
Yellow
Cherry
Multiple
Orange
Orange
Fiddle
Let's say we have below table of below schema.
create table result
(
id int,
task_id int,
test_name string,
test_result string
);
And dataset populated on this table looks like this.
insert into result
values (1, 1, 'test_a', 'pass'),
(2, 1, 'test_b', 'fail'),
(3, 1, 'test_c', 'pass'),
(4, 1, 'test_d', 'pass'),
(5, 2, 'test_a', 'pass'),
(6, 2, 'test_b', 'pass'),
(7, 2, 'test_c', 'pass'),
(8, 2, 'test_d', 'pass');
Basically single task has multiple test results entry. I want to retrieve task_id that has test_b fail but all the other test passed. So in this example it should return only task_id: 1.
I've tried with EXISTS and HAVING but it doesn't seem working in this case. I'm new to SQL. How can I implement it?
I would just use aggregation with a having clause:
select task_id
from result
group by task_id
having sum(case when test_name = 'test_b' and test_result = 'fail' then 1 else 0 end) = 1 and
sum(case when test_result = 'pass' then 1 else 0 end) = count(*) - 1;
The first condition validates that test_b failed. The second counts the number of passes and it should be one less then the number of rows for the task.
If your database supports except (or minus), you an use set-based operations:
select task_id
from result
where test_name = 'test_b' and test_result = 'fail'
except
select task_id
from result
where test_name <> 'test_b' and test_result = 'fail'
Maybe selecting distinct task IDs that have a fail result:
select distinct [task_id], [task_result]
from [result]
where [task_result] = 'fail'
Note that this query will scan the entire table unless there is an index on task_result.
Following code first sums test takers per task and counts fro 'test_b' whether it failed or not. Outer select ensure 'test_b' failed and other have passed.
select task_id from (
select
task_id,
count(test_result) numberoftakers,
sum(case when test_result<>'pass' AND test_name='test_b' then 1 else 0 end) numberoffailb,
sum(case when test_result='pass' then 1 else 0 end) numberofallpasses
from result
group by task_id) a
where numberoftakers=numberoffailb+numberofallpasses and numberoffailb=1
Assuming that (task_id, task_name) is a unique key of your table, you can indeed use (not) exists, along with a correlated subqueries wich ensures that other records having the same task_id did not passed.
select task_id
from result r
where
test_name = 'test_b'
and test_result = 'fail'
and not exists (
select 1
from result r1
where
r1.task_id = r.task_id
and r1.id != r.id
and r1.test_result = 'fail'
)
The left join antipattern also comes to mind:
select r.task_id
from result r
left join result r1
on r1.task_id = r.task_id
and r1.id != r.id
and r1.test_result = 'fail'
where
r.test_name = 'test_b'
and r.test_result = 'fail'
and r1.id is null
Demo on DB Fiddle - Both queries return:
| task_id |
| :------ |
| 1 |
I have this table setup.
create table holdMyBeer
(
Id int,
Name varchar(20)
)
insert into holdMyBeer
values (1, 'park'), (1, 'washington'), (1, 'virginia'),
(2, 'harbor'), (2, 'premier'), (2, 'park'),
(3, 'park'), (3, 'washington'), (3, 'virginia'), (3, 'Ball');
I am looking for the id's (parents) that at least have park, washington and virginia as name(child).
I have the answer on Fiddle. http://sqlfiddle.com/#!6/e7346/1 but there must be a better way to do this.
This concept is called as Conditional Aggregation. I am grouping on Id and then checking whether there is atleast one entry for park,washington,virginia by using having clause and . This should answer your question.
SELECT Id
FROM holdMyBeer
GROUP BY Id
HAVING SUM( CASE WHEN Name = 'park' THEN 1 ELSE 0 END ) >= 1 AND
SUM( CASE WHEN Name = 'washington' THEN 1 ELSE 0 END ) >= 1 AND
SUM( CASE WHEN Name = 'virginia' THEN 1 ELSE 0 END ) >= 1;
Imagine I am an owner of many bookstores. I keep a database of all events that occur in all of my many bookstores. Two events of note are "Book Added" and "Book Removed", for when a book is added to the inventory of a story, and when it is sold from a store. An example schema would be bookstore_id, event_name, `time.
Now say I have a second table, which maintains the current state of each bookstore, so the schema would be bookstore_id, num_books.
I want to be able to use the first table to get the count of all the "Book Added" events per bookstore, subtract the count of all the "Book Removed" events per bookstore, and then update the number of books in each bookstore in the second table.
The only way I can think to do it requires using a cursor, but I'm assuming there's a more "SQL-esque" way to do it that is more set-based and doesn't require a cursor.
You can count the events by using a GROUP BY clause.
If we would create 2 subtables where we count the added respectively the removed books, we can simply subtract the results and update these in the parent table. This will look like:
UPDATE b
SET b.numbooks = AddedBooks.BooksAdded - RemovedBooks.BooksRemoved
FROM dbo.Books b
INNER JOIN (SELECT be.book_id, count(*) AS BooksAdded
FROM dbo.BookEvents be
WHERE be.event = 'BookAdded'
GROUP BY be.book_id, be.event) AS AddedBooks
ON b.bookid = AddedBooks.book_id
INNER JOIN (SELECT be.book_id, count(*) AS BooksRemoved
FROM dbo.BookEvents be
WHERE be.event = 'BookRemoved'
GROUP BY be.book_id, be.event) AS RemovedBooks
ON b.bookid = RemovedBooks.book_id
select bookstore_id
, sum(case when event_name = "Book Removed" then -1 else 1 end) as "num books"
from bookstores
group by bookstore_id
if more than 2 events
select bookstore_id
, sum(case when event_name = "Book Removed" then -1
when event_name = "Book Added" then 1
end) as "num books"
from bookstores
group by bookstore_id
And I would just make it a view unless you come up with performance issues
We can use CTEs to get details individually and process them.
With CTE_Add AS
(
Select Bkstr_ID, Count(event_Name) As Added From temp Where event = 'Added' Group by Bkstr_ID
), CTE_Rem As
(
Select Bkstr_ID, Count(event_Name) As Removed From temp Where event = 'Removed' Group by Bkstr_ID
)
Select A.Bkstr_ID, Added - Removed
From CTE_Add A
Left Join CTE_Rem R On A.Bkstr_ID= R.Bkstr_ID
This will give you ID and count.
Instead of select, you can use Insert statement
I'd use SUM(CASE WHEN ...). Below is an example.
If object_id('tempdb..#BoookStores') Is Not Null Drop Table #BoookStores
Create Table #BoookStores (bookstore_id int, num_books int)
/* We have 3 stores */
Insert #BoookStores (bookstore_id, num_books)
Values (1, 0), (2, 0), (3, 0)
If object_id('tempdb..#Events') Is Not Null Drop Table #Events
Create Table #Events (bookstore_id int, event_name varchar(10), time dateTime Default(GetDate()) )
Insert #Events (bookstore_id, event_name)
Values
(1, 'Added'), (1, 'Added'), (1, 'Added'), (1, 'Added'), -- Added 4 books to 1. store
(2, 'Added'), (2, 'Added'), (2, 'Added'), -- Added 3 books to 2. store
(3, 'Added'), (3, 'Added'), -- Added 2 books to 3. store
/* removed 2 books from each stores */
(1, 'Removed'), (1, 'Removed'),
(2, 'Removed'), (2, 'Removed'),
(3, 'Removed'), (3, 'Removed')
/* Calculate adds and removes. Update the results */
;With Tmp As (
Select E.bookstore_id,
Sum(Case When E.event_name = 'Added' Then 1 Else 0 End) As AddCount,
Sum(Case When E.event_name = 'Removed' Then 1 Else 0 End) As RemoveCount
From #Events E
Group By E.bookstore_id
)
Update BS Set num_books = T.AddCount-T.RemoveCount
From #BoookStores BS
Inner Join Tmp T On T.bookstore_id = BS.bookstore_id
/* check results*/
Select * From #BoookStores BS
Something like this will get you in the ball park. Similar logic could be used for INSERT.
UPDATE tableA
SET tableA.num_books = tableB.num_books
FROM secondTable AS TableA
INNER JOIN (
SELECT bookstore_id,
SUM(CASE
WHEN event_name = 'Books Added'
THEN 1
END) - SUM(CASE
WHEN event_name = 'Books Removed'
THEN 1
END
) AS num_books
FROM firstTable
GROUP BY bookstore_id
) TableB ON TableA.bookstore_id = tableB.bookstore_id
You can try a query like below:
update t1
set num_books=inventory
FROM bs t1 LEFT JOIN
(select bookstore_id,SUM(case when event_name like 'A' then 1 when event_name like 'R' then -1 else NULL end) as inventory
from bse
group by bookstore_id) t2
on t1.bookstore_id=t2.bookstore_id
Live SQL demo
UPDATE bsc
SET bsc.num_books = bse.num_books
FROM bookstorecounts bsc
JOIN (SELECT bookstore_id,
SUM(CASE event_name
WHEN 'Book Removed' THEN -1
WHEN 'Book Added' THEN 1
END) AS num_books
FROM bookstoreevents
GROUP BY bookstore_id
) bse ON bsc.bookstore_id = bse.bookstore_id
Table one contains
ID|Name
1 Mary
2 John
Table two contains
ID|Color
1 Red
2 Blue
2 Green
2 Black
I want to end up with is
ID|Name|Red|Blue|Green|Black
1 Mary Y Y
2 John Y Y Y
Thanks for any help.
Thanks for the responses. I'm going to re-post this with some additional info about exactly what I'm trying to do that may complicate this. Can someone close this?
If you use T-SQL you can use PIVOT (http://msdn.microsoft.com/en-us/library/ms177410.aspx)
Here is query I used:
declare #tbl_names table(id int, name varchar(100))
declare #tbl_colors table(id int, color varchar(100))
insert into #tbl_names
select 1, 'Mary'
union
select 2, 'John'
insert into #tbl_colors
select 1, 'Red'
union
select 1, 'Blue'
union
select 2, 'Green'
union
select 2, 'Blue'
union
select 2, 'Black'
select name,
case when [Red] is not null then 'Y' else '' end as Red,
case when [Blue] is not null then 'Y' else '' end as Blue,
case when [Green] is not null then 'Y' else '' end as Green,
case when [Black] is not null then 'Y' else '' end as Black
from
(
select n.id, name, color from #tbl_names n
inner join #tbl_colors c on n.id = c.id
) as subq
pivot
(
min(id)
FOR color IN ([Red], [Blue], [Green], [Black])
) as pvt
And here is output:
John Y Y Y
Mary Y Y
I can use a CASE statement with a subquery to input the Y values.
select ID, Name,
case
when exists (select * from Colors C where C.ID = N.ID and Color = 'Red') then
'Y'
else
NULL
end
,
case
when exists (select * from Colors C where C.ID = N.ID and Color = 'Blue') then
'Y'
else
NULL
end
,
case
when exists (select * from Colors C where C.ID = N.ID and Color = 'Green') then
'Y'
else
NULL
end
,
case
when exists (select * from Colors C where C.ID = N.ID and Color = 'Black') then
'Y'
else
NULL
end
from Names N
I think you're going to have to end up with something like this :
SELECT t1.ID,
t1.Name,
CASE
WHEN red.ID IS NULL THEN ''
ELSE 'Y'
END As Red,
CASE
WHEN blue.ID IS NULL THEN ''
ELSE 'Y'
END As Blue
FROM Table1 t1
LEFT JOIN Table2 Red
ON t1.ID = Red.ID AND Red.Color = 'Red'
LEFT JOIN Table2 Blue
ON t1.ID = Blue.ID AND Blue.Color = 'Blue'
MS Sql does not support PIVOT queries like MS Access.
As other commenters have pointed out, you don't display exactly how you are linking people and colors. If you are using a linking table (person_id, color_id) then there is no way to solve this problem in standard SQL since it requires a pivot or cross-tabulation, which is not part of standard SQL.
If you are willing to add the condition that the number of colors is limited and known and design time, you could come up with a solution using one join for each color and CASE or IF functions in the SQL. But that would not be elegant and, furthermore, I wouldn't trust that condition to stay true for very long.
If you are able to come up with a different way of storing the color linking information you might have more options for producing the output you want, but a different storage technique implies some degree of denormalization of the database which could well cause other difficulties.
Otherwise, you will have to do this in a stored procedure or application code.
Contrary to what some other posters have said; I see no need for a third table. If colors are a well known enumeration in you application then you don't need a "Color" table.
What you are looking for is a PIVOT like this one.