check whether an array values a subset of a query? - sql

I've a set of rows
SELECT id from Users WHERE...
1
2
6
8
9
and I've and array with values 2,3,6
How can I check in SQL that the array is a sub set of the result of the query?

SQL doesn't as such support arrays so I'm not entirely sure how you're storing your array of numbers, and that will affect the best way to answer this question.
That said, I'd do this:
SELECT u.id
FROM Users U
RIGHT JOIN Numbers N
ON U.id=N.Number
WHERE N.Number IN (2,3,6)
That's the basic query; exact details from there depend on what you'd be doing to detect the failure. Any records where u.ID IS NULL indicate it isn't a subset. If you don't actually immediately want the set of IDs you could modify it to
SELECT COUNT(*) AS Missing
FROM Users U
RIGHT JOIN Numbers N
ON U.id=N.Number
WHERE N.Number IN (2,3,6)
AND u.id IS NULL
and, whenever Missing was > 0 you'd know you didn't have a subset. (In SQL Server at least you can then cast the int to a bit to get 0=false, !0=true if that's easier for your app to work with.)
Other details we can add with more info about what you're actually trying to do, but hopefully that makes sense as a basic technique.
(N.B. this all assumes that you've got a numbers / tally table in your database. They're incredibly useful so, if you haven't already, I'd get one set up.)

You have to check each record/item individually, then count them.
If the JOIN is the same size as the array, the array is a sub-set of the table.
Here is an example that assumes your array in in a table...
SELECT
COUNT(*)
FROM
Users
INNER JOIN
search
ON search.id = Users.id
HAVING
COUNT(*) = (SELECT COUNT(*) FROM search)

Use Dynamic SQL:
declare #cmd varchar(200)
select #cmd = "select id from Users WHERE id in (" + #array + ")"
exec(#cmd)

If you can populate a one column table with the values that you need to test against then you could do this.
Select count(*)
From
(
Select id
From users
Intersect
Select id
From testValues
) test
If the count is equal to the number of values you're testing against then the array forms a subset.

Related

Loop through table and update a specific column

I have the following table:
Id
Category
1
some thing
2
value
This table contains a lot of rows and what I'm trying to do is to update all the Category values to change every first letter to caps. For example, some thing should be Some Thing.
At the moment this is what I have:
UPDATE MyTable
SET Category = (SELECT UPPER(LEFT(Category,1))+LOWER(SUBSTRING(Category,2,LEN(Category))) FROM MyTable WHERE Id = 1)
WHERE Id = 1;
But there are two problems, the first one is trying to change the Category Value to upper, because only works ok for 1 len words (hello=> Hello, hello world => Hello world) and the second one is that I'll need to run this query X times following the Where Id = X logic. So my question is how can I update X rows? I was thinking in a cursor but I don't have too much experience with it.
Here is a fiddle to play with.
You can split the words apart, apply the capitalization, then munge the words back together. No, you shouldn't be worrying about subqueries and Id because you should always approach updating a set of rows as a set-based operation and not one row at a time.
;WITH cte AS
(
SELECT Id, NewCat = STRING_AGG(CONCAT(
UPPER(LEFT(value,1)),
SUBSTRING(value,2,57)), ' ')
WITHIN GROUP (ORDER BY CHARINDEX(value, Category))
FROM
(
SELECT t.Id, t.Category, s.value
FROM dbo.MyTable AS t
CROSS APPLY STRING_SPLIT(Category, ' ') AS s
) AS x GROUP BY Id
)
UPDATE t
SET t.Category = cte.NewCat
FROM dbo.MyTable AS t
INNER JOIN cte ON t.Id = cte.Id;
This assumes your category doesn't have non-consecutive duplicates within it; for example, bora frickin bora would get messed up (meanwhile bora bora fickin would be fine). It also assumes a case insensitive collation (which could be catered to if necessary).
In Azure SQL Database you can use the new enable_ordinal argument to STRING_SPLIT() but, for now, you'll have to rely on hacks like CHARINDEX().
Updated db<>fiddle (thank you for the head start!)

SQL loop through a table and find the next part record

Not sure where to start on this one. I inheriated a table that has a list of part numbers that are are active and inactive. If the part number is inactive, they enter the next valid part number. If the part number is active there is no Next PartNumber. They want to search on a Part Number and find all of the next part numbers that match.
Basically the table looks like this.
PartNumber Varchar(20), Active Varchar(3), NextPartNumber Varchar(20).
Problem is I do not know how many part numbers are in the chain. Here is a sample of the data:
100X No XYZ
XYZ No 45A6
45A6 Yes
QWER No RT98
RT98 No POUL1
POUL1 No N9HGT
N9HGT No FGH12
FGH12 Yes
I can write a query like this, but since I don't know how many part numbers there are, this won't work.
Select A.PartNumber, A.NextPartNumber, B.PartNumber, B.NextPartNumber, C.PartNumber, C.NextPartNumber
FROM tblPartTable as A
inner join
tblPartTable as B
on A.PartNumber = B.NextPartNumber
inner join
tblPartTable as C
on B.PartNumber = C.NextPartNumber
where A.PartNumber = '100X'
With SQL Server (which I'm assuming you're talking about since your earlier questions have been about it), you can use a recursive common table expression to easily get the searched for part and all its successors, there is no need to loop manually;
WITH cte AS (
-- Base condition, where do we start the search?
SELECT t.* FROM tblPartTable t WHERE t.PartNumber = '100X'
UNION ALL
-- Continue condition, how do we find the next part from the current one?
SELECT t.* FROM tblPartTable t JOIN cte ON t.PartNumber = cte.NextPartNumber
)
SELECT partnumber, active FROM cte;
An SQLfiddle to test with.
The same query works on most RDBMS's except MySQL.

Check if a list of items already exists in a SQL database

I want to create a group of users only if the same group does not exist already in the database.
I have a GroupUser table with three columns: a primary key, a GroupId, and a UserId. A group of users is described as several lines in this table sharing a same GroupId.
Given a list of UserId, I would like to find a matching GroupId, if it exists.
What is the most efficient way to do that in SQL?
Let say your UserId list is stored in a table called 'MyUserIDList', the following query will efficiently return the list of GroupId containing exactly your user list. (SQL Server Syntax)
Select GroupId
From (
Select GroupId
, count(*) as GroupMemberCount
, Sum(case when MyUserIDList.UserID is null then 0 else 1 End) as GroupMemberCountInMyList
from GroupUser
left outer join MyUserIDList on GroupUser.UserID=MyUserIDList.UserID
group by GroupId
) As MySubQuery
Where GroupMemberCount=GroupMemberCountInMyList
There are couple of ways of doing this. This answer is for sql server only (as you have not mentioned it in your tags)
Pass the list of userids in comma seperated to a stored procedure and in the SP create a dynamic query with this and use the EXEC command to execute the query. This link will guide you in this regard
Use a table-valued parameter in a SP. This is applicable to sql server 2008 and higher only.
The following link will help you get started.
http://www.codeproject.com/Articles/113458/TSQL-Passing-array-list-set-to-stored-procedure-MS
Hope this helps.
One other solution is that you convert the input list into a table. This can be done with various approaches. Unions, temporary tables and others. A neat solution combines the answer of
user1461607 for another question here on SO, using a comma-separated string.
WITH split(word, csv) AS (
-- 'initial query' (see SQLite docs linked above)
SELECT
'', -- place holder for each word we are looking for
'Auto,A,1234444,' -- items you are looking for
-- make sure the list ends with a comma !!
UNION ALL SELECT
substr(csv, 0, instr(csv, ',')), -- each word contains text up to next ','
substr(csv, instr(csv, ',') + 1) -- next recursion parses csv after this ','
FROM split -- recurse
WHERE csv != '' -- break recursion once no more csv words exist
) SELECT word, exisiting_data
FROM split s
-- now join the key you want to check for existence!
-- for demonstration purpose, I use an outer join
LEFT OUTER JOIN (select 'A' as exisiting_data) as t on t.exisiting_data = s.word
WHERE s.word != '' -- make sure we clamp the empty strings from the split function
;
Results in:
Auto,null
A,A
1234444,null

SQL IN() operator with condition inside

I've got table with few numbers inside (or even empty): #states table (value int)
And I need to make SELECT from another table with WHERE clause by definite column.
This column's values must match one of #states numbers or if #states is empty then accept all values (like there is no WHERE condition for this column).
So I tried something like this:
select *
from dbo.tbl_docs docs
where
docs.doc_state in(iif(exists(select 1 from #states), (select value from #states), docs.doc_state))
Unfortunately iif() can't return subquery resulting dataset. I tried different variations with iif() and CASE but it wasn't successful. How to make this condition?
select *
from dbo.tbl_docs docs
where
(
(select count(*) from #states) > 0
AND
docs.doc_state in(select value from #states)
)
OR
(
(select count(*) from #states)=0
AND 1=1
)
Wouldn't a left join do?
declare #statesCount int;
select #statesCount = count(1) from #states;
select
docs.*
from dbo.tbl_docs docs
left join #states s on docs.doc_state = s.value
where s.value is not null or #statesCount = 0;
In general, whenever your query contains sub-queries, you should stop for five minutes, and think hard about whether you really need a sub-query at all.
And if you've got a server capable of doing that, in many cases it might be better to preprocess the input parameters first, or perhaps use constructs such as MS SQL's with.
select *
from dbo.tbl_docs docs
where exists (select 1 from #states where value = doc_state)
or not exists (select 1 from #state)

How do I return rows if concatenated value is within another set of values?

This may be a dumb question, but I am trying to concatenate values from two columns and then return rows where those concatenated values are in another set.
My question is, what is the best method to do this? I've done this in the past using an IN statement, but if I have a lot of values this time around.
Would I be better off creating a new table with my comparison values and get my results using some kind of relationship statement? Something like that below?
SELECT Users.ID + Users.SubID AS UniqueID, TempVals.ID
FROM Users, TempVals
WHERE Users.ID + Users.SubID = TempVals.ID
Thanks!
What you have should work fine assuming it's MySQL and the data types of the columns being concatenated are strings/character-types - no worries, you should be able to cast them.
Another way:
SELECT DISTINCT U.ID + U.SubID AS UniqueID
FROM Users as U
WHERE (U.ID + U.SubID) IN (
SELECT DISTINCT T.ID
FROM TempVals as T
)
Also, the type of database will determine the method of concatenation:
SQL Server: +
Oracle: CONCAT()
MySQL: CONCAT()
Reference: Using IN Statement - SQL