SQL Find String - sql

I would like to know if finding a string can be done from another table. It's a bit complicated.
Here's the table: (tbl_dishes)
| dish | Type |
| egg, hotdog & bread | Breakfast |
From the table above, I want to get the individual descriptions of the column dish from another table
2nd Table (tbl_Foods)
| food | Description |
| egg | Fresh |
| hotdog | red |
| bread | toasted |
| steak | meat |
Let's say my query would look like this: (but it's wrong)
SELECT food, description FROM tbl_Foods
WHERE food Exists IN (SELECT dish FROM tbl_Dishes)
My desired results would be:
| food | Description |
| egg | Fresh |
| hotdog | red |
| bread | toasted |
It's like getting all matched word in the dish column. I don't know if it's possible. Please help.
Thank you.

SELECT food, description
FROM tbl_Foods
join tbl_Dishes
on tbl_Dishes.dish like ('%' + tbl_Foods.food +'%')

You will need to split the list
DECLARE #DelimString VARCHAR(100)
SET DelimString = SELECT REPLACE(REPLACE(dish,'&',','),' ', '') FROM tbl_Dishes
DECLARE #Dish TABLE (Dish VARCHAR(50)); INSERT INTO #Dish SELECT CAST(ParamValue AS VARCHAR) FROM MultiValueParams_String(#DelimString)
Use this function.
Create function [dbo].[MultiValueParams_String] (#ParamList varchar(4000))
returns #Values table (RoNum INT,ParamValue varchar(4000))
as
begin
declare #Delim char(1) = ',' -- comma is always the delimiter
declare #Chrind int = 1
declare #Piece nvarchar(50)
declare #RoNum int = 0
while #Chrind>0
begin
select #Chrind=charindex(#Delim,#ParamList)
if #Chrind>0
select #Piece=left(#ParamList,#chrind-1)
else
select #Piece=#ParamList
insert #values(RoNum,ParamValue) values (#RoNum,#Piece)
select #ParamList = right(#ParamList,len(#ParamList)-#chrind)
if len(#ParamList)=0 break
SELECT #RoNum = #RoNum + 1
end
return
end
SELECT food, description
FROM tbl_Foods f
INNER JOIN #Dish d ON f.food = d.dish
Something like this.

Related

How to select data using table/column names stored in a table? Dynamically building SQL?

I have a table that shows events and vehicles associated with those events:
id | model | colour | cargo
--------------------------------------------
1 | vw | red | tomatoes
2 | bmw | red | golf clubs
I would like to have a table that I can use to manage alerts. There is a column that represents strings to check for, another column that shows which column/table it is applied to and some text that describes the alert:
id | column_name | check_string | alert_string
--------------------------------------------
1 | my_table.colour | red | 'Vehicle is red'
2 | my_table.cargo | [sport,club] | 'Vehicle is carrying sports goods'
or possibly:
id | column_name | check_string | alert_string
--------------------------------------------
1 | my_table.colour | red | 'Vehicle is red'
2 | my_table.cargo | sport | 'Vehicle is carrying sports goods'
3 | my_table.cargo | club | 'Vehicle is carrying sports goods'
I would like to have a query that I can run that would return all the alerts that apply to that row:
id | alert_text
--------------------------------------------
1 | ['Vehicle is red']
2 | ['Vehicle is red', 'Vehicle is carrying sports goods']
The way I was doing this was building up SQL commands in Python and running them against the database but this becomes a burden as the number of rules grow or variables need to be changed/updated (suddenly we don't care about red cars but are very concerned with blue cars).
SELECT id, 'Vehicle is red' as alert_text FROM my_table
WHERE my_table.colour = 'red';
SELECT id, 'Sports goods' as alert_text FROM my_table
WHERE my_table.cargo in ['sport', 'club'];
SELECT <many other rules>;
Is there a better way to do this? Is it worth building a DB table that can dynamically point to a column, strings to check in that column and then alert text associated with the rule? Should I even be using SQL for this problem?
I've got a feeling that SQL is maybe not the right tool for this job but I don't know what I don't know...
This SQL code would allow you to have a DB table of any size for both Events and Alerts
Declare #vSQL nvarchar(Max)
Declare #vColumnName nvarchar(25)
Declare #vCheckString nvarchar(25)
Declare #vAlertString nvarchar(50)
Declare vCursor CURSOR For
Select [column_name], [check_string], [alert_string] From vehicle_alerts
Open vCursor;
Fetch Next From vCursor Into #vColumnName, #vCheckString, #vAlertString
--Make global temp table from source then delete contents.
--Ensures field lengths are as large as the largest value in the temp table (Not the most efficient but it works)
Select id, alert_string INTO ##alerts From vehicle_alerts
Delete From ##alerts
While ##FETCH_STATUS = 0 --Loop through alerts and interrogate your events using the LIKE operator
Begin
Set #vSQL = 'INSERT INTO ##alerts Select id, '''+#vAlertString+''' As [Alert] From vehicle_events Where ' + #vColumnName + ' Like ''%' + #vCheckString + '%'''
Execute (#vSQL)
Fetch Next From vCursor Into #vColumnName, #vCheckString, #vAlertString
End;
--Use STUFF to combine the alert strings by id
Select id,STUFF((Select ',' + [alert_string] From ##alerts Where id = a.id FOR XML PATH('')),1,1,'') AS Tmp
From ##alerts AS a
Group By id
Drop Table ##alerts
Close vCursor
Deallocate vCursor

Substring output for different cases

I need output with following cases:
+-----------------+--------+
| STRING in table | OUTPUT |
+-----------------+--------+
| NONGL_NONGL | NONGL |
| GL252_GL252 | GL |
| GL400_GL400 | GL |
| NOS_NOS | NOS |
+-----------------+--------+
I tried to use SUBSTRING() but it is not giving me proper output.
So for the NONGL and NOS result you need:
SELECT LEFT([col_name], CHARINDEX('_', [col_name]) - 1)
And for the GL output, since the string is containing numbers you need PATINDEX():
SELECT LEFT([col_name], PATINDEX('%[0-9]%', [col_name]) - 1)
In the end just use a CASE WHEN
DECLARE #tbl Table (string VARCHAR(MAX) );
INSERT INTO #tbl VALUES
('NONGL_NONGL'),
('GL252_GL252'),
('GL400_GL400'),
('NOS_NOS');
SELECT
string
,CASE WHEN
string LIKE '%[0-9]%' then LEFT(string, PATINDEX('%[0-9]%', string) - 1)
ELSE LEFT(string, CHARINDEX('_', string) - 1)
END AS [Output]
from #tbl
This should do what you want:
select (case when col like 'NONGL%' then 'NONGL'
when col like 'GL%' then 'GL'
when col like 'NOS%' then 'NOS'
end) as new_col
Here is another way:
DECLARE #StrTable Table (Str VARCHAR(MAX) );
INSERT INTO #StrTable VALUES
('NONGL_NONGL'),
('GL252_GL252'),
('GL400_GL400'),
('NOS_NOS');
SELECT Str,
IIF(Str LIKE 'NONGL%','NONGL',IIF(Str LIKE 'GL%', 'GL', IIF(Str LIKE 'NOS%', 'NOS', Str))) AS Outputs
FROM #StrTable;
Result:
+-------------+---------+
| Str | Outputs |
+-------------+---------+
| NONGL_NONGL | NONGL |
| GL252_GL252 | GL |
| GL400_GL400 | GL |
| NOS_NOS | NOS |
+-------------+---------+

SQL Sort Numeric Strings After Split

I currently have char values in a table column which are in the format "IS-" and then 1 to 5 numbers, a possible period with either 2 numbers or a letter following the period.
Examples are, IS-1, IS-12, IS-123, IS-123.11, IS-123.a.
I need to split the string so that I grab only the number part, sort the strings ASC, and the bring the strings back together the way they were.
Explanation. I have the following set of values, IS-1170, IS-1171, IS-1172, IS-1173, IS-1174, IS-870.a, IS-871.a, IS-872.a. As you can see, because IS-1 comes before IS-8 they are sorting out of numerical order.
Any idea where to begin? I was thinking of using CASE, but I'm not really sure how to proceed.
Thanks.
Do string functions in your ORDER BY to remove only the number. Something like this should work:
SELECT col
FROM table
ORDER BY CAST(CASE WHEN ISNUMERIC(SUBSTRING(col,4,20)) = 1
THEN SUBSTRING(col,4,20)
ELSE LEFT(SUBSTRING(col,4,20),CHARINDEX('.',SUBSTRING(col,4,20),0)-1)
END AS NUMERIC)
This will first remove the IS- and check if the rest of the string is a number. If it is, it will leave the decimal digits, otherwise it will remove the . and the following alpha characters.
This is assuming your intended ordering in the case of numeric decimal places would be:
IS-123.A
IS-123.1
IS-123.2
If you don't care about what's after the decimal/period, then simply:
ORDER BY CAST(LEFT(SUBSTRING(col,4,20),CHARINDEX('.',SUBSTRING(col,4,20),0)-1) AS NUMERIC)
If I understand you correctly, this might help you:
DECLARE #mockup TABLE(ID INT IDENTITY,YourExample VARCHAR(100));
INSERT INTO #mockup VALUES
('IS-1, IS-12, IS-123, IS-123.11, IS-123.a.')
,('IS-1170, IS-1171, IS-1172, IS-1173, IS-1174, IS-870.a, IS-871.a, IS-872.a');
WITH Splitted AS
(
SELECT *
,CAST('<x>' + REPLACE(m.YourExample,',','</x><x>') + '</x>' AS XML) AS SplitAtComma
FROM #mockup AS m
)
,NumberExtracted AS
(
SELECT s.ID
,part.value('text()[1]','nvarchar(max)') AS OnePart
,CAST('<y>' + REPLACE(REPLACE(part.value('text()[1]','nvarchar(max)'),'.','-'),'-','</y><y>') + '</y>' AS XML).value('/y[2]/text()[1]','int') AS TheNumber
FROM Splitted AS s
CROSS APPLY s.SplitAtComma.nodes('/x') AS A(part)
)
SELECT *
FROM NumberExtracted
ORDER BY ID,TheNumber;
The first CTE uses a string-split via XML to get all values within the original string (btw: never store comma separated values!).
The second CTE will use the same approach to extract the number, typesafe as INT.
You can use this in an ORDER BY finally.
The result:
+----+-----------+-----------+
| ID | OnePart | TheNumber |
+----+-----------+-----------+
| 1 | IS-1 | 1 |
+----+-----------+-----------+
| 1 | IS-12 | 12 |
+----+-----------+-----------+
| 1 | IS-123 | 123 |
+----+-----------+-----------+
| 1 | IS-123.11 | 123 |
+----+-----------+-----------+
| 1 | IS-123.a. | 123 |
+----+-----------+-----------+
| 2 | IS-870.a | 870 |
+----+-----------+-----------+
| 2 | IS-871.a | 871 |
+----+-----------+-----------+
| 2 | IS-872.a | 872 |
+----+-----------+-----------+
| 2 | IS-1170 | 1170 |
+----+-----------+-----------+
| 2 | IS-1171 | 1171 |
+----+-----------+-----------+
| 2 | IS-1172 | 1172 |
+----+-----------+-----------+
| 2 | IS-1173 | 1173 |
+----+-----------+-----------+
| 2 | IS-1174 | 1174 |
+----+-----------+-----------+
IF OBJECT_ID(N'tempdb..##table1', N'U') IS NOT NULL
DROP TABLE ##table1;
create table ##table1(col1 varchar(20))
declare #query as nvarchar(max)
declare #var1 as varchar(max)='IS-1, IS-12, IS-123, IS-123.11, IS-123.a.,IS-1170, IS-1171, IS-1172, IS-1173, IS-1174, IS-870.a, IS-871.a, IS-872.a.'
set #var1=replace(#var1,',','''),(''')
set #var1='('''+#var1+''')'
set #var1=replace(#var1,' ','')
set #query='insert into ##table1 values'+#var1
EXEC sp_executesql #query
IF OBJECT_ID(N'tempdb..##table2', N'U') IS NOT NULL
DROP TABLE ##table2;
select * into ##table2 from ##table1 order by cast(replace(replace(replace(col1,'IS-',''),'.a.',''),'.a','') as float)
declare #results varchar(max)
select #results = coalesce(#results + ', ', '') + convert(varchar(12),col1) from ##table2
select #results
DROP TABLE ##table1
DROP TABLE ##table2

Aggregates on the right side of an APPLY cannot reference columns from the left side

I am trying to make some sense of a xBase type database with some 2000 tables. Rather than importing them all into a SQL Server database, I wanted to import the tables one-by-one using a 'SELECT INTO tmpDBF' statement, then extract what I want to know like table structure and value ranges for each of the columns. Then, when I import the next table I want to be able to run the same query against a differently structured tmpDBF table.
I was hoping to do this using a cross apply, but I come up against the above error message.
select cols.column_name 'Name', cols.data_type 'Type', mv.minV 'Minimum'
from information_schema.columns cols
cross apply (select MIN(cols.column_name) minV FROM tmpDBF ) mv
where cols.table_name = 'tmpDBF'
Is there way to restructure the query or did I turn into a dead-end street?
Added on October 6:
Given tmpDBF
Who | Zip
--------|------
Charlie | 97689
Foxtrot | 92143
Delta | 12011
I would like to see the following result
Name | Type | Minimum | Maximum
-----|---------|---------|--------
who | varchar | Charlie | Foxtrot
Zip | int | 12011 | 96789
I realise that the Minimum and Maximum columns need to be cast as varchars.
This is not possible for two reasons.
you cannot dynamically change a column name in a query
you cannot mix multiple datatypes in a single column.
But to get you something similar to what you are looking for you can flip the problem around like this:
SQL Fiddle
MS SQL Server 2008 Schema Setup:
CREATE TABLE dbo.a(c1 INT, c2 INT, c3 DATE);
INSERT INTO dbo.a VALUES(1,2,'2013-04-05'),(4,5,'2010-11-10'),(7,8,'2012-07-09');
Query 1:
SELECT
MIN(c1) c1_min,MAX(c1) c1_max,
MIN(c2) c2_min,MAX(c2) c2_max,
MIN(c3) c3_min,MAX(c3) c3_max
FROM dbo.a;
Results:
| C1_MIN | C1_MAX | C2_MIN | C2_MAX | C3_MIN | C3_MAX |
|--------|--------|--------|--------|------------|------------|
| 1 | 7 | 2 | 8 | 2010-11-10 | 2013-04-05 |
That gives you all the column minima and maxima in a single row. (It's not dynamic yet. Stay with me...)
To make it a little more readable you can use a sort of UNPIVOT like this:
Query 2:
SELECT
CASE X.FN WHEN 1 THEN 'MIN' ELSE 'MAX' END AS FN,
CASE X.FN WHEN 1 THEN c1_min ELSE c1_max END AS c1,
CASE X.FN WHEN 1 THEN c2_min ELSE c2_max END AS c2,
CASE X.FN WHEN 1 THEN c3_min ELSE c3_max END AS c3
FROM(
SELECT
MIN(c1) c1_min,MAX(c1) c1_max,
MIN(c2) c2_min,MAX(c2) c2_max,
MIN(c3) c3_min,MAX(c3) c3_max
FROM dbo.a)AGG
CROSS JOIN (VALUES(1),(2))X(FN)
ORDER BY X.FN;
Results:
| FN | C1 | C2 | C3 |
|-----|----|----|------------|
| MIN | 1 | 2 | 2010-11-10 |
| MAX | 7 | 8 | 2013-04-05 |
Now to make it dynamic we have to build that query on the fly, like this:
Query 3:
DECLARE #cmd NVARCHAR(MAX);
SET #cmd =
'SELECT CASE X.FN WHEN 1 THEN ''MIN'' ELSE ''MAX'' END AS FN'+
(SELECT ',CASE X.FN WHEN 1 THEN '+name+'_min ELSE '+name+'_max END AS '+name
FROM sys.columns WHERE object_id = OBJECT_ID('dbo.a')
FOR XML PATH(''),TYPE).value('.','NVARCHAR(MAX)')+
' FROM(SELECT '+
STUFF((SELECT ',MIN('+name+') '+name+'_min,MAX('+name+') '+name+'_max'
FROM sys.columns WHERE object_id = OBJECT_ID('dbo.a')
FOR XML PATH(''),TYPE).value('.','NVARCHAR(MAX)'),1,1,'')+
' FROM dbo.a)AGG CROSS JOIN (VALUES(1),(2))X(FN) ORDER BY X.FN;';
EXEC(#cmd);
Results:
| FN | C1 | C2 | C3 |
|-----|----|----|------------|
| MIN | 1 | 2 | 2010-11-10 |
| MAX | 7 | 8 | 2013-04-05 |
This query takes the columns of the table at runtime, builds the appropriate query dynamically and executes it. It contains the table name ('dbo.a') in three places. If you want it to work with different tables you need to replace all three.
Try something like
select cols.column_name 'Name', cols.data_type 'Type', mv.minV 'Minimum'
from information_schema.columns cols
cross apply (select MIN(cols.column_name) minV FROM tmpDBF
WHERE tmpDBF.CommonCol = cols.CommonCol) mv
where cols.table_name = 'tmpDBF'

How to merge multiple rows into one row with filtering rules in SQL Server

I have a table like this:
+---------------+---------------+----------------+---------------------+
| MedicalCardId | DiagnosisType | DiagnosisOrder | Symptom |
+---------------+---------------+----------------+---------------------+
| 1 | Main | 1 | Lung Cancer |
| 1 | Secondary | 1 | High Blood Pressure |
| 1 | Secondary | 2 | Heart Attack |
| 1 | Secondary | 3 | Gastritis |
| 2 | Main | 1 | Diabetes |
| 2 | Secondary | 1 | Kidney Malfunction |
| 3 | Main | 1 | Flu |
+---------------+---------------+----------------+---------------------+
The DiagnosisOrder for each 'Main' DiagnosisType is 1, and for 'Secondary' DiagnosisType of the same MedicalCardId, it restarts to increase from 1.
I would like to merge multiple rows of the same MedicalCardId into a single row, and each Symptom becomes a new column depending on its DiagnosisType and DiagnosisOrder
The query result is expected to be like:
+---------------+-------------+---------------------+-------------------+-------------------+
| MedicalCardId | MainSymptom | SecondarySymptom1 | SecondarySymptom2 | SecondarySymptom3 |
+---------------+-------------+---------------------+-------------------+-------------------+
| 1 | Lung Cancer | High Blood Pressure | Heart Attack | Gastritis |
| 2 | Diabetes | Kidney Malfunction | | |
| 3 | Flu | | | |
+---------------+-------------+---------------------+-------------------+-------------------+
I've tried using PIVOT, but I'm unable to apply it to my practice.
You can try with conditional aggregation -
select MedicalCardId,
max(case when DiagnosisType='Main' then Symptom end) as MainSymptom,
max(case when DiagnosisType='Secondary' and DiagnosisOrder=1 then Symptom end) as SecondarySymptom1,
max(case when DiagnosisType='Secondary' and DiagnosisOrder=2 then Symptom end) as SecondarySymptom2,
max(case when DiagnosisType='Secondary' and DiagnosisOrder=3 then Symptom end) as SecondarySymptom3
from tablename
group by MedicalCardId
I believe you need to create a dynamic pivot table. The reason why you can’t use a normal pivot table query is because you don’t know how many Secondary Symptoms there are and therefore you don’t know how many columns to create. Below is a stored procedure that works. The first step is creating a VARCHAR (#Columns) variable that will be used to store the dynamic column names these will be [Main], [Secondary1], [Secondary2], [Secondary3] so on and so forth (I used a case statement to create the column names per your expected query result). The second step is creating another VARCHAR (#SQL) variable that will contain the pivot table SQL query. In this step you will use string concatenation to put this variable together.
Kris Wenzel has a great tutorial on dynamic pivot tables at essentialsql.com here is the link https://www.essentialsql.com/create-dynamic-pivot-table-sql-server/
Here is the stored procedure.
USE [TestDB]
GO
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
-- =============================================
-- Author: <Author,,Name>
-- Create date: <Create Date,,>
-- Description: <Description,,>
-- =============================================
CREATE PROCEDURE [dbo].[GenerateData]
AS
BEGIN
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
SET NOCOUNT ON;
-- Insert statements for procedure here
--GATHER PIVOT COLUMNS DYNAMICALLY
DECLARE #Columns as VARCHAR(MAX)
SELECT #Columns =
COALESCE(#Columns + ', ','') + QUOTENAME([Diagnosis])
FROM
(SELECT DISTINCT case when [DiagnosisOrder] = 1 and [DiagnosisType] = 'Main' then 'MainSymptom' else 'SecondarySymptom' + CAST([DiagnosisOrder] AS VARCHAR) end [Diagnosis] FROM [TestDB].[dbo].[test] ) AS B
ORDER BY B.[Diagnosis]
--CREATE SQL QUERY FOR PIVOT TABLE
DECLARE #SQL as VARCHAR(MAX)
SET #SQL = 'SELECT MedicalCardId, ' + #Columns + '
FROM
(
select [MedicalCardId]
,[Diagnosis]
,[Sympton]
from
(
SELECT [MedicalCardId]
,case when [DiagnosisOrder] = 1 and [DiagnosisType] = ''Main'' then ''MainSymptom'' else ''SecondarySymptom'' + CAST([DiagnosisOrder] AS VARCHAR) end [Diagnosis]
,[Sympton]
FROM [TestDB].[dbo].[test]
) A
) t
PIVOT(
MAX([Sympton])
FOR [Diagnosis] IN (' + #Columns + ')
) AS pivot_table order by [MedicalCardId]'
--EXECUTE SQL
EXEC(#SQL)
END
GO