Please look at the below query..
select name as [Employee Name] from table name.
I want to generate [Employee Name] dynamically based on other column value.
Here is the sample table
s_dt dt01 dt02 dt03
2015-10-26
I want dt01 value to display as column name 26 and dt02 column value will be 26+1=27
I'm not sure if I understood you correctly. If I'am going into the wrong direction, please add comments to your question to make it more precise.
If you really want to create columns per sql you could try a variation of this script:
DECLARE #name NVARCHAR(MAX) = 'somename'
DECLARE #sql NVARCHAR(MAX) = 'ALTER TABLE aps.tbl_Fabrikkalender ADD '+#name+' nvarchar(10) NULL'
EXEC sys.sp_executesql #sql;
To retrieve the column name from another query insert the following between the above declares and fill the placeholders as needed:
SELECT #name = <some colum> FROM <some table> WHERE <some condition>
You would need to dynamically build the SQL as a string then execute it. Something like this...
DECLARE #s_dt INT
DECLARE #query NVARCHAR(MAX)
SET #s_dt = (SELECT DATEPART(dd, s_dt) FROM TableName WHERE 1 = 1)
SET #query = 'SELECT s_dt'
+ ', NULL as dt' + RIGHT('0' + CAST(#s_dt as VARCHAR), 2)
+ ', NULL as dt' + RIGHT('0' + CAST((#s_dt + 1) as VARCHAR), 2)
+ ', NULL as dt' + RIGHT('0' + CAST((#s_dt + 2) as VARCHAR), 2)
+ ', NULL as dt' + RIGHT('0' + CAST((#s_dt + 3) as VARCHAR), 2)
+ ' FROM TableName WHERE 1 = 1)
EXECUTE(#query)
You will need to replace WHERE 1 = 1 in two places above to select your data, also change TableName to the name of your table and it currently puts NULL as the dynamic column data, you probably want something else there.
To explain what it is doing:
SET #s_dt is selecting the date value from your table and returning only the day part as an INT.
SET #query is dynamically building your SELECT statement based on the day part (#s_dt).
Each line is taking #s_dt, adding 0, 1, 2, 3 etc, casting as VARCHAR, adding '0' to the left (so that it is at least 2 chars in length) then taking the right two chars (the '0' and RIGHT operation just ensure anything under 10 have a leading '0').
It is possible to do this using dynamic SQL, however I would also consider looking at the pivot operators to see if they can achieve what you are after a lot more efficiently.
https://technet.microsoft.com/en-us/library/ms177410(v=sql.105).aspx
Related
I'm relatively new to SQL and have been searching for a solution for nearly a week now and wondering whether any expert here could give me some advice please:
For start, I'm using SQL server management tool 2018 for my work. I have two tables X and Y, Table X contains column ID, A, B, C... and table Y contains column ID, Ax, Ay, Bx, By, Cx, Cy etc.
I need update table X , column A=Ax+Ay if Ax>Ay, or A=Ay+Ax if Ax<=Ay, so value in column A will be AxAy or AyAx.
Same with Column B, C etc.
I try to use declare function but it gives error message saying subquery returned more than 1 value, I understand that's probably because the set queries return multiple rows of data.But what's the best way of doing this?
declare #column varchar (20), #x varchar (20), #y varchar(20)
set #column=(select A from TableX);
set #x=(select Ax from TableY)
set #y=(select Ay from TableY)
update TableX
set #column=(#y+#x)
from TableX
inner join TableY
on TableX.ID=TableY.ID
where #x <= #y
Please could you help?
Many thanks in advance.
The simplest way to achieve the "Ax then Ay or Ay then Ax, whichever is smaller" is to use a CASE expression.
CASE when #x < #y then #x + #y else #y + #x END
You cannot assign a column name to a variable and then run SQL that tells it to update the column named in that variable. This is simply not supported in SQL: you must ALWAYS know what columns you are updating at the time you write the statement. The closest equivalent is using dynamic SQL, which is almost always a bad idea.
Your code structure shows you are thinking "row by row" -- do this on the row, then that on the row, then the next thing on the row, and finally move to the next row. Efficient SQL is achieved when you think in sets. You should have one SQL statement, possibly with CTEs to determine individual results.
While I was trying to write the SQL that I would recommend, Максим Золотенко provided a full answer. I'm still posting this because I think the points I've raised are useful to think about.
You can just update each column in a statement instead of using a function. Copy paste can make this really easy.
UPDATE dbo.X
SET x.A = CASE WHEN Ax > Ay THEN Ax+Ay ELSE Ay+Ax END,
x.B = CASE WHEN Bx > By THEN Bx+BY ELSE By+Bx END
FROM X
INNER JOIN Y
ON x.ID = Y.ID
If you want to be able to run this on various columns with the same pattern without adding SET statements to this update query, you will have to use dynamic sql since column names cannot be variables. Here is the dynamic sql.
DECLARE #columnPrefix VARCHAR(20) = 'A';
DECLARE #updateQuery VARCHAR(MAX) = 'UPDATE dbo.X
SET x.' + #columnPrefix + '= CASE WHEN ' + #columnPrefix + 'x > ' + #columnPrefix + 'y THEN ' + #columnPrefix + 'x+' + #columnPrefix + 'y ELSE ' + #columnPrefix + 'y+' + #columnPrefix + 'x END
FROM X
INNER JOIN Y
ON x.ID = Y.ID';
EXEC(#updateQuery);```
You can try this:
update TableX
set TableX.A = case when TableY.Ax <= TableY.Ay then TableY.Ax + TableY.Ay
else TableY.Ay + TableY.Ax
end,
TableX.B = case when TableY.Bx <= TableY.By then TableY.Bx + TableY.By
else TableY.By + TableY.Bx
end,
TableX.C = case when TableY.Cx <= TableY.Cy then TableY.Cx + TableY.Cy
else TableY.Cy + TableY.Cx
end
from TableX
inner join TableY on TableX.ID=TableY.ID
In general, read carefully about UPDATE statement and CASE operator.
Updated, according to #Hong2020 comments:
You can use dynamic SQL to update columns in any combinations. For example, it can be a stored procedure (Updated again at 2020 Jan 9):
create procedure dbo.SP_UPDATE_COLS
#Column nvarchar(100), -- column to be updated
#xCol nvarchar(100),
#yCol nvarchar(100)
as
declare #SQL nvarchar(max) = '
update TableX
set TableX.' + #Column + ' = case when TableY.' + #xCol + ' <= TableY.' + #yCol + ' then ''^''+TableY.' + #xCol + ' + ''^'' + TableY.' + #yCol + '
else ''^'' + TableY.' + #yCol + ' + ''^'' + TableY.' + #xCol + '
end
from TableX
inner join TableY on TableX.ID=TableY.ID
';
exec(#SQL);
--select #SQL;
go
Using SQL Server Azure or 2017 with Full Text Search, I need to return possible matches on names.
Here's the simple scenario: an administrator is entering contact information for a new employee, first name, last name, address, etc. I want to be able to search the Employee table for a possible match on the name(s) to see if this employee has already been entered in the database.
This might happen as an autosuggest type of feature, or simply display some similar results, like here in Stackoverflow, while the admin is entering the data.
I need to prevent duplicates!
If the admin enters "Bob", "Johnson", I want to be able to match on:
Bob Johnson
Rob Johnson
Robert Johnson
This will give the administrator the option of seeing if this person has already been entered into the database and choose one of those choices.
Is it possible to do this type of match on words like "Bob" and include "Robert" in the results? If so, what is necessary to accomplish this?
Try this.
You need to change the #per parameter value to your requirement. It indicates how many letters out of the length of the first name should match for the result to return. I just set it to 50% for testing purposes.
The dynamic SQL piece inside the loop adds all the CHARINDEX result per letter of the first name in question, to all existing first names.
Caveats:
Repeating letters will of course be misleading, like Bob will count 3 matches in Rob because there's 2 Bs in Bob.
I didn't consider 2 first names, like Bob Robert Johnson, etc so this will fail. You can improve on that however, but you get the idea.
The final SQL query gets the LetterMatch that is greater than or equal to the set value in #per.
DECLARE #name varchar(MAX) = 'Bobby Johnson' --sample name
DECLARE #first varchar(50) = SUBSTRING(#name, 0, CHARINDEX(' ', #name)) --get the first part of the name before space
DECLARE #last varchar(50) = SUBSTRING(#name, CHARINDEX(' ', #name) + 1, LEN(#name) - LEN(#first) - 1) --get the last part of the name after space
DECLARE #walker int = 1 --for looping
DECLARE #per float = LEN(#first) * 0.50 --declare percentage of how many letters out of the length of the first name should match. I just used 50% for testing
DECLARE #char char --for looping
DECLARE #sql varchar(MAX) --for dynamic SQL use
DECLARE #matcher varchar(MAX) = '' --for dynamic SQL use
WHILE #walker <> LEN(#first) + 1 BEGIN --loop through all the letters of the first name saved in #first variable
SET #char = SUBSTRING(#first, #walker, 1) --save the current letter in the iteration
SET #matcher = #matcher + IIF(#matcher = '', '', ' + ') + 'IIF(CHARINDEX(''' + #char + ''', FirstName) > 0, 1, 0)' --build the additional column to be added to the dynamic SQL
SET #walker = #walker + 1 --move the loop
END
SET #sql = 'SELECT * FROM (SELECT FirstName, LastName, ' + #matcher + ' AS LetterMatch
FROM TestName
WHERE LastName LIKE ' + '''%' + #last + '%''' + ') AS src
WHERE CAST(LetterMatch AS int) >= ROUND(' + CAST(#per AS varchar(50)) + ', 0)'
SELECT #sql
EXEC(#sql)
SELECT * FROM tbl_Names
WHERE Name LIKE '% user defined text %';
using a text in between % % will search those text on any position in the data.
I want this procedure change the table name when I execute it.
The table name that I want to change is Recargas_#mes
There is some way to do that?
#MES DATETIME
AS
BEGIN
SELECT CUENTA, SUM(COSTO_REC) COSTO_REC
INTO E09040_DEV.BI_PRO_COSTO_RECARGAS
FROM (
SELECT a.*,(CASE
WHEN COD_AJUSTE IN ('ELEC_TEXT','TFREPPVV_C') THEN (A.VALOR)*(R.COSTO) ELSE 0 END)
FROM Recargas_#MES AS A, BI_PRO_LISTA_COSTOS_RECARGAS AS R
WHERE R.ANO_MES = #MES
) D
GROUP BY CUENTA
END
Sample code:
-- Declare variables
DECLARE #MES DATETIME;
DECLARE #TSQL NVARCHAR(MAX);
-- Set the variable to valid statement
SET #TSQL = N'
SELECT CUENTA, SUM(COSTO_REC) AS COSTO_REC
INTO E09040_DEV.BI_PRO_COSTO_RECARGAS
FROM (
SELECT A.*,
(CASE
WHEN COD_AJUSTE IN (''ELEC_TEXT'',''TFREPPVV_C'') THEN
(A.VALOR)*(R.COSTO)
ELSE 0
END)
FROM
Recargas_' + REPLACE(CONVERT(CHAR(10), #MES, 101), '/', '') + ' AS A,
BI_PRO_LISTA_COSTOS_RECARGAS AS R
WHERE R.ANO_MES = ' + CONVERT(CHAR(10), #MES, 101) + '
) D
GROUP BY CUENTA'
-- Execute the statement
EXECUTE (#SQL)
Some things to note:
1 - I assume the table name has some type of extension that is a date? I used MM/DD/YYYY and removed the slashes as a format for the suffix.
2 - The WHERE clause will only work if you are not using the time part of the variable.
For instance, 03/15/2016 00:00:00 would be date without time entry. If not, you will have to use >= and < to grab all hours for a particular day.
3 - You are creating a table on the fly with this code. On the second execution, you will get a error unless you drop the table.
4 - You are not using the ON clause when joining table A to table R. To be ANSI compliant, move the WHERE clause to a ON clause.
5 - The actual calculation created by the CASE statement is not give a column name.
Issues 3 to 5 have to be solved on your end since I do not have the detailed business requirements.
Have Fun.
It should work using dynamic SQL to allow putting a dynamic table name:
DECLARE #SQL NVARCHAR(MAX) = N'
SELECT CUENTA, SUM(COSTO_REC) COSTO_REC
INTO E09040_DEV.BI_PRO_COSTO_RECARGAS
FROM (
SELECT a.*,(CASE
WHEN COD_AJUSTE IN (''ELEC_TEXT'',''TFREPPVV_C'') THEN (A.VALOR)*(R.COSTO) ELSE 0 END)
FROM Recargas_' + #MES + ' AS A, BI_PRO_LISTA_COSTOS_RECARGAS AS R
WHERE R.ANO_MES = ' + CAST(#MES AS VARCHAR(32)) + '
) D
GROUP BY CUENTA'
EXECUTE (#SQL)
I have this portion of the query where I'm doing this.
FROM #Table2 WHERE Attribute = ISNULL('+#Attrb+', Attribute)) AS D
Obviously this isn't working for me as #Attr can bring in a string or can be empty. is there a better way to handle this?
What I need is, this a parameter that is passed to this stored procedure. There are two possible choices (or a third one is empty). I'm allowing the users to type it in. How would you tackle this?
Thanks.
FROM #Table2 ' + ISNULL('WHERE Attribute = ''' + NULLIF(#Attrb,'') + '''', '') + ') AS D
Example
CREATE TABLE #Table2 (Attribute VARCHAR(MAX))
INSERT INTO #Table2 VALUES
('Id'),('Class'),('Name')
DECLARE #Attrb VARCHAR(MAX),
#SQL VARCHAR(MAX)
--Attrb is NULL
SET #SQL = ' SELECT * FROM #Table2 ' + ISNULL('WHERE Attribute = ''' + NULLIF(#Attrb,'') + '''', '')
EXEC(#SQL)
/*Result
Attribute
---------
Id
Class
Name
*/
--Give Attrb a value
SET #Attrb = 'Class'
SET #SQL = ' SELECT * FROM #Table2 ' + ISNULL('WHERE Attribute = ''' + NULLIF(#Attrb,'') + '''', '')
EXEC(#SQL)
/*Result
Attribute
---------
Class
*/
You haven't indicated what version of SQL server you are using, but if you are using 2012 or later, you might want to check iif
Example for testing:
declare #Attrib varchar(20)
set #Attrib = NULL
select iif(#Attrib is null,'Null Result',#Attrib)
I am not sure what the string literals are doing in there. I am pretty sure you want something like this.
WHERE Attribute = ISNULL(#Attrb, Attribute)
Or if you are trying to deal with NULL or an empty string you can use NULLIF
WHERE Attribute = ISNULL(NULLIF(#Attrb, ''), Attribute)
--EDIT--
Modifying this to work with dynamic sql is pretty straight forward.
'WHERE Attribute = ISNULL(NULLIF(' + ISNULL(#Attrb, Attribute) + ', ''''), Attribute)'
I have a field in my table which has multiple reason codes concatenated in 1 column.
e.g. 2 records
Reason_Codes
Record1: 001,002,004,009,010
Record2: 001,003,005,006
In my SSRS report the user will be searching for data using one of the above reason codes. e.g.
001 will retrieve both records.
005 will retrieve the second record
and so on.
Kindly advise how this can be achieved using SQL or Stored Procedure.
Many thanks.
If you are just passing in a single Reason Code to search on, you don't even need to bother with splitting the comma-separated list: you can just use a LIKE clause as follows:
SELECT tb.field1, tb.field2
FROM SchemaName.TableName tb
WHERE ',' + tb.Reason_Codes + ',' LIKE '%,' + #ReasonCode + ',%';
Try the following to see:
DECLARE #Bob TABLE (ID INT IDENTITY(1, 1) NOT NULL, ReasonCodes VARCHAR(50));
INSERT INTO #Bob (ReasonCodes) VALUES ('001,002,004,009,010');
INSERT INTO #Bob (ReasonCodes) VALUES ('001,003,005,006');
DECLARE #ReasonCode VARCHAR(5);
SET #ReasonCode = '001';
SELECT tb.ID, tb.ReasonCodes
FROM #Bob tb
WHERE ',' + tb.ReasonCodes + ',' LIKE '%,' + #ReasonCode + ',%';
-- returns both rows
SET #ReasonCode = '005';
SELECT tb.ID, tb.ReasonCodes
FROM #Bob tb
WHERE ',' + tb.ReasonCodes + ',' LIKE '%,' + #ReasonCode + ',%';
-- returns only row #2
I have blogged about something like this a long time ago. May be this will help: http://dotnetinternal.blogspot.com/2013/10/comma-separated-to-temp-table.html
The core solution would be to convert the comma separated values into a temporary table and then do a simple query on the temporary table to get your desired result.