select * from tbl_employees
where ssn in ('123-456-789','777-777-777');
But with the issue that the list should be a parameter at some point (either in the sql procedure itself, or the programming language that calls the sql). And to take the MS SQL Server syntax as example, the following does not return an error, but simply does not work:
-- MS SQL Server
CREATE PROCEDURE get_employees_from_ssn_list @ssn_list VARCHAR(2000)
AS SELECT * FROM tbl_employees WHERE ssn IN (@ssn_list)
exec get_employees_from_ssn_list '123-456-789,777-777-777'
-- MS SQL Server
CREATE PROCEDURE get_employees_from_ssn_list @ssn_list VARCHAR(2000)
AS EXEC('SELECT * FROM tbl_employees
WHERE ssn IN (''' + @ssn_list + ''')');
-- Oracle
CREATE OR REPLACE PROCEDURE get_employees_from_ssn_list
(p_ssn_list IN VARCHAR2, emp_refcur in out SYS_REFCURSOR)
AS
v_SQL varchar(2000);
BEGIN
v_SQL := 'SELECT * FROM tbl_employees WHERE ssn IN (''' || p_ssn_list || ''')';
OPEN emp_refcur FOR v_SQL;
END;
As from MySQL 5.x, it's also possible, but a bit more complicated. Here as to be run using the mysql command line:
-- MySQL
delimiter //;
create procedure get_employees_from_ssn_list(p_ssn_list IN VARCHAR)
begin
prepare stmp from CONCAT('SELECT * FROM tbl_employees
WHERE ssn IN (\'', p_ssn_list , '\')';
execute stmp;
deallocate prepare stmp;
end//
delimiter ;//
Due to the issues noted above, please do NOT use that kind of syntax, but read further!
-- MS SQL Server
CREATE PROCEDURE get_employees_from_ssn_list @ssn_list VARCHAR(2000)
AS SELECT *
FROM tbl_employees
WHERE ',' + @ssn_list + ',' LIKE '%,' + ssn + ',%';
WHERE ',123-456-789,777-777-777,' LIKE '%,123-456-789,%'
Hint: if the field ssn was of numerical data type, you would need to write cast(ssn as varchar(100)), for example.
-- MySQL
delimiter //;
create procedure get_employees_from_ssn_list(p_ssn_list IN VARCHAR)
begin
SELECT *
FROM tbl_employees
WHERE CONCAT(',', p_ssn_list , ',') LIKE CONCAT('%,', ssn, ',%');
end//
You might find the built-in FIND_IN_SET function from MySQL a very tempting alternative:
-- MySQL
delimiter //;
create procedure get_employees_from_ssn_list(p_ssn_list IN VARCHAR)
begin
SELECT *
FROM tbl_employees
WHERE FIND_IN_SET(ssn, p_ssn_list) > 0;
end//
To finish, the Oracle version (I hope you don't get lost in the quotes):
-- Oracle
CREATE OR REPLACE PROCEDURE get_employees_from_ssn_list
(p_ssn_list IN VARCHAR2, emp_refcur in out SYS_REFCURSOR)
AS
v_SQL varchar(2000);
BEGIN
v_SQL := 'SELECT * FROM tbl_employees
WHERE '',' || p_ssn_list || ','' LIKE ''%,'' || ssn || '',%'' ';
OPEN emp_refcur FOR v_SQL;
END;
-- MS SQL Server
CREATE FUNCTION dbo.ParmsToList
(@Parameters varchar(8000), @delimiter varchar(10) )
-- SQL 2005+ version would change the varchar(8000) into varchar(max)
returns @result TABLE (Value varchar(8000), row_num int identity )
AS
begin
declare @dx varchar(9)
if @delimiter is null set @delimiter = ' '
if datalength(@delimiter) < 1 set @delimiter = ' '
set @dx = left(@delimiter, datalength(@delimiter)-1)
declare @Value varchar(8000), @Pos int
set @Parameters = @Parameters + @delimiter
set @Pos = CHARINDEX(@delimiter, @Parameters, 1)
IF REPLACE(@Parameters, @delimiter, @dx) <> ''
BEGIN
WHILE @Pos > 0
BEGIN
SET @Value = LEFT(@Parameters, @Pos - 1)
IF @Value <> ''
BEGIN
INSERT INTO @result (Value)
VALUES (@Value)
END
SET @Parameters = SUBSTRING(@Parameters, @Pos+ datalength(@delimiter),8000)
SET @Pos = CHARINDEX(@delimiter, @Parameters, 1)
END --WHILE @Pos > 0
END -- IF REPLACE(@Parameters, @delimiter, @dx) <> ''
RETURN
END
-- MS SQL Server
CREATE PROCEDURE get_employees_from_ssn_list @ssn_list VARCHAR(2000)
AS SELECT *
FROM tbl_employees
WHERE ssn IN (SELECT VALUE FROM dbo.ParmsToList(@ssn_list,',') );
I will not discuss (here) on how efficient my function is, as that is out of the scope of this article.
-- MS SQL Server
CREATE PROCEDURE get_employees_from_ssn_list @ssn_list VARCHAR(2000)
AS SELECT e.*
FROM tbl_employees e
JOIN dbo.ParmsToList(@ssn_list,',') f
ON f.value = e.ssn
ORDER BY f.row_num;
In MySQL, there is no alternative, unfortunately, as:
-- Oracle
CREATE OR REPLACE TYPE myVarcharTable AS TABLE OF VARCHAR2
/
Now, we can create our function:
-- Oracle
create or replace FUNCTION get_rows_from_list
(l in LONG default null, sep in varchar2 default ',')
return myVarcharTable PIPELINED
as
l_pos INT := 1;
l_next INT;
l_part VARCHAR(500);
begin
select instr( l, sep, l_pos) into l_next FROM DUAL;
while (l_next>0)
loop
select substr(l, l_pos, l_next - l_pos) into l_part FROM DUAL;
pipe row(l_part);
select l_next + 1, instr( l, sep, l_pos)
into l_pos, l_next FROM DUAL;
end loop;
select substr(l, l_pos) into l_part FROM DUAL;
pipe row(l_part);
return;
end;
/
And the usage would be like this:
-- Oracle
CREATE OR REPLACE PROCEDURE get_employees_from_ssn_list
(p_ssn_list IN VARCHAR2, emp_refcur in out SYS_REFCURSOR)
AS
BEGIN
OPEN emp_refcur FOR
SELECT ssn, last_name, first_name
FROM tbl_employees
WHERE ssn IN (SELECT column_value
FROM TABLE(get_rows_from_list(p_ssn_list, ','))
)
;
END;
You now have a method to do this without dynamic sql, though still nice and easy-to-read code, and possibly using a index on ssn field. You will see that you can reuse that function quite often in your developments.
-- MS SQL Server
create function dbo.ParmsToListViaXML
(@Parameters varchar(8000), @delimiter varchar(10) )
returns @result TABLE (Value varchar(8000), row_num int identity )
AS
Begin
declare @XML XML
SET @XML = '<t><r>' + Replace(@Parameters , @delimiter, '</r><r>') + '</r></t>'
INSERT INTO @result (value)
SELECT t.r.value('.','VARCHAR(8000)') AS val
FROM @XML.nodes('/t/r') as t(r)
return
END
The usage is the same as for my other version. Of course, this function could have a brother that accepts the xml directly, or you could use that directly in your procedure, avoiding the additional function call.
-- Oracle
CREATE OR REPLACE PROCEDURE get_employees_from_ssn_list
(p_ssn_list IN VARCHAR2, emp_refcur in out SYS_REFCURSOR)
AS
v_ssn_list varchar2(2000);
BEGIN
v_ssn_list := '<t><r>' || replace( p_ssn_list, ',', '</r><r>') || '</r></t>';
OPEN emp_refcur FOR
SELECT ssn, last_name, first_name
FROM tbl_employees
WHERE ssn IN ( SELECT extract(value(t), '/r/text()').getStringval()
FROM table(xmlsequence( extract(xmltype(v_ssn_list), '/t/r'))) t
)
;
END;
For both MS SQL Server and Oracle codes, not being used to XML in general, it took me a couple of hours to get both working correctly. Hence, don't be discouraged if you don't understand the syntax right away.
-- MS SQL Server
SELECT e.* FROM tmp_employees e
WHERE ssn IN ( select t.SSN FROM temp_table t);
-- MS SQL Server
SELECT e.* FROM tmp_employees e
WHERE EXISTS ( select null FROM temp_table t WHERE t.ssn = e.ssn);
-- MS SQL Server
SELECT e.* FROM tmp_employees e
JOIN temp_table t
ON t.ssn = e.ssn;
Notes:
-- MS SQL Server
CREATE PROCEDURE get_employees_from_ssn_list @ssn_list VARCHAR(2000)
AS SELECT e.*
FROM tbl_employees e
JOIN dbo.ParmsToList(@ssn_list,',') f
ON f.value = e.ssn
ORDER BY f.row_num;
The JOIN with the function also works in Oracle.
Have a question about something in this article? You can receive help directly from the article author. Sign up for a free trial to get started.
Comments (12)
Author
Commented:actually, you should NOT modify the sql function, but pass the data correctly using CDATA tags:
http://en.wikipedia.org/wiki/CDATA
CHeers and thanks for your comment.
a3
Commented:
Thanks for the CDATA tip, that makes sense! I actually spent some time yesterday looking for something like that (as my workaround was kinda dirty...) but alas. So thanks for mentioning this!
I still think that the function should be able to handle special XML characters by itself though, otherwise queries (or reports in my particular case) become too annoying to write. In case someone is interested, here's the updated version using the clean CDATA tags:
Open in new window
Best regards,VV
Commented:
Commented:
http://www.sqlservercentral.com/articles/Tally+Table/72993/
The discussion about the article is incredible. I learned a lot from reading and participating.
http://www.sqlservercentral.com/Forums/Topic1101315-203-1.aspx
Author
Commented:http://www.oracle-developer.net/display.php?id=412
but must admit the code is not really "readable" ...
View More