SQL insert into not exists on multiple results - c#

I have Plan table that has a one to many relationship with a Price table.
(1 plan can have many prices)
However the problem is I have another table called "StandardPrices" that holds the names of all the prices (The reason for this is I want to be able to add or remove prices at any time)
Plan Table:
ID int primary key,
plan Name varchar(200),
...
PriceTable:
ID int primary key,
PlanId int foreign key references plan(ID)
PriceName ID foreign key standardprices(id)
StandardPrices:
ID int primary key,
PriceName varchar(200),
DefaultPrice money
So whenever a plan is created, it automatically creates a List of all the prices in the StandardPrice list (with default values).
The problem I have, Is I need, whenever I create a new StandardPrice, it automatically checks if that price exists in every plan, and if it doesnt, create an entry in the price table for that planid.
I use Stored procedures and thought the best way to do this would be through SQL.
When StandardPrices are created:
begin
insert into StandardPrices (PriceName, Defaultprice)
values (#priceName, #DefaultPrice)
end
begin
//list all plans.
//cross reference PriceTable to see if plan exists
//if not insert priceplan with default value
end
I am a bit confused how i can implement such a sql command?

I think it looks something like this:
insert into PriceTable (PlanId, PriceName)
select PlanId, #priceName
from Plan
where not exists
(select null from PriceTable where PriceTable.PlanId = Plan.PlanId)
And you should probably do this as part of an INSERT trigger on your database.

Do something like this:
if not exists
(
select *
from PriceTable
where PriceName = #priceName
)
begin
insert into PriceTable(PriceName)
values(#priceName)
end
What this does is it conditionally checks to see if that PriceName is already in the PriceTable. If it is not, then it will insert that new #priceName into the PriceTable. I wasn't sure by your original post what the value would be for PlanID, but you should get the idea from my above query.

You need to get a list of all plans, LEFT OUTER joined to the appropriate price. If the price record is null, there isn't an appropriate association so add it.
You'll probably need to use a cursor to iterate through the results and add prices.
Something like..
SELECT Plan.Id, Price.Id
FROM Plan LEFT OUTER JOIN Price ON Price.PlanId = Plan.Id
WHERE Price.Id = [Your New Price Id]

Using MERGE and assuming your ID columns have the IDENTITY property:
MERGE INTO PriceTable
USING
(
SELECT p1.ID AS PlanId, sp1.PriceName
FROM Plan p1
CROSS JOIN StandardPrices sp1
) AS SourceTable
ON PriceTable.PlanId = SourceTable.PlanId
AND PriceTable.PriceName = SourceTable.PriceName
WHEN NOT MATCHED
THEN
INSERT ( PlanId, PriceName )
VALUES ( PlanId, PriceName ) ;
Here is a fuller sketch, where I've drooped the seemingly redundant ID columns, promoted the name columns to relational keys, added constraints, used consistent naming between tables, changed MONET to DECIMAL, etc:
CREATE TABLE Plans
(
plan_name VARCHAR(200) NOT NULL
UNIQUE
CHECK ( plan_name <> ' ' )
) ;
CREATE TABLE StandardPrices
(
price_name VARCHAR(200) NOT NULL
UNIQUE
CHECK ( price_name <> ' ' ),
default_price DECIMAL(19, 4) NOT NULL
CHECK ( default_price >= 0 )
) ;
CREATE TABLE Prices
(
plan_name VARCHAR(200) NOT NULL
REFERENCES Plans ( plan_name ),
price_name VARCHAR(200) NOT NULL
REFERENCES StandardPrices ( price_name ),
UNIQUE ( plan_name, price_name )
) ;
DECLARE #plan_name_new VARCHAR(200) ;
DECLARE #price_name_1 VARCHAR(200) ;
DECLARE #default_price_1 DECIMAL(19, 4) ;
DECLARE #price_name_2 VARCHAR(200) ;
DECLARE #default_price_2 DECIMAL(19, 4) ;
SET #plan_name_new = 'One'
SET #price_name_1 = 'Day'
SET #default_price_1 = 55 ;
SET #price_name_2 = 'When'
SET #default_price_2 = 99
INSERT INTO Plans ( plan_name )
VALUES ( #plan_name_new ) ;
INSERT INTO StandardPrices ( price_name, default_price )
VALUES ( #price_name_1, #default_price_1 ) ;
INSERT INTO StandardPrices ( price_name, default_price )
VALUES ( #price_name_2, #default_price_2 ) ;
MERGE INTO Prices
USING
(
SELECT p1.plan_name, sp1.price_name
FROM Plans p1
CROSS JOIN StandardPrices sp1
) AS SourceTable
ON Prices.plan_name = SourceTable.plan_name
AND Prices.price_name = SourceTable.price_name
WHEN NOT MATCHED
THEN
INSERT ( plan_name, price_name )
VALUES ( plan_name, price_name ) ;

(MySql) I'll suggest you to create with temporary table; It is clean and readable (I will add without alias to be more readable):
CREATE TEMPORARY TABLE tempTable
(
table1FK varchar(250),
table2Value varchar(250)
);
INSERT INTO tempTable(table1FK, table2Value)
VALUES ('it', '+39'),
('lt', '+370'),
('gb', '+44'),
('cn', '+86');
INSERT INTO table2(table1FK_id, table2Value)
SELECT table1.id, tempTable.prefix
FROM tempTable
inner join table1 ON table1.code = tempTable.country_code
left join table2 ON table2.country_id = table1.id
where table2.id is null;
DROP temporary TABLE tempTable;

Related

Insert and Update an array of dynamic length into a table

My table is
(id int,property varchar)
Suppose I want to insert {3, 'a, b, c'}. How can I do it in a stored procedure?
The table entry would be:
id property
3 a
3 b
3 c
Also when i want to update my id=3 row by {3, 'ab, b, bg, ht'} . What would be the stored procedure for this insert and update operation? My table entries should become
id property
3 ab
3 b
3 bg
3 ht
sample table:
create table yourtable (id int,property varchar(5))
Procedure for that table:
create procedure p_test
(
#id int,
#prolist varchar(2000)
) as
begin
;with x as
(
SELECT * FROM yourtable WHERE id = #ID
)
MERGE INTO
x t1
using
(SELECT #id id, ltrim(t.c.value('.', 'VARCHAR(2000)')) property
FROM (
SELECT x = CAST('<t>' +
REPLACE(#prolist, ',', '</t><t>') + '</t>' AS XML)
) a
CROSS APPLY x.nodes('/t') t(c)) t2 on t1.id = t2.id and t1.property = t2.property
when not matched then INSERT (id,property)
VALUES(t2.id, t2.property)
when matched
THEN UPDATE SET t1.id = t2.id
WHEN NOT MATCHED BY SOURCE THEN DELETE
;
end
Testing:
exec p_test 3, 'b,c'
select * from yourtable
exec p_test 3, 'a,b,c'
select * from yourtable
exec p_test 3, 'a,c'
select * from yourtable
exec p_test 4, 'g,h'
select * from yourtable
Result:
id property
3 b
3 c
id property
3 b
3 c
3 a
id property
3 c
3 a
id property
4 g
3 c
3 a
4 h
EDIT:
in order to update a new column use this table:
create table yourtable (id int,property varchar(5), is_active bit default 1)
Use this procedure:
alter procedure p_test
(
#id int,
#prolist varchar(2000)
) as
begin
;with x as
(
SELECT * FROM yourtable WHERE id = #ID
)
MERGE INTO
x t1
using
(SELECT #id id, ltrim(t.c.value('.', 'VARCHAR(2000)')) property
FROM (
SELECT x = CAST('<t>' +
REPLACE(#prolist, ',', '</t><t>') + '</t>' AS XML)
) a
CROSS APPLY x.nodes('/t') t(c)) t2 on t1.id = t2.id and t1.property = t2.property
when not matched then INSERT (id,property, is_active)
VALUES(t2.id, t2.property, 1)
when matched
THEN UPDATE SET t1.id = t2.id, is_active = 1
WHEN NOT MATCHED BY SOURCE THEN
UPDATE SET t1.is_active = 0
;
end
First one:
insert into <tableName>(id, property) values(3, 'a');
insert into <tableName>(id, property) values(3, 'b');
insert into <tableName>(id, property) values(3, 'c');
Second issue:
update <tableName> set property='ab' where property = 'a';
update <tableName> set property='bg' where property = 'c';
insert into <tableName>(id, property) values(3, 'ht');
And now, a question: are you sure this is what your problem needs? Usually, when we call a column id we want it to be an identifier, that is, unique for each row. This may be a little bit off topic, but just in case...
There should be an id/primary key which will be unique. Profide any unique field and update records on the base of that unique field. Or you can make property unique or pair of id and property unique.
If I understood well, you could do something similar to this:
Insert INTO #tempTable
SELECT 3, * FROM dbo.splitstring('a,b,c,d')
It's just a pseudocode and you should do it inside your stored procedure.
If this is the right approach you have to take a look at this: T-SQL split string

LINQ adding columns to base result set

I have three classes, Code,CodeContact and Contact which map to tables in a database. In my code the classes have one static method which returns the data you'll find in the following SQL script
create table #Contacts
(ContactId int
,Name varchar(50)
,Phone varchar(50)
)
insert into #Contacts(ContactId,Name,Phone)
values (1,'jon',111 )
,(2,'jim',222)
,(3,'james',333)
,(4,'john',444)
,(5,'tim',555)
,(6,'jane',666)
go
create table #ContactsCodes
(ContactId int
,CodeId int
)
insert into #ContactsCodes(ContactId,CodeId)
values (1,1 )
,(1,2 )
,(1,3 )
,(1,4 )
,(2,2 )
,(2,3 )
,(3,1 )
,(3,4 )
,(4,1 )
,(4,5 )
create table #Codes
(CodeId int
,CodeText varchar(50)
)
insert into #Codes
values (1,'Code Red' )
,(2,'Code Blue' )
,(3,'Code Green' )
,(4,'Code Gray' )
,(5,'Code Black' )
,(6,'code clear' )
I'm looking for some advice on how to get LINQ code to acieve something like that following
declare #filter varchar(50) = (select 1)
select c.ContactId
,c.Name
,c.Phone
,case when varColumn.ContactId is null
then 'Not Present'
else 'Present' end as [Code Red]
,case when varColumn2.ContactId is null
then 'Not Present'
else 'Present' end as [Code Blue]
from Contacts as c
--****************end of 'cohort' base set of IDs
--first item in filter list becomes a
--column on a DataTable
left join
(
select distinct cc.ContactId
from Codes as codes
left join ContactsCodes as cc
on cc.CodeId = codes.CodeId
where codes.CodeId =#filter
) as varColumn on varColumn.ContactId = c.ContactId
--second item in filter becomes another column
left join
(
select distinct cc.ContactId
from Codes as codes
left join ContactsCodes as cc
on cc.CodeId = codes.CodeId
where codes.CodeId =#filter+1
) as varColumn2 on varColumn2.ContactId = c.ContactId
So let's say I have an IEnumerable with 1,2,3 in it. I would like to iterate over the collection and left join to the Contact.ContactId column and simply give an indication if that particular CodeId was found in the ContactsCodes table. Here's what the result looks like if you run the above SQL:
I will eventually need the tackd on column data to be pretty dynamic. But for the time being, can anyone provide a direction to go? I think the easiest choice might be to go the LINQ to SQL route and have the stuff inside the left join a stored procedure and call it X number of times for whatever's in the list, but I'm welcome to any suggestions.

convert xml string in a sql table to dynamic columns

I have two tables (using table variables for illustration. You can run these directly in management studio) that are related by the Id column.
Items in the first table has some standard set of columns and the second table has some extended parameter data for the same record. I'm storing the extended set as xml as it is dynamic in all aspects (different per product or new values being added etc).
I'm able to join these two tables and flatten out the column list as you can see in the example below. But my query requires to have the dynamic columns be defined beforehand. I would like to have this truly dynamic in the sense that if I were to add a new column in the #extended table, it should automatically come out as a new column in the output column list.
Basically the list of additional columns should be determined by the xml for that record. column name should be the xml tag and value should be value for the xml tag for each id.
Any pointers? (and can it be fast too with around 100k records or more in each table)
declare #standard table
(
Id INT,
Column1 varchar(10),
Column2 varchar(10),
Column3 varchar(10)
)
declare #extended table
(
Id INT,
column1 xml
)
insert into #standard values (1,'11', '12', '13')
insert into #standard values (2,'21', '22', '23')
insert into #extended values (1,'<FieldSet><Field><id>1</id><column4>1x</column4><column5>4x</column5></Field></FieldSet>')
insert into #extended values (2,'<FieldSet><Field><id>2</id><column4>2x</column4><column5>5x</column5></Field></FieldSet>')
select s.column1, s.column2,
(
SELECT Item2.value('(column4)[1]', 'varchar(50)')
FROM
e.column1.nodes('/FieldSet') AS T(Item)
CROSS APPLY e.column1.nodes('/FieldSet/Field') AS T2(Item2)
) column4,
(
SELECT Item2.value('(column5)[1]', 'varchar(50)')
FROM
e.column1.nodes('/FieldSet') AS T(Item)
CROSS APPLY e.column1.nodes('/FieldSet/Field') AS T2(Item2)
) column5
from #extended e
join #standard s on s.Id = e.Id
First off you can simplify your current query a bit.
select s.column1,
s.column2,
e.column1.value('(/FieldSet/Field/column4)[1]', 'varchar(50)') as column4,
e.column1.value('(/FieldSet/Field/column5)[1]', 'varchar(50)') as column5
from extended as e
join standard as s
on s.Id = e.Id
To do what you want will not be easy or fast. You need to get a list of all name/value pairs in your XML.
select T1.X.value('.', 'int') as Id,
T2.X.value('local-name(.)', 'sysname') as Name,
T2.X.value('.', 'varchar(10)') as Value
from extended as e
cross apply e.column1.nodes('/FieldSet/Field/id') as T1(X)
cross apply e.column1.nodes('/FieldSet/Field/*[position() > 1]') as T2(X)
Use that in a pivot query and join to standard.
select S.column1,
S.column2,
P.column4,
P.column5
from standard as s
inner join
(
select id, P.column4, P.column5
from (
select T1.X.value('.', 'int') as Id,
T2.X.value('local-name(.)', 'sysname') as Name,
T2.X.value('.', 'varchar(10)') as Value
from extended as e
cross apply e.column1.nodes('/FieldSet/Field/id') as T1(X)
cross apply e.column1.nodes('/FieldSet/Field/*[position() > 1]') as T2(X)
) as e
pivot (min(Value) for Name in (column4, column5)) P
) P
on S.Id = P.Id
To do this with a dynamic number of columns returned you need to build this pivot query dynamically.
Store the Name/Value pairs in temp table, use that table to figure out the columns you need and to build your query.
create table #ext
(
Id int,
Name sysname,
Value varchar(10),
primary key(Id, Name)
)
insert into #ext(Id, Name, Value)
select T1.X.value('.', 'int') as Id,
T2.X.value('local-name(.)', 'sysname') as Name,
T2.X.value('.', 'varchar(10)') as Value
from extended as e
cross apply e.column1.nodes('/FieldSet/Field/id') as T1(X)
cross apply e.column1.nodes('/FieldSet/Field/*[position() > 1]') as T2(X)
declare #SQL nvarchar(max)
set #SQL =
'select S.column1,
S.column2,
[COLLIST]
from standard as s
inner join
(
select id, [COLLIST]
from #ext as e
pivot (min(Value) for Name in ([COLLIST])) P
) P
on S.Id = P.Id'
declare #ColList nvarchar(max)
set #ColList =
(select ','+Name
from #ext
group by Name
for xml path(''), type).value('.', 'nvarchar(max)')
set #SQL = replace(#SQL, '[COLLIST]', stuff(#ColList, 1, 1, ''))
exec (#SQL)
drop table #ext
I hope it will help you
SELECT #COUNT_XML=0
SELECT #COUNT_XML=(SELECT #xxxxx_GROUP_ID.value('count(/NewDataSet/position/ID)', 'INT'))
IF(#COUNT_XML > 0)
BEGIN
IF OBJECT_ID('tempdb..#TBL_TEMPOSITION') IS NOT NULL
DROP TABLE #TBL_TEMPOSITION
CREATE TABLE #TBL_TEMPOSITION (ID NUMERIC(18,0))
INSERT INTO #TBL_TEMPOSITION (ID)
SELECT XMLxxxxGroup.PositionGPItem.value('.','NUMERIC(18,0)')
FROM #xxxxx_GROUP_ID.nodes('/NewDataSet/position/ID') AS XMLPositionGroup(PositionGPItem)
SELECT #emp_cond =#emp_cond+ N' AND CM.STATIC_EMP_INFO.POSITION_ID IN (SELECT ID FROM #TBL_TEMPOSITION) '
END

Parameters to the EXISTS clause in a stored procedure

I have a table DEPT, which holds 2 columns - ID, NAME.
A search form is presented with the IDs from the DEPT table and the user can chose any number of IDs and submit the form, to get the related NAMEs.
Clarification/Inputs:
I don't want to build a dynamic query - its not manageable.
I prefer a stored procedure using table-valued parameters
Any other solutions to proceed?
NOTE:
This example is simple with 1 table - in real life, I have to deal with more than 6 tables!
Thanks for any suggestions
CREATE TYPE dbo.DeptList
AS TABLE
(
ID INT
);
GO
CREATE PROCEDURE dbo.RetrieveDepartments
#dept_list AS dbo.DeptList READONLY
AS
BEGIN
SET NOCOUNT ON;
SELECT Name FROM dbo.table1 WHERE ID IN (SELECT ID FROM #dept)
UNION ALL
SELECT Name FROM dbo.table2 WHERE ID IN (SELECT ID FROM #dept)
-- ...
END
GO
Now in your C# code, create a DataTable, fill it in with the IDs, and pass it in to the stored procedure. Assuming you already have a list called tempList and the IDs are stored in id:
DataTable tvp = new DataTable();
tvp.Columns.Add(new DataColumn("ID"));
foreach(var item in tempList)
{
tvp.Rows.Add(item.id);
}
using (connObject)
{
SqlCommand cmd = new SqlCommand("StoredProcedure", connObject);
cmd.CommandType = CommandType.StoredProcedure;
SqlParameter tvparam = cmd.Parameters.AddWithValue("#dept_list", tvp);
tvparam.SqlDbType = SqlDbType.Structured;
...
}
You can also use a split function. Many exist, this is the one I like if you can guarantee that the input is safe (no <, >, & etc.):
CREATE FUNCTION dbo.SplitInts_XML
(
#List VARCHAR(MAX),
#Delimiter CHAR(1)
)
RETURNS TABLE
AS
RETURN
(
SELECT Item = y.i.value('(./text())[1]', 'int')
FROM
(
SELECT x = CONVERT(XML, '<i>'
+ REPLACE(#List, #Delimiter, '</i><i>') + '</i>').query('.')
) AS a
CROSS APPLY x.nodes('i') AS y(i)
);
GO
Now your procedure can be:
CREATE PROCEDURE dbo.RetrieveDepartments
#dept_list VARCHAR(MAX)
AS
BEGIN
SET NOCOUNT ON;
;WITH d AS (SELECT ID = Item FROM dbo.SplitInts(#dept_list, ','))
SELECT Name FROM dbo.table1 WHERE ID IN (SELECT ID FROM d)
UNION ALL
SELECT Name FROM dbo.table2 WHERE ID IN (SELECT ID FROM d)
-- ...
END
GO

Suggestion for a tag cloud algorithm

I have a MSSQL 2005 table:
[Companies](
[CompanyID] [int] IDENTITY(1,1) NOT NULL,
[Title] [nvarchar](128),
[Description] [nvarchar](256),
[Keywords] [nvarchar](256)
)
I want to generate a tag cloud for this companies. But I've saved all keywords in one column separated by commas. Any suggestions for how to generate tag cloud by most used keywords. There could be millions of companies approx ten keywords per company.
Thank you.
Step 1: separate the keywords into a proper relation (table).
CREATE TABLE Keywords (KeywordID int IDENTITY(1,1) NOT NULL
, Keyword NVARCHAR(256)
, constraint KeywordsPK primary key (KeywordID)
, constraint KeywordsUnique unique (Keyword));
Step 2: Map the many-to-many relation between companies and tags into a separate table, like all many-to-many relations:
CREATE TABLE CompanyKeywords (
CompanyID int not null
, KeywordID int not null
, constraint CompanyKeywords primary key (KeywordID, CompanyID)
, constraint CompanyKeyword_FK_Companies
foreign key (CompanyID)
references Companies(CompanyID)
, constraint CompanyKeyword_FK_Keywords
foreign key (KeywordID)
references Keywords (KeywordID));
Step 3: Use a simple GROUP BY query to generate the 'cloud' (by example taking the 'cloud' to mean the most common 100 tags):
with cte as (
SELECT TOP 100 KeywordID, count(*) as Count
FROM CompanyKeywords
group by KeywordID
order by count(*) desc)
select k.Keyword, c.Count
from cte c
join Keyword k on c.KeywordID = k.KeywordID;
Step 4: cache the result as it changes seldom and it computes expensively.
I'd much rather see your design normalized as suggested by Remus, but if you're at a point where you can't change your design...
You can use a parsing function (the example I'll use is taken from here), to parse your keywords and count them.
CREATE FUNCTION [dbo].[fnParseStringTSQL] (#string NVARCHAR(MAX),#separator NCHAR(1))
RETURNS #parsedString TABLE (string NVARCHAR(MAX))
AS
BEGIN
DECLARE #position int
SET #position = 1
SET #string = #string + #separator
WHILE charindex(#separator,#string,#position) <> 0
BEGIN
INSERT into #parsedString
SELECT substring(#string, #position, charindex(#separator,#string,#position) - #position)
SET #position = charindex(#separator,#string,#position) + 1
END
RETURN
END
go
create table MyTest (
id int identity,
keywords nvarchar(256)
)
insert into MyTest
(keywords)
select 'sql server,oracle,db2'
union
select 'sql server,oracle'
union
select 'sql server'
select k.string, COUNT(*) as count
from MyTest mt
cross apply dbo.fnParseStringTSQL(mt.keywords,',') k
group by k.string
order by count desc
drop function dbo.fnParseStringTSQL
drop table MyTest
Both Remus and Joe are correct but yes as what Joe said if you dont have a choice then you have to live with it. I think I can offer you an easy solution by using an XML Data Type. You can already easily view the parsed column by doing this query
WITH myCommonTblExp AS (
SELECT CompanyID,
CAST('<I>' + REPLACE(Keywords, ',', '</I><I>') + '</I>' AS XML) AS Keywords
FROM Companies
)
SELECT CompanyID, RTRIM(LTRIM(ExtractedCompanyCode.X.value('.', 'VARCHAR(256)'))) AS Keywords
FROM myCommonTblExp
CROSS APPLY Keywords.nodes('//I') ExtractedCompanyCode(X)
now knowing that you can do that, all you have to do is to group them and count, but you cannot group XML methods so my suggestion is create a view of the query above
CREATE VIEW [dbo].[DissectedKeywords]
AS
WITH myCommonTblExp AS (
SELECT
CAST('<I>' + REPLACE(Keywords, ',', '</I><I>') + '</I>' AS XML) AS Keywords
FROM Companies
)
SELECT RTRIM(LTRIM(ExtractedCompanyCode.X.value('.', 'VARCHAR(256)'))) AS Keywords
FROM myCommonTblExp
CROSS APPLY Keywords.nodes('//I') ExtractedCompanyCode(X)
GO
and perform your count on that view
SELECT Keywords, COUNT(*) AS KeyWordCount FROM DissectedKeywords
GROUP BY Keywords
ORDER BY Keywords
Anyways here is the full article -->http://anyrest.wordpress.com/2010/08/13/converting-parsing-delimited-string-column-in-sql-to-rows/

Categories