String_split into table - sql-server

I have a stored procedure which takes two parameters.
Parameter #1: StudentID (int) (e.g. 4, 7, ...)
Parameter #2: Books to lendIds (string) (contains IDs (int) separated by a semicolon, which books should be lent) like 3;45;83;37.
Now I have a table with two columns:
StudentID (Int) | BookID (Int)
I want the procedure to separate the IDs in the second parameter with STRING_SPLIT ( #parameter2 , ';') and then insert them into the table and write the studentID in front of each.
Example:
#Parameter1 = 4
#parameter2 = 3;45;83;37
This should result as data in the table as follows:
StudentID (Int) | BookID (Int)
----------------+----------------
4 | 3
4 | 45
4 | 83
4 | 37
Unfortunately I have no idea how to make this work. Can you please help me or give me tips or keywords what I could look for to find the solution?

You may use STRING_SPLIT as follows:
SELECT #parameter1 AS StudentID, value AS BookID
FROM STRING_SPLIT(#parameter2, ';');

Related

SQL Help - Find all distinct IDs that are not equal to a value - There are multiple IDs that are the same

I am trying to return the IDs in a table where the IDs do not contain a value in another column. There are multiple IDs in the table that are the same, however the second column contains a different value. I want to return all of the distinct IDs where the second column does not contain a value of 10.
For example my data table looks like:
ID | Value
32 | 8
32 | 9
32 | 10
35 | 8
35 | 9
As per the table above I want to return all the IDs that do not contain 10, however when I run the following script I am getting back a result that gives me all the values that contain 8 & 9 and only omits 10 when I want just the distinct ID that have no rows that all contain 10 in the Values column.
I am also joining to another table.
select distinct table1.ID, table2.Values
from table1
left join table2 on table1.ID=table2.ID
where table2.Values <> 10
One method would be a HAVING:
SELECT ID
FROM dbo.YourTable
GROUP BY ID
HAVING COUNT(CASE [Value] WHEN 10 THEN 1 END) = 0;

Between two different range numbers

I have a stored procedure that has a table for a parameter with two columns: From and To. Both int. It is used for searching scores.
The example of the table is
+-----------+-------+----+
| RowNumber | From | To |
+-----------+-------+----+
| 1 | 0 | 30 |
| 2 | 60 | 80 |
+-----------+-------+----+
How can I search a table to have results that include all scores between 0 and 30 and 60 and 80?
I had tried between inside a while loop but nothing.
This is a guess in the absence of a reply, however, maybe...
CREATE TABLE Score (ID int IDENTITY(1,1),
Score int);
INSERT INTO Score
VALUES (65),(17),(97),(14),(34),(79),(37),(87),(65),(63),(15),(75),(05),(25),(38),(28),(88);
GO
CREATE TABLE ScoreRange (ID int IDENTITY(1,1),
[From] int, --Try to avoid keywords, and especially reserved words, for column names
[To] int); --Try to avoid keywords, and especially reserved words, for column names
INSERT INTO ScoreRange
VALUES (0,30),
(60,80);
GO
SELECT *
FROM Score S;
SELECT S.*
FROM Score S
JOIN ScoreRange SR ON S.Score BETWEEN SR.[From] AND SR.[To];
GO
DROP TABLE Score;
DROP TABLE ScoreRange;
It's kinda hard to answer without sample data - but I think you are looking for something like this:
SELECT t.*
FROM YourTable As t
JOIN #TVP As p ON t.Score >= p.[From] AND t.Score <= p.[To]
select * from t
where exists (
select 1 from ranges r
where t.val between r.from and r.to
);

How to display data horizontally in SQL Server?

How do I display my table data horizontally?
This is my table definition
create table [User]
(
Id int primary key identity(1,1),
Name varchar(50),
Gender varchar(10)
)
This is the data I have in my SQL Server table
+====+=======+========+
| Id | Name | Gender |
+====+=======+========+
| 1 | Fahad | Male |
+----+-------+--------+
| 2 | Saad | Male |
+----+-------+--------+
| 3 | Asif | Male |
+====+=======+========+
and I want to show it horizontally like this
+========+=======+======+======+
| Id | 1 | 2 | 3 |
+========+=======+======+======+
| Name | Fahad | Saad | Asif |
+--------+-------+------+------+
| Gender | Male | Male | Male |
+========+=======+======+======+
Perhaps a combination of UNPIVOT and PIVOT?
(Although your columns need to be of the same type for this to work, which I've changed in your table, or you can just CAST in a SELECT/CTE etc)
CREATE table [User](
Id int primary key identity(1,1),
Name varchar(50),
Gender varchar(50)
)
SET IDENTITY_INSERT [User] ON
INSERT INTO [User](Id,Name,Gender) VALUES
(1, 'Fahad','Male'),
(2,'Saad','Male'),
(3,'Asif','Male')
SELECT * FROM [User]
UNPIVOT ([Value] FOR Cols IN ([Name],[Gender])) Unp
PIVOT (MAX([Value]) FOR Id IN ([1],[2],[3])) Piv
Cols 1 2 3
------ ------ ------ -------
Gender Male Male Male
Name Fahad Saad Asif
(2 row(s) affected)
CASE can also be used to achieve the same - there are tons of examples on SO.
Edit: Excellent example Simple way to transpose columns and rows in Sql?
(and this is probably a dup of that question)
Yes, it seems we might need to do combination of UNPIVOT and PIVOT.
Try below, It may provide you the exact result as what you expect. Please change your design first
Gender varchar(10) to Gender varchar(50)
Try below,
;WITH cte AS(
SELECT *
FROM [User]
UNPIVOT([Value] FOR Cols IN ([Name], [Gender])) Unp
PIVOT(MAX([Value]) FOR Id IN ([1], [2], [3])) Piv
)
SELECT Cols AS Id,
[1],
[2],
[3]
FROM cte
ORDER BY
Id DESC
Here is a stored procedure that works on any given table. It presumes that the table key is in the first column.
IF OBJECT_ID(N'[Invert]','P') IS NOT NULL
DROP PROCEDURE [Invert]
GO
CREATE PROCEDURE dbo.[Invert] #tbl sysname, #top int=1000 AS
DECLARE #key sysname SELECT #key=COLUMN_NAME FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_NAME=#tbl AND ORDINAL_POSITION=1
DECLARE #sql nvarchar(max), #ids varchar(max)
SET #sql='SELECT TOP '+CAST(#top as varchar(9))+' #out=COALESCE(#out+'','','''')+QUOTENAME('
+QUOTENAME(#key)+') FROM '+QUOTENAME(#tbl)+' ORDER BY '+QUOTENAME(#key)
EXECUTE sp_executesql #sql, N'#out varchar(max) OUTPUT', #out=#ids OUTPUT
SET #sql=NULL
SELECT #sql=COALESCE(#sql+' UNION ALL ','')+'SELECT '''+COLUMN_NAME+''' AS '+QUOTENAME(#key)
+ ',* FROM (SELECT TOP '+CAST(#top as varchar(9))+' '+QUOTENAME(#key)+' k,CAST('
+ QUOTENAME(COLUMN_NAME)+'as varchar(8000)) m FROM '+QUOTENAME(#tbl)
+' ORDER BY '+QUOTENAME(#key)+') t PIVOT (MAX(m) FOR k IN ('+#ids+')) x'+CHAR(13)
FROM INFORMATION_SCHEMA.COLUMNS c WHERE TABLE_NAME=#tbl AND c.ORDINAL_POSITION>1
ORDER BY c.ORDINAL_POSITION
EXECUTE(#sql)
GO
The stored procedure uses PIVOT to pivot each column. UNPIVOT is nice, but can only be used if all the columns have the same type (including length). The procedure generates a dynamic SELECT that uses UNION ALL operator to combine PIVOTs for each column (except the key). The list of key values (#ids) is also dynamically generated because the PIVOT command expects an explicit column list.
Then you can call it like this:
EXEC Invert [User]
The second optional parameter is the top clause (the default is 1000). Below is an example that returns a maximum of 5 rows:
EXEC Invert [User], 5
create table [User]
(
Id int primary key identity(1,1),
Name varchar(50),
Gender varchar(50),sal varchar(50)
)SET IDENTITY_INSERT [User] ON
--give same data type and size to all of field
INSERT INTO [User](Id,Name,Gender,sal) VALUES
(1, 'Fahad','Male',10000),
(2,'Saad','Male',20000),
(3,'Asif','Male',30000)
SELECT * FROM [User]
UNPIVOT ([Val] FOR Cols IN (name,gender,sal)) Unp
PIVOT (MAX([Val]) FOR Id IN ([1],[2],[3])) Piv
Cols 1 2 3
------ ------ ------ -------
Gender Male Male Male
Name Fahad Saad Asif
sal 10000 20000 30000

SQL Server : Bulk insert a Datatable into 2 tables

Consider this datatable :
word wordCount documentId
---------- ------- ---------------
Ball 10 1
School 11 1
Car 4 1
Machine 3 1
House 1 2
Tree 5 2
Ball 4 2
I want to insert these data into two tables with this structure :
Table WordDictionary
(
Id int,
Word nvarchar(50),
DocumentId int
)
Table WordDetails
(
Id int,
WordId int,
WordCount int
)
FOREIGN KEY (WordId) REFERENCES WordDictionary(Id)
But because I have thousands of records in initial table, I have to do this just in one transaction (batch query) for example using bulk insert can help me doing this purpose.
But the question here is how I can separate this data into these two tables WordDictionary and WordDetails.
For more details :
Final result must be like this :
Table WordDictionary:
Id word
---------- -------
1 Ball
2 School
3 Car
4 Machine
5 House
6 Tree
and table WordDetails :
Id wordId WordCount DocumentId
---------- ------- ----------- ------------
1 1 10 1
2 2 11 1
3 3 4 1
4 4 3 1
5 5 1 2
6 6 5 2
7 1 4 2
Notice :
The words in the source can be duplicated so I must check word existence in table WordDictionary before any insert record in these tables and if a word is found in table WordDictionary, the just found Word ID must be inserted into table WordDetails (please see Word Ball)
Finally the 1 M$ problem is: this insertion must be done as fast as possible.
If you're looking to just load the table the first time without any updates to the table over time you could potentially do it this way (I'm assuming you've already created the tables you're loading into):
You can put all of the distinct words from the datatable into the WordDictionary table first:
SELECT DISTINCT word
INTO WordDictionary
FROM datatable;
Then after you populate your WordDictionary you can then use the ID values from it and the rest of the information from datatable to load your WordDetails table:
SELECT WD.Id as wordId, DT.wordCount as WordCount, DT.documentId AS DocumentId
INTO WordDetails
FROM datatable as DT
INNER JOIN WordDictionary AS WD ON WD.word = DT.word
There a little discrepancy between declared table schema and your example data, but it was solved:
1) Setup
-- this the table with the initial data
-- drop table DocumentWordData
create table DocumentWordData
(
Word NVARCHAR(50),
WordCount INT,
DocumentId INT
)
GO
-- these are result table with extra information (identity, primary key constraints, working foreign key definition)
-- drop table WordDictionary
create table WordDictionary
(
Id int IDENTITY(1, 1) CONSTRAINT PK_WordDictionary PRIMARY KEY,
Word nvarchar(50)
)
GO
-- drop table WordDetails
create table WordDetails
(
Id int IDENTITY(1, 1) CONSTRAINT PK_WordDetails PRIMARY KEY,
WordId int CONSTRAINT FK_WordDetails_Word REFERENCES WordDictionary,
WordCount int,
DocumentId int
)
GO
2) The actual script to put data in the last two tables
begin tran
-- this is to make sure that if anything in this block fails, then everything is automatically rolled back
set xact_abort on
-- the dictionary is obtained by considering all distinct words
insert into WordDictionary (Word)
select distinct Word
from DocumentWordData
-- details are generating from initial data joining the word dictionary to get word id
insert into WordDetails (WordId, WordCount, DocumentId)
SELECT W.Id, DWD.WordCount, DWD.DocumentId
FROM DocumentWordData DWD
JOIN WordDictionary W ON W.Word = DWD.Word
commit
-- just to test the results
select * from WordDictionary
select * from WordDetails
I expect this script to run very fast, if you do not have a very large number of records (millions at most).
This is the query. I'm using temp table to be able to test.
if you use the 2 CTEs, you'll be able to generate the final result
1.Setting up a sample data for test.
create table #original (word varchar(10), wordCount int, documentId int)
insert into #original values
('Ball', 10, 1),
('School', 11, 1),
('Car', 4, 1),
('Machine', 3, 1),
('House', 1, 2),
('Tree', 5, 2),
('Ball', 4, 2)
2. Use cte1 and cte2. In your real database, you need to replace #original with the actual table name you have all initial records.
;with cte1 as (
select ROW_NUMBER() over (order by word) Id, word
from #original
group by word
)
select * into #WordDictionary
from cte1
;with cte2 as (
select ROW_NUMBER() over (order by #original.word) Id, Id as wordId,
#original.word, #original.wordCount, #original.documentId
from #WordDictionary
inner join #original on #original.word = #WordDictionary.word
)
select * into #WordDetails
from cte2
select * from #WordDetails
This will be data in #WordDetails
+----+--------+---------+-----------+------------+
| Id | wordId | word | wordCount | documentId |
+----+--------+---------+-----------+------------+
| 1 | 1 | Ball | 10 | 1 |
| 2 | 1 | Ball | 4 | 2 |
| 3 | 2 | Car | 4 | 1 |
| 4 | 3 | House | 1 | 2 |
| 5 | 4 | Machine | 3 | 1 |
| 6 | 5 | School | 11 | 1 |
| 7 | 6 | Tree | 5 | 2 |
+----+--------+---------+-----------+------------+

Find "regional" relationships in SQL data using a query, or SSIS

Edit for clarification: I am compiling data weekly, based on Zip_Code, but some Zip_Codes are redundant. I know I should be able to compile a small amount of data, and derive the redundant zip_codes if I can establish relationships.
I want to define a zip code's region by the unique set of items and values that appear in that zip code, in order to create a "Region Table"
I am looking to find relationships by zip code with certain data. Ultimately, I have tables which include similar values for many zip codes.
I have data similar to:
ItemCode |Value | Zip_Code
-----------|-------|-------
1 |10 | 1
2 |15 | 1
3 |5 | 1
1 |10 | 2
2 |15 | 2
3 |5 | 2
1 |10 | 3
2 |10 | 3
3 |15 | 3
Or to simplify the idea, I could even concantenate ItemCode + Value into unique values:
ItemCode+
Value | Zip_Code
A | 1
B | 1
C | 1
A | 2
B | 2
C | 2
A | 3
D | 3
E | 3
As you can see, Zip_Code 1 and 2 have the same distinct ItemCode and Value. Zip_Code 3 however, has different values for certain ItemCodes.
I need to create a table that establishes a relationship between Zip_Codes that contain the same data.
The final table will look something like:
Zip_Code | Region
1 | 1
2 | 1
3 | 2
4 | 2
5 | 1
6 | 3
...etc
This will allow me to collect data only once for each unique Region, and derive the zip_code appropriately.
Things I'm doing now:
I am currently using a query similar to a join, and compares against Zip_Code using something along the lines of:
SELECT a.ItemCode
,a.value
,a.zip_code
,b.ItemCode
,b.value
,b.zip_code
FROM mytable as a, mytable as b -- select from table twice, similar to a join
WHERE a.zip_code = 1 -- left table will have all ItemCode and Value from zip 1
AND b.zip_code = 2 -- right table will have all ItemCode and Value from zip 2
AND a.ItemCode = b.ItemCode -- matches rows on ItemCode
AND a.Value != b.Value
ORDER BY ItemCode
This returns nothing if the two zip codes have exactly the same ItemNum, and Value, and returns a slew of differences between the two zip codes if there are differences.
This needs to move from a manual process to an automated process however, as I am now working with more than 100 zip_codes.
I do not have much programming experience in specific languages, so tools in SSIS are somewhat limited to me. I have some experience using the Fuzzy tools, and feel like there might be something in Fuzzy Grouping that might shine a light on apparent regions, but can't figure out how to set it up.
Does anyone have any suggestions? I have access to SQLServ and its related tools, and Visual Studio. I am trying to avoid writing a program to automate this, as my c# skills are relatively nooby, but will figure it out if necessary.
Sorry for being so verbose: This is my first Question, and the page I agreed to in order to ask a question suggested to explain in detail, and talk about what I've tried...
Thanks in advance for any help I might receive.
Give this a shot (I used the simplified example, but this can easily be expanded). I think the real interesting part of this code is the recursive CTE...
;with matches as (
--Find all pairs of zip_codes that have matching values.
select d1.ZipCode zc1, d2.ZipCode zc2
from data d1
join data d2 on d1.Val=d2.Val
group by d1.ZipCode, d2.ZipCode
having count(*) = (select count(distinct Val) from data where zipcode = d1.Zipcode)
), cte as (
--Trace each zip_code to it's "smallest" matching zip_code id.
select zc1 tempRegionID, zc2 ZipCode
from matches
where zc1<=zc2
UNION ALL
select c.tempRegionID, m.zc2
from cte c
join matches m on c.ZipCode=m.zc1
and c.ZipCode!=m.zc2
where m.zc1<=m.zc2
)
--For each zip_code, use it's smallest matching zip_code as it's region.
select zipCode, min(tempRegionID) as regionID
from cte
group by ZipCode
Demonstrating that there's a use for everything, though normally it makes me cringe: concatenate the values for each zip code into a single field. Store ZipCode and ConcatenatedValues in a lookup table (PK on the one, UQ on the other). Now you can assess which zip codes are in the same region by grouping on ConcatenatedValues.
Here's a simple function to concatenate text data:
CREATE TYPE dbo.List AS TABLE
(
Item VARCHAR(1000)
)
GO
CREATE FUNCTION dbo.Implode (#List dbo.List READONLY, #Separator VARCHAR(10) = ',') RETURNS VARCHAR(MAX)
AS BEGIN
DECLARE #Concat VARCHAR(MAX)
SELECT #Concat = CASE WHEN Item IS NULL THEN #Concat ELSE COALESCE(#Concat + #Separator, '') + Item END FROM #List
RETURN #Concat
END
GO
DECLARE #List AS dbo.List
INSERT INTO #List (Item) VALUES ('A'), ('B'), ('C'), ('D')
SELECT dbo.Implode(#List, ',')

Resources