I have a Clients table already populated by thousands of records and now I need to search for a non-existing number in the card number column starting from the number x.
Example: I would like to search for the first available card number starting from number 2000.
Unfortunately I cannot select MAX() as there are records with 9999999 (which is the limit).
Is it possible to do this search through a single SELECT?
It's possible with a few nested SELECTs:
SELECT MIN(`card_number`) + 1 as next_available_number
FROM( SELECT (2000-1) as `card_number`
UNION
SELECT `card_number`
FROM clients
WHERE `card_number` >= 2000
) tmp
WHERE NOT EXISTS ( SELECT NULL
FROM clients
WHERE `card_number` = tmp.`card_number` + 1 )
It can be done with a self-join on your clients table, where you search for the lowest cardnumber for which the cardnumber + 1 does not exist.
In case x is 12, the query would be:
SELECT MIN(cardnumber) + 1
FROM clients
WHERE cardnumber + 1 NOT IN (SELECT cardnumber FROM clients)
AND cardnumber + 1 > 12
Eg. with a dataset of
INSERT INTO clients (cardnumber) VALUES
(1),(2),(3),(4),(5),(6),(7),(8),(9),(11),(12),(13),(14),(15),(17),(18)
this returns 16, but not 10.
Example on SQL Fiddle.
I think this is very similar to this question, but the minimum criteria is new.
If the credit card is represented as integer in your table and your starting number is 2000 you could do something like:
SELECT top 1 (card_id + 1)
FROM CreditCards t
WHERE card_id IN (
SELECT card_id
FROM CreditCards
WHERE card_id LIKE '%[2][0][0][0]%'
)
AND NOT EXISTS (SELECT 1 FROM CreditCards t2 WHERE t2.card_id = t.card_id + 1)
ORDER BY card_id
Example data (Table: CreditCards):
card_id
2000002
2000103
2000000
2000108
2000106
3000201
1000101
Result is: 2000001
Note that %[2][0][0][0]% is fixed here. You could also introduce a parameter.
It is not an optimal solution, but it does the trick.
Perhaps this was a wonky design decision on my part, but I created a table that has a column that holds from 1 to N comma-separated values, and I need to query whether any of several values are contained within those values.
To make it more clear, I'm allowing the user to select an array of movie genres (comedy, drama, etc.) and have a "movies" table where these genres are all contained in one column. For example, the genres column for the movie "The Princess Bride" contains the composite csv value "Adventure, Family, Fantasy"
So if the user selects at least one of those genres, that movie should be included in the result set that is returned based on the search criteria that they choose. But the movie should only be returned once, even if the user selected more than one of those genres to search for.
A previous incarnation of the database contained lookup tables and many-to-many tables which obviated this problem, but in an attempt to make it "easier" and more straightforward by making it one big (wide) table, I have run into this conundrum (of how to craft the query).
The query is currently dynamically built this way:
string baseQuery = "SELECT * FROM MOVIES_SINGLETABLE ";
string imdbRatingFilter = "WHERE IMDBRating >= #IMDBMinRating ";
string yearRangeFilter = "AND YearReleased BETWEEN #EarliestYear AND #LatestYear ";
string genreFilterBase = "AND GENRE IN ({0}) ";
string mpaaRatingFilterBase = "AND MPAARating IN ({0}) ";
string orderByPortion = "ORDER BY IMDBRating DESC, YearReleased DESC ";
...and the strings containing the selected criteria built like so:
if (filterGenres)
{
if (ckbxAction.Checked) genresSelected = "\'Action\',"; // changed to "#" hereafter:
if (ckbxAdventure.Checked) genresSelected = genresSelected + #"'Adventure',";
. . .
if (ckbxWar.Checked) genresSelected = genresSelected + #"'War',";
if (ckbxWestern.Checked) genresSelected = genresSelected + #"'Western',";
LastCommaIndex = genresSelected.LastIndexOf(',');
genresSelected = genresSelected.Remove(LastCommaIndex, 1)
}
// the same situation holds for mpaaRatings as for Genres:
if (filterMPAARatings)
{
if (ckbxG.Checked) mpaaRatingsSelected = #"'G',";
if (ckbxPG.Checked) mpaaRatingsSelected = mpaaRatingsSelected + #"'PG',";
if (ckbxPG13.Checked) mpaaRatingsSelected = mpaaRatingsSelected + #"'PG13',";
if (ckbxNR.Checked) mpaaRatingsSelected = mpaaRatingsSelected + #"'NR',";
LastCommaIndex = mpaaRatingsSelected.LastIndexOf(',');
mpaaRatingsSelected = mpaaRatingsSelected.Remove(LastCommaIndex, 1);
}
. . .
//string genreFilterBase = "AND GENRES IN ({0}) ";
if (filterGenres)
{
genreFilter = string.Format(genreFilterBase, genresSelected);
completeQuery = completeQuery + genreFilter;
}
//string mpaaRatingFilterBase = "AND MPAARating IN ({0}) ";
if (filterMPAARatings)
{
mpaaRatingFilter = string.Format(mpaaRatingFilterBase, mpaaRatingsSelected);
completeQuery = completeQuery + mpaaRatingFilter;
}
Is my design salvageable? IOW, can I retrieve the appropriate data given these admittedly questionable table design decisions?
UPDATE
I tested GMB's SQL by incorporating it into my SQL, but I may be doing something wrong, because it won't compile:
I don't know why those commas are there, but I reckon GMB is more of a SQL expert than I am...
Nevertheless, this does work (sans the commas and pipes):
UPDATE 2
I tried using CONTAINS, also:
SELECT * FROM MOVIES_SINGLETABLE
WHERE IMDBRating >= 7.5
AND YearReleased BETWEEN '1980' AND '2020'
AND CONTAINS (genres, 'Adventure')
OR CONTAINS (genres,'Family')
OR CONTAINS (genres, 'Fantasy')
AND CONTAINS (MPAARating, 'G')
OR CONTAINS (MPAARating, 'PG')
OR CONTAINS (MPAARating, 'PG-13')
ORDER BY IMDBRating DESC, YearReleased DESC
...but got "Cannot use a CONTAINS or FREETEXT predicate on table or indexed view 'MOVIES_SINGLETABLE' because it is not full-text indexed."
The answer by Alex Aza here [https://stackoverflow.com/questions/6003240/cannot-use-a-contains-or-freetext-predicate-on-table-or-indexed-view-because-it] gives a solution, but apparently it's not available for SQL Server Express:
...and besides, this will eventually (soon) be migrated to a SQLite table, anyway, and I doubt SQLite would support CONTAINS if doing it in SQL Server (albeit Express) requires, even if possible at all, hurtling through hoops.
UPDATE 3
I incorporated Lukasz's idea for SQLite (as that's what I'm now querying), with a query string that ended up being:
SELECT MovieTitle, MPAARating, IMDBRating, DurationInMinutes,
YearReleased, genres, actors, directors, screenwriters FROM
MOVIES_SINGLETABLE WHERE IMDBRating >= #IMDBMinRating AND
(YearReleased BETWEEN #EarliestYear AND #LatestYear) AND
(INSTR(genres, #genre1) > 0 OR INSTR(genres, #genre2) > 0) AND
(MPAARating = #mpaaRating1) ORDER BY IMDBRating DESC, YearReleased
DESC LIMIT 1000
...but still get no results.
Using SQL Server CHARINDEX:
SELECT *
FROM MOVIES_SINGLETABLE
WHERE IMDBRating >= 7.5 AND YearReleased BETWEEN '1980' AND '2020'
AND (
CHARINDEX ('Adventure',genres) > 0
OR CHARINDEX ('Family',genres) > 0
OR CHARINDEX ( 'Fantasy',genres) > 0
)
AND (
CHARINDEX ('G', MPAARating) > 0
OR CHARINDEX ('PG', MPAARating) > 0
OR CHARINDEX ('PG-13', MPAARating) > 0
)
ORDER BY IMDBRating DESC, YearReleased DESC
Or SQLite INSTR:
SELECT *
FROM MOVIES_SINGLETABLE
WHERE IMDBRating >= 7.5 AND YearReleased BETWEEN '1980' AND '2020'
AND (
INSTR (genres, 'Adventure') > 0
OR INSTR (genres,'Family') > 0
OR INSTR (genres, 'Fantasy') > 0
)
AND (
INSTR (MPAARating, 'G') > 0
OR INSTR (MPAARating, 'PG') > 0
OR INSTR (MPAARating, 'PG-13') > 0
)
ORDER BY IMDBRating DESC, YearReleased DESC;
db<>fiddle demo
Notes:
Added parentheses around OR condition
Storing data in CSV format is not the best design(column does not contain atomic value).
That's bad design indeed. Your first effort should go into fixing it: each element in the CSV list should be stored in a separate row. See: Is storing a delimited list in a database column really that bad?
One workaround would be to use a series of like conditions, like so:
and (
',' || mpaarating || ',' like '%,' || {0} || ',%'
or ',' || mpaarating || ',' like '%,' || {1} || ',%'
or ',' || mpaarating || ',' like '%,' || {2} || ',%'
)
This might be what you are looking for
DECLARE #TempTable TABLE (FilmName VARCHAR(128), GENRE VARCHAR(128))
INSERT INTO #TempTable VALUES ('Film 1', '1,2,5')
INSERT INTO #TempTable VALUES ('Film 2', '1,3,4')
INSERT INTO #TempTable VALUES ('Film 3', '6')
DECLARE #SearchGenre VARCHAR(128) = '3,4,2'
DECLARE #SearchGenreT TABLE (gens VARCHAR(8))
INSERT INTO #SearchGenreT SELECT * FROM STRING_SPLIT(#SearchGenre, ',')
SELECT * FROM #TempTable
WHERE
(
SELECT COUNT(a.value) from STRING_SPLIT(GENRE, ',') a
JOIN /*STRING_SPLIT(#SearchGenre, ',')*/ #SearchGenreT b ON a.value = b.gens
) > 0
Yes, it is possible to work within your limitations. It's not pretty, but it is functional. All of the below is within SQL Server T-SQL syntax.
Find or create a function that can split your comma-delimited values into tables
Run the genres column through your function in order to split your list of genres down into a miniature tables of individual values; we do this with CROSS APPLY
In your WHERE clause, run the user's preferences through your function to split that into individual values; see the code for more clarity.
CREATE TABLE MOVIES_SINGLETABLE (
MovieID nvarchar(10) NOT NULL,
MovieTitle nvarchar(100) NOT NULL,
Genres nvarchar(100) null
)
GO
INSERT INTO MOVIES_SINGLETABLE VALUES ('M001', 'The Princess Bride', 'Fantasy, Action, Comedy')
INSERT INTO MOVIES_SINGLETABLE VALUES ('M002', 'Die Hard', 'Action')
INSERT INTO MOVIES_SINGLETABLE VALUES ('M003', 'Elf', 'Christmas, Holiday, Comedy')
INSERT INTO MOVIES_SINGLETABLE VALUES ('M004', 'Percy Jackson and the Lightning-Thief', 'Fantasy')
go
DECLARE #genreList varchar(150) = 'Comedy, Action'
-- IN SQL 2016 onward
SELECT DISTINCT MovieTitle, Genres
FROM MOVIES_SINGLETABLE m
CROSS APPLY STRING_SPLIT(m.genres, ',') mgenres
WHERE TRIM(mgenres.value) IN (SELECT TRIM(value) FROM string_split(#genreList, ','))
-- Before 2016, using a function dbo.stringSplit we create beforehand
-- notice the syntax is nearly identical
SELECT DISTINCT MovieTitle, Genres
FROM MOVIES_SINGLETABLE m
CROSS APPLY dbo.stringSplit(m.genres, ',') mgenres
WHERE LTRIM(RTRIM((mgenres.value))) IN (SELECT LTRIM(RTRIM(value)) FROM dbo.stringSplit(#genreList, ','))
SQL Server 2016 onward has the functionality built in, and appears to exist in the Express license. However, here is the dbo.stringSplit function code I used, and the source if you want different variations of it:
/* SOURCE: https://stackoverflow.com/a/19935594/14443733 */
CREATE FUNCTION dbo.stringSplit (
#list NVARCHAR(max),
#delimiter NVARCHAR(255)
)
RETURNS TABLE
AS
RETURN (
SELECT [Value]
FROM (
SELECT [Value] = LTRIM(RTRIM(SUBSTRING(#List, [Number], CHARINDEX(#delimiter, #List + #delimiter, [Number]) - [Number])))
FROM (
SELECT Number = ROW_NUMBER() OVER (
ORDER BY name
)
FROM sys.all_columns
) AS x
WHERE Number <= LEN(#List)
AND SUBSTRING(#delimiter + #List, [Number], DATALENGTH(#delimiter) / 2) = #delimiter
) AS y
);
GO
I want to get alternate series of records using SQL Server.
For example :
I want to skip first 10 records (1 to 10) in sequence and get other 10 records (11 to 20) after that I want to skip next 10 records (21 to 30) and get another next 10 records (31 to 40)
I have done for alternate rows as below...
SELECT ROW, EmployeeID
FROM
(SELECT
ROW_NUMBER() OVER (ORDER BY EmployeeID) AS ROW, *
FROM Employee) A
WHERE
ROW % 2 = 0
But in case of my requirement above logic will not work. Please help me to make above thing works..
Linq will also accepted
Thanks
SELECT ROW, EmployeeID
FROM
(SELECT
ROW_NUMBER() OVER (ORDER BY EmployeeID) AS ROW, *
FROM Employee) A
WHERE
((ROW - 1)/10) % 2 = 1
I have done above of your requirement like below (Example).
Generated 1200 number sequentially from 1 to 1200 like below
;WITH CTE
AS
(
SELECT 1 PERIOD_SID
UNION ALL
SELECT PERIOD_SID+1 FROM CTE WHERE PERIOD_SID<1200
)
SELECT * INTO #PERIOD1 FROM CTE
OPTION(MAXRECURSION 0)
After this i have find alternative numbers like you have mentioned in your query
SELECT PERIOD_SID FROM
(
SELECT CASE
WHEN PERIOD_SID%10 <> 0 THEN PERIOD_SID / 10
WHEN PERIOD_SID%10 = 0 THEN ( PERIOD_SID / 10 ) - 1
END RNO,
PERIOD_SID
FROM #PERIOD1 )A
WHERE RNO%2=0
so if you have sequential numbers in your query (If you dont have generate using rownumber) then apply above logic.
Your query need to convert like below.
SELECT EMPLOYEEID
FROM (SELECT Row_number()
OVER (
ORDER BY EMPLOYEEID)AS ROW,
*
FROM EMPLOYEE) A
WHERE ( CASE
WHEN RNO%10 <> 0 THEN RNO / 10
WHEN RNO%10 = 0 THEN ( RNO / 10 ) - 1
END )%2 = 0
may be you can try this
SELECT ROW, EmployeeID FROM(
SELECT ROW_NUMBER()OVER (ORDER BY EmployeeID)AS ROW,* FROM Employee)
A WHERE ((ROW - (ROW%10))/10) % 2 = 1
Don't if this works or not coz I dont have sql server to run. Please show the error/result after running this query.
Ask if any doubt.
In essence I want to pick the best match of a prefix from the "Rate" table based on the TelephoneNumber field in the "Call" table. Given the example data below, '0123456789' would best match the prefix '012' whilst '0100000000' would best match the prefix '01'.
I've included some DML with some more examples of correct matches in the SQL comments.
There will be circa 70,000 rows in the rate table and the call table will have around 20 million rows. But there will be a restriction on the Select from the Call table based on a dateTime column so actually the query will only need to run over 0.5 million call rows.
The prefix in the Rate table can be up to 16 characters long.
I have no idea how to approach this in SQL, I'm currently thinking of writing a C# SQLCLR function to do it. Has anyone done anything similar? I'd appreciate any advice you have.
Example Data
Call table:
Id TelephoneNumber
1 0123456789
2 0100000000
3 0200000000
4 0780000000
5 0784000000
6 0987654321
Rate table:
Prefix Scale
1
01 1.1
012 1.2
02 2
078 3
0784 3.1
DML
create table Rate
(
Prefix nvarchar(16) not null,
Scale float not null
)
create table [Call]
(
Id bigint not null,
TelephoneNumber nvarchar(16) not null
)
insert into Rate (Prefix, Scale) values ('', 1)
insert into Rate (Prefix, Scale) values ('01', 1.1)
insert into Rate (Prefix, Scale) values ('012', 1.2)
insert into Rate (Prefix, Scale) values ('02', 2)
insert into Rate (Prefix, Scale) values ('078', 3)
insert into Rate (Prefix, Scale) values ('0784', 3.1)
insert into [Call] (Id, TelephoneNumber) values (1, '0123456789') --match 1.2
insert into [Call] (Id, TelephoneNumber) values (2, '0100000000') --match 1.1
insert into [Call] (Id, TelephoneNumber) values (3, '0200000000') --match 2
insert into [Call] (Id, TelephoneNumber) values (4, '0780000000') --match 3
insert into [Call] (Id, TelephoneNumber) values (5, '0784000000') --match 3.1
insert into [Call] (Id, TelephoneNumber) values (6, '0987654321') --match 1
Note: The last one '0987654321' matches the blank string because there are no better matches.
Since this is based on partial matching, a subselect would be the only viable option (unless, like LukeH assumes, every call is unique)
select
c.Id,
c.TelephoneNumber,
(select top 1
Scale
from Rate r
where c.TelephoneNumber like r.Prefix + '%' order by Scale desc
) as Scale
from Call c
SELECT t.Id, t.TelephoneNumber, t.Prefix, t.Scale
FROM
(
SELECT *, ROW_NUMBER() OVER
(
PARTITION BY c.TelephoneNumber
ORDER BY r.Scale DESC
) AS RowNumber
FROM [call] AS c
INNER JOIN [rate] AS r
ON c.TelephoneNumber LIKE r.Prefix + '%'
) AS t
WHERE t.RowNumber = 1
ORDER BY t.Id
Try this one:
select Prefix, min(c.TelephoneNumber)
from Rate r
left outer join Call c on c.TelephoneNumber like left(Prefix + '0000000000', 10)
or c.TelephoneNumber like Prefix + '%'
group by Prefix
You can use a left join to try to find a "better" match, and then eliminate such matches in your where clause. e.g.:
select
*
from
Call c
inner join
Rate r
on
r.Prefix = SUBSTRING(c.TelephoneNumber,1,LEN(r.Prefix))
left join
Rate r_anti
on
r_anti.Prefix = SUBSTRING(c.TelephoneNumber,1,LEN(r_anti.Prefix)) and
LEN(r_anti.Prefix) > LEN(r.Prefix)
where
r_anti.Prefix is null