SQL Query data based on a variable - sql

I have a simple query that I am trying to adapt to a new situation. For this situation, if the variable locationID=1 then I need all records, regardless of its locationID.
However, if it is not equal to 1, I need to only provide records that match that of the #locationID.
Here is my setup example:
DECLARE #locationID INT = 1; -- All Results Regardless of locationID
--DECLARE #locationID INT = 2; -- Only results that match this locationID (2)
--DECLARE #locationID INT = 3; -- Only results that match this locationID (3)
-- Temp Data
DECLARE #temp AS TABLE (color VARCHAR(10), locationID INT, name VARCHAR(20))
INSERT INTO #temp( color, locationID, name )VALUES ( 'Blue', 1, 'Test 1' )
INSERT INTO #temp( color, locationID, name ) VALUES ( 'Red', 2, 'Test 2' )
INSERT INTO #temp( color, locationID, name ) VALUES ( 'Red', 1, 'Test 3' )
INSERT INTO #temp( color, locationID, name ) VALUES ( 'Red', 2, 'Test 4' )
INSERT INTO #temp( color, locationID, name ) VALUES ( 'Red', 3, 'Test 5' )
-- Query
SELECT *
FROM #temp
WHERE
locationID = ...
I am trying to figure out if I need to use a CASE WHEN or some other method for this.

WHERE (locationID = #LocationId OR #locationID = 1)

Try this:
...WHERE (#LocationId = 1 AND 1=1)
OR (#LocationId <> 1 AND LocationId = #LocationId)

This seems devilishly simple, perhaps I am not understanding your question. If you want to query by a variable value, then just use the variable value:
DECLARE #LocationId INT = 1
SELECT *
FROM #temp
WHERE (#LocationId = 1 AND 1=1)
OR (#LocationId <> 1 AND LocationId = #LocationId)

A couple of other ideas. Since using a constant of 1 is not exactly intuitive or self-documenting, you could default that to NULL. And then say:
WHERE LocationID = COALESCE(#LocationID, LocationID);
If LocationID column is nullable, then instead you could use some token value that is at least slightly more self-documenting than 1, like -1 (since I don't think anyone looking at the code will immediately know that there isn't really a valid row where LocationID = 1):
WHERE LocationID = COALESCE(NULLIF(#LocationID, -1), LocationID);
This is susceptible to parameter sniffing, so you might want to add OPTION (RECOMPILE) if you find you are often switching between "all rows" and "very specific rows." You can also use dynamic SQL to optionally build the WHERE clause, assuming your real scenario uses a real table:
DECLARE #sql nvarchar(max) = N'SELECT ... FROM dbo.RealTable'
IF #LocationID <> 1 -- or IS NOT NULL or <> -1 or what have you
BEGIN
SET #sql += N' WHERE LocationID = #LocationID';
END
EXEC sys.sp_executesql #sql, N'#LocationID int', #LocationID;
This way you get a different plan when the parameter is included vs. when it is not, which doesn't really make the scan (where you need all rows) any better, but it guards against the case where you use a seek + lookup against all rows, which can be really bad. More often, it prevents you from getting the dreadful full table/index scan when you really could have used a seek. But which way that goes is completely dependent on which case is more likely the first time the query runs.
And even here you may want to use OPTION (RECOMPILE) at the end of #sql if the distribution of data across different LocationID values is (or might later become) heavily skewed.
I call this type of query "the kitchen sink," and have written about it here:
#BackToBasics : An Updated "Kitchen Sink" Example

So the variable should be 1 or the locationid?
You could use an IN for that.
... WHERE #LocationID IN (1, LocationID)
Example snippet:
declare #locationID int;
-- Test data using a table variable
declare #varTable table (id int identity(1,1) primary key, locationID int, name varchar(20), color varchar(10));
insert into #varTable (locationID, name, color) values
(1,'Test 1','Blue'),
(2,'Test 2','Red'),
(1,'Test 3','Red'),
(2,'Test 4','Red'),
(3,'Test 5','Red');
-- Returning all records
set #locationID = 1;
SELECT t.* FROM #varTable t WHERE #locationID IN (1, t.locationID);
-- Only returning those with locationID = 2
set #locationID = 2;
SELECT t.* FROM #varTable t WHERE #locationID IN (1, t.locationID);

Related

SQL: how can I set a variable in this context?

I'm trying to set a couple of variables, one is a name of the table (#table), the other one is a string.
I've got the error message:
Must declare the table variable "#table".
as far as I could understand it's because I must use #table as a table variable, but I just need it as a string
declare #a varchar(50);
declare #table varchar(100);
select #table =
case
WHEN Version = 'Advanced' THEN ("tableadv")
WHEN Version = 'Professional' THEN ("tablepro")
WHEN Version = 'Light' THEN ("tablelight")
WHEN Version = 'Short' THEN ("tableshort")
END
FROM partno where inpn=3
set #a = (select top (1) LicenseNumber from #table where used is null)
insert into seriali (LicenseNumber, idpn, serdgtrace)
select #a, 2, 'DAT-enrico'
update #table set used = 1 where LicenseNumber=#a
any help will be appreciated.
Many thanks,
enrico
You need table variable not only variable to store more values with more columns not just single value :
declare #table table (
#col int,
. . .
)
insert into #table (col)
select case WHEN Version = 'Advanced' THEN 'tableadv'
WHEN Version = 'Professional' THEN 'tablepro'
WHEN Version = 'Light' THEN 'tablelight'
WHEN Version = 'Short' THEN 'tableshort'
end
from partno
where inpn = 3
hello Yogesh and thanks for the answer,
anyway I need this case structure to pick the right table name based on a field in another table.
Then I have to pass this value to update the right table;
set #a = (select top (1) LicenseNumber from #table where used is null)
update #table set used = 1 where LicenseNumber=#a
I don't need to build a table variable with one of the case values in a record, because I won't be able to pass the right table name either
Many thanks,
Enrico

SQL search in two columns with one combined value

I will try to demonstrate what I am trying to achieve. This is an oversimplified example for my case.
Suppose I have a table contains two columns
ID YEAR
--- ----
1 2017
2 2018
and I have a search term 2017 / 1
What I want to do is something like this
select * from table where 'YEAR / ID' LIKE '%searchterm%'
Is this possible ?
Thanks in advance.
In my opinion the most effective way is:
Firstly divide String x = "2017 / 1" to two int values int year = 2017, int id = 1. I don't know what kind of programing language you are using but all of programing languages have special functions to make it easily (between all values you have '/').
Then use this query:
Select *
from table
where year = 2017
and id = 1
Use Below query, I have considered your search text format as 2017 / 1.
DECLARE #tblTest AS Table
(
Id INT,
YearNo INT
)
INSERT INTO #tblTest values (1,2017)
INSERT INTO #tblTest values (2,2018)
INSERT INTO #tblTest values (3,2017)
INSERT INTO #tblTest values (4,2018)
DECLARE #searchterm VARCHAR(50)='2017 / 1'
LEFT will give you string starting from left position to applied length.
RIGHT will give you string starting from right position to applied length
SELECT
*
FROM #tblTest
WHERE YearNo=LEFT(#searchterm,4)
AND Id = REPLACE(RIGHT(#searchterm,LEN(#searchterm)-(CHARINDEX('/',(REPLACE(#searchterm, ' ', ''))))),'/','')
If your database compatibility could be 130 then You can Try String_Split ref https://learn.microsoft.com/en-us/sql/t-sql/functions/string-split-transact-sql
Sql most long awaited function (as msdn says)
Declare #tbl table (id int Identity(1,1), value nvarchar(5))
Insert into #tbl ([value]) SELECT value from STRING_SPLIT(#searchstring,'/')
Declare #id int
Select #id = cast(value as int) from #tbl where id=2 --will give 1
Declare #value int
Select #id = cast(value as int) from #tbl where id=1 --ill give 2017
-- —now use them in sql
select * from table where YEAR=#value and ID = #id
You are going to screw up the performance if you do anything like below
select * from table where 'YEAR / ID' LIKE '%searchterm%'
Best way is you can split your search and supply to respective col
Declare #Search varchar(15)='2017/1'
Declare #Year int = (select LEFT(#Search,CHARINDEX('/',#search)-1))
Declare #month int = (select Right(#Search,(len(#search) -CHARINDEX('/',#search))))
select * from #temp where id=#month and year=#Year
Try this code :
select * from table where YEAR + ' / ' + ID LIKE '%searchterm%'
this query will run, but it will perform very poor.

Need to use `IN` for multiple string value in static query

SELECT *
FROM #empInfo
WHERE source IN (CASE WHEN #source = 'Mint' THEN 'Mint, Ming' ELSE #source END)
I am trying in the way as above.
CREATE TABLE #empInfo
(
id INT IDENTITY(1, 1),
ename VARCHAR(50),
source VARCHAR(50)
)
INSERT INTO #empInfo
VALUES ('Jon', 'Mint'), ('Jack', 'Ryan'), ('Jackie', 'Ming'),
('Jeny', 'Wing'), ('Jen', 'Wing')
DECLARE #source VARCHAR(50)
SET #source = 'Mint'
From the above what if want is, if the value Mint comes in the #source then it has to search for Mint,Ming else what comes in #source. #source always hold single value.
I have to apply this logic in very large query which is not dynamic nor I can make it, so please provide me the solution for this case if any.
Sorry, if it's not possible without dynamic query.
Thank you very much in advance.
SELECT * FROM #empInfo
WHERE (#source = 'Mint' and source in ('Mint','Ming'))
OR (#source <> 'Mint' and source = #source)
Remember that an IN clause needs separate string values. So you can't use a comma separated list in #source
A shorter version of the above query would be
SELECT * FROM #empInfo
WHERE #source = source
OR (#source = 'Mint' and source = 'Ming')

SQL Server: efficiently search for many values on many to many columns?

I am creating a website using SQL Server. In the admin interface, I have two fields:
Subject: Math, English, History, ...
Grade: 1, 2, 3, 4, ...
Multiple values of a field can be assigned to a record.
Now in the frontend search, I would like a visitor to be able to select more than one value of a field for search. For example, someone may search for Subject being Math OR History and Grade being 1 OR 3.
What table design and SQL statement (or MS-proprietary statement) should I use to have efficient search?
Thanks and regards.
UPDATE
Thanks for all input!
I feel compelled to explain. I am technical and familiar with SQL. One thing I learned over my MANY years of programming experience is to be practical. For this question, I already have an initial design, but not sure how other folks to handle it for EFFICIENT SEARCH (there are always smarter folks out there). Here is my table design for storing a record:
Subject
type: varchar. record example: ,1,3, (each is the id of corresponding value)
Grade (this means applicable grade)
type: varchar. record example: ,1,2, (each is the id of corresponding values. this means a record is applicable to grade 1, 2)
Search example
where (subject LIKE '%,1,%' OR subject like '%,3,%') AND (grade like '%,1,%')
This design should lead to efficient search, but has drawbacks that it increases the complexity data management in the backend.
Another reason for my design is: the Subject and Grade each have a list of values that never/rarely change, and once a record is created, it never or rarely updates.
I am trying to strike a balance among simplicity, understandability, design, management, etc.
create table Subject (
SubjectId int identity(1, 1),
SubjectName nvarchar(255),
other fields.... )
create table GradingScale (
GradeId int identity(1, 1),
Grade int,
Description varchar(25),
other fields... )
create table Students (
StudentId int identity(1, 1),
StudentName nvarchar(255))
create table StudentGrades (
StudentId int,
SubjectId int,
GradeId int,
SemesterId int )
create FUNCTION [dbo].[fnArray] ( #Str varchar(max), #Delim varchar(1) = ' ', #RemoveDups bit = 0 )
returns #tmpTable table ( arrValue varchar(max))
as
begin
declare #pos integer
declare #lastpos integer
declare #arrdata varchar(8000)
declare #data varchar(max)
set #arrdata = replace(#Str,#Delim,'|')
set #arrdata = #arrdata + '|'
set #lastpos = 1
set #pos = 0
set #pos = charindex('|', #arrdata)
while #pos <= len(#arrdata) and #pos <> 0
begin
set #data = substring(#arrdata, #lastpos, (#pos - #lastpos))
if rtrim(ltrim(#data)) > ''
begin
if #RemoveDups = 0
begin
insert into #tmpTable ( arrValue ) values ( #data )
end
else
begin
if not exists( select top 1 arrValue from #tmpTable where arrValue = #data )
begin
insert into #tmpTable ( arrValue ) values ( #data )
end
end
end
set #lastpos = #pos + 1
set #pos = charindex('|', #arrdata, #lastpos)
end
return
end
select *
from Students st
inner join StudentGrades sg on sg.StudentId = st.StudentId
inner join Subject s on sg.SubjectId = s.SubjectId
inner join GradingScale gs on sg.GradeId = gs.GradeId
inner join dbo.fnArray(#subjects, ',') sArr on s.SubjectId = convert(int, sArr.arrValue)
inner join dbo.fnArray(#grades, ',') gArr on gs.GradeId = convert(int, gArr.arrValue)
obviously #subjectId and #gradeId could be passed in via some drop down selectors or however your UI is composed.
Edited to use dbo.fnArray, a little tool that can parse delimited strings into a list.
Now of course this would mean that if you had 2 subjects and 2 grades...like Show me all students that took ( Math and Science ) and scored ( 2 or 3 ) this would work. However if you wanted students who took Math and scored 2 or 3 or Students who took Science and scored a 3 you would have to rework the query.

How to get query result with stored procedure (convert item quantity from one table into my unit defined in second table)

I have two MSSQL2008 tables like this:
I have problem on the unit conversion logic.
The result I expect like this :
1589 cigar = 1ball, 5slop, 8box, 2pcs
52 pen = 2box, 12pcs
Basically I'm trying to take number (qty) from one table and to convert (split) him into the units which I defined in other table!
Note : Both table are allowed to add new row and new data (dinamic)
How can I get these results through a SQL stored procedure?
i totally misunderstand the question lest time so previous answer is removed (you can see it in edit but it's not relevant for this question)... However i come up with solution that may solve your problem...
NOTE: one little think about this solution, if you enter the value in second table like this
+--------+-------+
| Item | qty |
+--------+-------+
| 'cigar'| 596 |
+--------+-------+
result for this column will be
598cigar = 0ball, 5slop, 8box, 0pcs
note that there is a ball and pcs is there even if their value is 0, that probably can be fix if you don't want to show that value but I let you to play with it...
So let's back to solution and code. Solution have two stored procedures first one is the main and that one is the one you execute. I call it sp_MainProcedureConvertMe. Here is a code for that procedure:
CREATE PROCEDURE sp_MainProcedureConvertMe
AS
DECLARE #srcTable TABLE(srcId INT IDENTITY(1, 1), srcItem VARCHAR(50), srcQty INT)
DECLARE #xTable TABLE(xId INT IDENTITY(1, 1), xVal1 VARCHAR(1000), xVal2 VARCHAR(1000))
DECLARE #maxId INT
DECLARE #start INT = 1
DECLARE #sItem VARCHAR(50)
DECLARE #sQty INT
DECLARE #val1 VARCHAR(1000)
DECLARE #val2 VARCHAR(1000)
INSERT INTO #srcTable (srcItem, srcQty)
SELECT item, qty
FROM t2
SELECT #maxId = (SELECT MAX(srcId) FROM #srcTable)
WHILE #start <= #maxId
BEGIN
SELECT #sItem = (SELECT srcItem FROM #srcTable WHERE srcId = #start)
SELECT #sQty = (SELECT srcQty FROM #srcTable WHERE srcId = #start)
SELECT #val1 = (CAST(#sQty AS VARCHAR) + #sItem)
EXECUTE sp_ConvertMeIntoUnit #sItem, #sQty, #val2 OUTPUT
INSERT INTO #xTable (xVal1, xVal2)
VALUES (#val1, #val2)
SELECT #start = (#start + 1)
CONTINUE
END
SELECT xVal1 + ' = ' + xVal2 FROM #xTable
GO
This stored procedure have two variables as table #srcTable is basically your second table but instead of using id of your table it's create new srcId which goes from 1 to some number and it's auto_increment it's done because of while loop to avoid any problems when there is some deleted values etc. so we wanna be sure that there wont be any skipped number or something like that.
There is few more variables some of them is used to make while loop work other one is to store data. I think it's not hard to figure out from code what are they used for...
While loop iterate throughout all rows from #srcTable take values processing them and insert them into #xTable which basically hold result.
In while loop we execute second stored procedure which have a task to calculate how many unit of something is there in specific number of item. I call her sp_ConvertMeIntoUnit and here is a code for her:
CREATE PROCEDURE sp_ConvertMeIntoUnit
#inItemName VARCHAR(50),
#inQty INT,
#myResult VARCHAR(5000) OUT
AS
DECLARE #rTable TABLE(rId INT IDENTITY(1, 1), rUnit VARCHAR(50), rQty INT)
DECLARE #yTable TABLE(yId INT IDENTITY(1, 1), yVal INT, yRest INT)
DECLARE #maxId INT
DECLARE #start INT = 1
DECLARE #quentity INT = #inQty
DECLARE #divider INT
DECLARE #quant INT
DECLARE #rest INT
DECLARE #result VARCHAR(5000)
INSERT INTO #rTable(rUnit, rQty)
SELECT unit, qty
FROM t1
WHERE item = #inItemName
ORDER BY qty DESC
SELECT #maxId = (SELECT MAX(rId) FROM #rTable)
WHILE #start <= #maxId
BEGIN
SELECT #divider = (SELECT rQty FROM #rTable WHERE rId = #start)
SELECT #quant = (#quentity / #divider)
SELECT #rest = (#quentity % #divider)
INSERT INTO #yTable(yVal, yRest)
VALUES (#quant, #rest)
SELECT #quentity = #rest
SELECT #start = (#start + 1)
CONTINUE
END
SELECT #result = COALESCE(#result + ', ', '') + CAST(y.yVal AS VARCHAR) + r.rUnit FROM #rTable AS r INNER JOIN #yTable AS y ON r.rId = y.yId
SELECT #myResult = #result
GO
This procedure contain three parametars it's take two parameters from the first one and one is returned as result (OUTPUT). In parameters are Item and Quantity.
There are also two variables as table #rTable we stored values as #rId which is auto increment and always will go from 1 to some number no matter what is there Id's in the first table. Other two values are inserted there from the first table based on #inItemName parameter which is sanded from first procedure... From the your first table we use unit and quantity and stored them with rId into table #rTable ordered by Qty from biggest number to lowest. This is a part of code for that
INSERT INTO #rTable(rUnit, rQty)
SELECT unit, qty
FROM t1
WHERE item = #inItemName
ORDER BY qty DESC
Then we go into while loop where we do some maths. Basically we store into variable #divider values from #rTable. In the first iteration we take the biggest value calculate how many times it's contain into the number (second parameter we pass from first procedure is qty from the yours second table) and store it into #quant than we also calculate modulo and store it into variable #rest. This line
SELECT #rest = (#quentity % #divider)
After that we insert our values into #yTable. Before we and with iteration in while loop we assign #quentity variable value of #rest value because we need to work just with the remainder not with whole quantity any more. In second iteration we take next (the second greatest number in our #rTable) number and procedure repeat itself...
When while loop finish we create a string. This line here:
SELECT #result = COALESCE(#result + ', ', '') + CAST(y.yVal AS VARCHAR) + r.rUnit FROM #rTable AS r INNER JOIN #yTable AS y ON r.rId = y.yId
This is the line you want to change if you want to exclude result with 0 (i talk about them at the beginning of answer)...
And at the end we store result into output variable #myResult...
Result of this stored procedure will return string like this:
+--------------------------+
| 1ball, 5slop, 8box, 2pcs |
+--------------------------+
Hope I didn't miss anything important. Basically only think you should change here is the name of the table and their columns (if they are different) in first stored procedure instead t2 here
INSERT INTO...
SELECT item, qty
FROM t2
And in second one instead of t1 (and column if needed) here..
INSERT INTO...
SELECT unit, qty
FROM t1
WHERE item = #inItemName
ORDER BY qty DESC
Hope i help a little or give you an idea how this can be solved...
GL!
You seem to want string aggregation – something that does not have a simple instruction in Transact-SQL and is usually implemented using a correlated FOR XML subquery.
You have not provided names for your tables. For the purpose of the following example, the first table is called ItemDetails and the second one, Items:
SELECT
i.item,
i.qty,
details = (
SELECT
', ' + CAST(d.qty AS varchar(10)) + d.unit
FROM
dbo.ItemDetails AS d
WHERE
d.item = i.item
FOR XML
PATH (''), TYPE
).value('substring(./text()[1], 3)', 'nvarchar(max)')
FROM
dbo.Items AS i
;
For the input provided in the question, the above query would return the following output:
item qty details
----- ----------- ------------------------------
cigar 1598 1pcs, 1000ball, 12box, 100slop
pen 52 1pcs, 20box
You can further arrange the data into strings as per your requirement. I would recommend you do it in the calling application and use SQL only as your data source. However, if you must, you can do the concatenation in SQL as well.
Note that the above query assumes that the same unit does not appear more than once per item in ItemDetails. If it does and you want to aggregate qty values per unit before producing the detail line, you will need to change the query a little:
SELECT
i.item,
i.qty,
details = (
SELECT
', ' + CAST(SUM(d.qty) AS varchar(10)) + d.unit
FROM
dbo.ItemDetails AS d
WHERE
d.item = i.item
GROUP BY
d.unit
FOR XML
PATH (''), TYPE
).value('substring(./text()[1], 3)', 'nvarchar(max)')
FROM
dbo.Items AS i
;