How to make a "two dependent parameters" SQL stored procedure - sql-server

I just can't figure it out, what I mean by two dependents parameters is this :
Suppose I have records like these :
ID Letter Number
-----------------------
23 A 1
23 A 2
23 B 1
23 B 2
81 A 1
81 B 2
The user is to input this :
First parameter : A,B
Second parameter : 1,2
Then only ID 23 would be returned, because it's the only one that respect all these conditions :
A1, A2, B1, B2
Every time I tried some query, 81 was returned...
In the context of my question it would be quick enough to make 4 conditions like :
A and 1, A and 2, B and 1, B and 2
But imagine if i have 16 * 16 ...
It would be extremely long to write them all...

You can use COUNT DISTINCT in the HAVING clause:
SELECT Id
FROM #Tbl
WHERE
Letter IN('A', 'B')
AND Number IN(1, 2)
GROUP BY Id
HAVING
COUNT(DISTINCT Letter) = 2
AND COUNT(DISTINCT Number) = 2;
For more dynamic approach, you can put the criteria in table variables:
DECLARE #Letters TABLE(Letter CHAR(1));
DECLARE #Numbers TABLE(Number INT);
INSERT INTO #Letters VALUES ('A'), ('B');
INSERT INTO #Numbers VALUES (1), (2);
WITH CteCross(Letter, Number) AS(
SELECT Letter, Number
FROM #Letters
CROSS JOIN #Numbers
)
SELECT t.Id
FROM #Tbl t
INNER JOIN CteCross cc
ON cc.Letter = t.Letter
AND cc.Number = t.Number
GROUP BY t.Id
HAVING COUNT(*) = (SELECT COUNT(*) FROM CteCross);
ONLINE DEMO

Related

Displaying sorted hierarchy rows in SQL server?

Assuming I have this table : ( c is a child of parent p)
c p
------
40 0
2 3
2 40
3 1
7 2
1 0
Where (0 means root) — I want the order of select to be displayed as :
c b
------
1 0
3 1
2 3
40 0
2 40
7 2
That's becuase we have 2 roots (1,40) and 1 < 40.
So we start at 1 and then display below it - all it's descendants.
Then we get to 40. same logic again.
Question:
How can I do it ?
I've succeeded to display it recursively + finding level of hierarchy*(not sure if it helps though)*
WITH cte(c, p) AS (
SELECT 40, 0 UNION ALL
SELECT 2,3 UNION ALL
SELECT 2,40 UNION ALL
SELECT 3,1 UNION ALL
SELECT 7,2 UNION ALL
SELECT 1,0
) , cte2 AS(
SELECT c,
p,
PLevel = 1
FROM cte
WHERE p = 0
UNION ALL
SELECT cte.c,
cte.p,
PLevel = cte2.PLevel + 1
FROM cte
INNER JOIN cte2
ON cte2.c = cte.p
)
SELECT *
FROM cte2
Full SQL fiddle
You have almost done it. Just add a rank to identify each group and then sort the data on it.
Also, as you are working with more complex hierarchy we need to change the [level] value. In is now not a number, put the full path of the current element to its parent. Where \ means parent. For example the following string:
\1\5\4\1
represents the hierarchy below:
1
--> 5
--> 4
--> 1
I get the idea from hierarchyid type. You may want to consider storing hierarchies using it, as it has handy build-in functions for working with such structures.
Here is full working example with the new data:
DECLARE #DataSource TABLE
(
[c] TINYINT
,[p] TINYINT
);
INSERT INTO #DataSource ([c], [p])
VALUES (1,0)
,(3, 1)
,(2, 3)
,(5,1)
,(7, 2)
,(40, 0)
,(2, 40);
WITH DataSource ([c], [p], [level], [rank])AS
(
SELECT [c]
,[p]
,CAST('/' AS VARCHAR(24))
,ROW_NUMBER() OVER (ORDER BY [c] ASC)
FROM #DataSource
WHERE [p] = 0
UNION ALL
SELECT DS.[c]
,DS.[p]
,CAST(DS1.[level] + CAST(DS.[c] AS VARCHAR(3)) + '/' AS VARCHAR(24))
,DS1.[rank]
FROM #DataSource DS
INNER JOIN DataSource DS1
ON DS1.[c] = DS.[p]
)
SELECT [c]
,[p]
FROM DataSource
ORDER BY [Rank]
,CAST([level] AS hierarchyid);
Again, pay attention to the node (7,2) which is participating in the two groups (even in your example). I guess this is just a sample data and you have a way to defined where the node should be included.

Adding missing rows with a join

I have a SQL script that returns this derived table.
MM/YYYY Cat Score
02/2012 Test1 19
04/2012 Test1 15
05/2012 Test1 16
07/2012 Test1 14
08/2012 Test1 15
09/2012 Test1 15
12/2012 Test1 11
02/2012 Test2 15
03/2012 Test2 13
05/2012 Test2 18
06/2012 Test2 14
08/2012 Test2 15
09/2012 Test2 14
12/2012 Test2 10
As you can see, I am missing some MM/YYYYs (03/2012, 06/2012, 11/2012, etc).
I would like to fill in the missing MM/YYYYs with the Cat & a 0 (zero) form the score. I know what the beginning date (1/2012) should be.
I have tried to join a table that contains the all MM/YYYY for the ranges the query will be run, but this only returns the missing rows for the first occurrence, it does not repeat for each Cat (should have known that).
So my question is this, can I do this using a join or will I have to do this in a temp table, and then output the data. I am looking to return a table, that has 24 rows, for the date range 1/2012 to 12/2012, with the appropriate Cat (Test1 & Test2) & the corresponding score, if it exists or a zero if it was not in the origial table.
AHIGA, LarryR…
Here's an example of how you can use some CTEs to get what you are after. This uses three CTEs: One for all the dates, one for your categories, then a third to cross join them. The third is really redundant but it's just to make it a little more obvious what we are looking at.
DECLARE
#StartDate datetime = '2/1/2012'
,#EndDate datetime = '4/26/2013'
;WITH DATES AS
(
SELECT
CAST(MONTH(#StartDate) as varchar(20)) + '/' + CAST(YEAR(#StartDate) as varchar(20)) 'CurrMonth'
,#StartDate as Datefield
UNION ALL
SELECT
CAST(MONTH(CurrDate) as varchar(20)) + '/' + CAST(YEAR(CurrDate) as varchar(20))
,CurrDate
FROM
DATES
CROSS APPLY (SELECT DATEADD(month, 1, Datefield)) CxA(CurrDate)
WHERE CurrDate < #EndDate
)
,CATS AS
(
SELECT 'Test1' as 'Cat'
UNION ALL
SELECT 'Test2'
)
,AllTheThings AS
(
SELECT
Datefield
,CurrMonth
,Cat
FROM
DATES D
CROSS JOIN
CATS C
)
SELECT * FROM AllTheThings
If I understodd correctly, you would join your table like this
SELECT
d.Date,c.Cat,ISNULL(t.Score,0) As Score
FROM YourDateTable d
CROSS JOIN
( -- Your Cat Table or UNION Cat here
SELECT 'Test1' AS Cat
UNION ALL
SELECT 'Test2'
) c
LEFT JOIN YourCurrentTable t
ON t.Date = d.Date
AND t.Cat = c.Cat
The best solution is to create a date table (over a temp table). It takes very little space.

How to query rows with nearest column values?

How should I construct T-SQL statement in order to achieve following:
from this
table1:
display_term|occurence
A|1
A|4
B|3
B|9
retrieve this
table2:
display_term|occurence
A|4
B|3
The "nearest distance" between A and B is 1 and it can be seen in resulting table.
i.e. I want to query the nearest (column "occurrence") distinct(column "display_term") records.
Thanks in advance
For just two terms, you can do something like:
declare #T table (display_term char(1) not null,occurence int not null)
insert into #T (display_term,occurence) values
('A',1),
('A',4),
('B',3),
('B',9)
select top 1
*
from
#T t1
cross join
#T t2
where
t1.display_term = 'A' and
t2.display_term = 'B'
order by ABS(t1.occurence - t2.occurence)
Which produces:
display_term occurence display_term occurence
------------ ----------- ------------ -----------
A 4 B 3
(You can search for UNPIVOT based solutions if you need exactly the result set you asked for)
It's not clear from your question whether this needs to extend to more terms - there's no obvious way to re-interpret the requirement for more terms, so I've left that undone for now.
UNPIVOT based solution if exact result set required. Setup #T as above:
select display_term,occurence from (
select top 1
t1.occurence as A,
t2.occurence as B
from
#T t1
cross join
#T t2
where
t1.display_term = 'A' and
t2.display_term = 'B'
order by ABS(t1.occurence - t2.occurence)
) t unpivot (occurence for display_term in (A,B)) as u
Result:
display_term occurence
------------------------------------ -----------
A 4
B 3
You said you want the nearest. It's not clear exactly what that means. Your example above shows the maximum of each display term. If you want the maximum value for each display term, you want to use aggregation. This is achieved by using the Group By and the Max method.
SELECT display_term, Max(occurence) as MaxOccurrence
FROM TABLE_NAME
GROUP BY display_term
If a single row result will work for you, this will do it:
SELECT a.display_term AS adt,
a.occurence As aoc,
b.display_term AS bdt,
b.occurence AS boc,
ABS(a.occurence - b.occurence) AS distance
FROM my_table a, my_table b
WHERE a.display_term = 'A'
AND b.display_term = 'B'
AND ABS(a.occurence - b.occurence) = (
SELECT MIN(ABS(a.occurence - b.occurence))
FROM my_table a, my_table b
WHERE a.display_term = 'A'
AND b.display_term = 'B'
)

How do I get the "Next available number" from an SQL Server? (Not an Identity column)

Technologies: SQL Server 2008
So I've tried a few options that I've found on SO, but nothing really provided me with a definitive answer.
I have a table with two columns, (Transaction ID, GroupID) where neither has unique values. For example:
TransID | GroupID
-----------------
23 | 4001
99 | 4001
63 | 4001
123 | 4001
77 | 2113
2645 | 2113
123 | 2113
99 | 2113
Originally, the groupID was just chosen at random by the user, but now we're automating it. Thing is, we're keeping the existing DB without any changes to the existing data(too much work, for too little gain)
Is there a way to query "GroupID" on table "GroupTransactions" for the next available value of GroupID > 2000?
I think from the question you're after the next available, although that may not be the same as max+1 right? - In that case:
Start with a list of integers, and look for those that aren't there in the groupid column, for example:
;WITH CTE_Numbers AS (
SELECT n = 2001
UNION ALL
SELECT n + 1 FROM CTE_Numbers WHERE n < 4000
)
SELECT top 1 n
FROM CTE_Numbers num
WHERE NOT EXISTS (SELECT 1 FROM MyTable tab WHERE num.n = tab.groupid)
ORDER BY n
Note: you need to tweak the 2001/4000 values int the CTE to allow for the range you want. I assumed the name of your table to by MyTable
select max(groupid) + 1 from GroupTransactions
The following will find the next gap above 2000:
SELECT MIN(t.GroupID)+1 AS NextID
FROM GroupTransactions t (updlock)
WHERE NOT EXISTS
(SELECT NULL FROM GroupTransactions n WHERE n.GroupID=t.GroupID+1 AND n.GroupID>2000)
AND t.GroupID>2000
There are always many ways to do everything. I resolved this problem by doing like this:
declare #i int = null
declare #t table (i int)
insert into #t values (1)
insert into #t values (2)
--insert into #t values (3)
--insert into #t values (4)
insert into #t values (5)
--insert into #t values (6)
--get the first missing number
select #i = min(RowNumber)
from (
select ROW_NUMBER() OVER(ORDER BY i) AS RowNumber, i
from (
--select distinct in case a number is in there multiple times
select distinct i
from #t
--start after 0 in case there are negative or 0 number
where i > 0
) as a
) as b
where RowNumber <> i
--if there are no missing numbers or no records, get the max record
if #i is null
begin
select #i = isnull(max(i),0) + 1 from #t
end
select #i
In my situation I have a system to generate message numbers or a file/case/reservation number sequentially from 1 every year. But in some situations a number does not get use (user was testing/practicing or whatever reason) and the number was deleted.
You can use a where clause to filter by year if all entries are in the same table, and make it dynamic (my example is hardcoded). if you archive your yearly data then not needed. The sub-query part for mID and mID2 must be identical.
The "union 0 as seq " for mID is there in case your table is empty; this is the base seed number. It can be anything ex: 3000000 or {prefix}0000. The field is an integer. If you omit " Union 0 as seq " it will not work on an empty table or when you have a table missing ID 1 it will given you the next ID ( if the first number is 4 the value returned will be 5).
This query is very quick - hint: the field must be indexed; it was tested on a table of 100,000+ rows. I found that using a domain aggregate get slower as the table increases in size.
If you remove the "top 1" you will get a list of 'next numbers' but not all the missing numbers in a sequence; ie if you have 1 2 4 7 the result will be 3 5 8.
set #newID = select top 1 mID.seq + 1 as seq from
(select a.[msg_number] as seq from [tblMSG] a --where a.[msg_date] between '2023-01-01' and '2023-12-31'
union select 0 as seq ) as mID
left outer join
(Select b.[msg_number] as seq from [tblMSG] b --where b.[msg_date] between '2023-01-01' and '2023-12-31'
) as mID2 on mID.seq + 1 = mID2.seq where mID2.seq is null order by mID.seq
-- Next: a statement to insert a row with #newID immediately in tblMSG (in a transaction block).
-- Then the row can be updated by your app.

MISSING SERIES

Im creating a stored procedure/function in MS SQL which should return the missing series.
Example:
the table "orders" contain field name "ordNo".
ordNo
000001
000003
000005
the functions should return these values:
000002
000004
any idea?
thank you very much.
What about something simple like:
SELECT ordNo - 1
FROM Orders o
WHERE NOT EXISTS (
SELECT *
FROM Orders n
WHERE n.ordNo = o.OrdNo - 1
)
AND ordNo > 1
Edit: Ah - this won't find missing "runs" in the series though. Only single missing numbers.
Here's a version which I think will at least find the "from" and "to" values for "missing" order numbers:
SELECT (SELECT MAX(ordNo) + 1 FROM Orders m WHERE m.ordNo < o.OrdNo) fromOrdNo,
(ordNo - 1) toOrdNo
FROM Orders o
WHERE NOT EXISTS (
SELECT *
FROM Orders n
WHERE n.ordNo = o.OrdNo - 1
)
AND ordNo > 1
the pseudo code for it goes like this:
1.start at 00001
2.Increment by 1 and check if this exists in the table (ordNo).
3.If not exists, return the number, else repeat the process.
Perhaps the following example should help.
-- the table that will have rows numbered from 1 to 1000
select top 1000 identity(int,1,1) as id into #X from syscolumns
-- the table with missing values
select top 200 identity(int,1,2) as id into #Y from syscolumns
-- select * from #x
-- select * from #y
select #x.id, #y.id from #x
left outer join #y on #x.id = #y.id
where #y.id is null
You should have a temp table like #x, which will have all the values (including max value of the row). In above example, I am assuming the range is from 1 to 1000.

Resources