SQL performance width versus depth - sql

I have a customers table that holds information about customer prefrences like if he wants to receive a newsletter and so on. If he/she wants to receive a newsletter, the value is stored in the column "customerNewsletter" and set to true. However I have a couple of these bit values and parameters that are in a column of there own. I store dates, true/false, integers and tekst like this for each customer.
I find that about 80% of my customers wants to receive a newsletter and that makes that 80% of the values is set to true. I now store a value for each customer set to false or true. What if I only should have to store the 20% set to false ??
There is a list of about 20 of these parameters that I could include as a column (they are now), but I was wondering if there is a better way.
So I create 3 tables to hold these parameter values, a param table holding the actual value, a paramsNames table, that holds the names of the values and a params table that connects the parameters to a customerID
SELECT
customerParamsName as [Name],
customerParamText as [Text],
customerParamINT as [int],
customerParamsDateTime as [Date]
FROM db14.customerParams
INNER JOIN db14.customerParam ON customerParamsChildID = customerParamID
INNER JOIN db14.customerParamsNames ON customerParamNameID = customerParamsNameID
This would give me
Name Text int Date
Phonenumber NULL 615164898 2013-09-20 00:00:00.000
Can anyone tell me if this is a good way to go, or are there more common ways of storing Multi-Type parameters more efficiently ?
AFTER some MORE consideration
I have created 2 tables
customerParam
paramID paramNameID ParamParentID paramChildID paramText paramINT paramDate
INT TINYINT INT INT varchar(24) INT DATETIME
PRIMARY INDEXED
customerParamNames
paramNameID paramName
TINYINT VARCHAR(24)
PRIMARY
1 'FirstName'
2 'LastName'
3 'Email Address'
4 'Phonenumber'
5 etc..
Let's say I want to store the firstName and LastName
I create records in customerParam for both values ;
paramID paramNameID ParamParentID paramChildID paramText paramINT paramDate
17456 1 'John'
17467 2 'Doo'
17468 1 752 17456
17469 2 752 17467
As I expect more occurrences for the name ‘John’ I am storing it as an independent value, then joining it using the parentID/ChildID combination.
and for the phoneNumber
17470 4 752 31615164899
17471 5 752 'me#here.com'
The phonenumber is very explicit to this customer, I am using the parentID to join it straight to the customer. The same goes for the emailaddress.
At this time this solution looks like the way to go... I am also still looking at the xml approach but I don’t have a good understanding on how to use XQuery and xmlDocuments stored in a database.
And It seems like a lot of overhead.
I will move forward with the solution above... until someone gives me a better one.
Example SQL
DECLARE #paramNames TABLE (paramNameID TINYINT, paramName varchar(24))
DECLARE #param TABLE (paramID INT, paramNameID TINYINT, paramParentID INT, paramChildID INT, paramText varchar(24), paramINT INT, paramDate datetime)
INSERT INTO #paramNames VALUES ( 1, 'firstname')
INSERT INTO #paramNames VALUES ( 2, 'lastname')
INSERT INTO #paramNames VALUES ( 3, 'emailaddress')
INSERT INTO #paramNames VALUES ( 4, 'phonenumber')
select * from #paramNames
INSERT INTO #param VALUES (1, 1, Null, Null, 'John' , Null, Null)
INSERT INTO #param VALUES (2, 2, Null, Null, 'Doo' , Null, Null)
INSERT INTO #param VALUES (3, 1, 752, 1, Null , Null, Null)
INSERT INTO #param VALUES (4, 2, 752, 2, Null , Null, Null)
INSERT INTO #param VALUES (5, 4, 752, Null, Null , 615164899, Null)
INSERT INTO #param VALUES (5, 3, 752, Null, 'me#here.com' , Null, Null)
select
a.paramParentID, b.paramName, c.paramText, c.paramINT, c.paramDate
from #param a
inner join #paramNames b on a.paramNameID = b.paramNameID
inner join #param c on a.paramChildID = c.paramID
UNION ALL
select
a.paramParentID, b.paramName, a.paramText, a.paramINT, a.paramDate
from #param a
inner join #paramNames b on a.paramNameID = b.paramNameID
WHERE paramParentID IS NOT NULL
AND paramChildID IS NULL
giving the result
paramParentID paramName paramText paramINT paramDate
752 firstname John NULL NULL
752 lastname Doo NULL NULL
752 phonenumber NULL 615164899 NULL
752 emailaddress me#here.com NULL NULL

I would approach this a little differently if you have performance and flexibility in mind.
USE Test;
CREATE TABLE Customers
(
CustomerID INT NOT NULL CONSTRAINT PK_Customers
PRIMARY KEY CLUSTERED IDENTITY(1,1)
, CustomerName NVARCHAR(255)
);
CREATE TABLE CustomersReceivingEmails
(
CustomerID INT NOT NULL CONSTRAINT FK_CustomerID
FOREIGN KEY REFERENCES Customers (CustomerID)
ON DELETE CASCADE ON UPDATE CASCADE
, EmailAddress NVARCHAR(255) NOT NULL
CONSTRAINT PK_CustomersReceivingEmails
PRIMARY KEY CLUSTERED (CustomerID, EmailAddress)
);
INSERT INTO Customers (CustomerName) VALUES ('Max');
INSERT INTO Customers (CustomerName) VALUES ('Mike');
INSERT INTO CustomersReceivingEmails (CustomerID, EmailAddress)
VALUES (1, 'us#them.com');
INSERT INTO CustomersReceivingEmails (CustomerID, EmailAddress)
VALUES (1, 'us#me.com');
/* ALL Customers */
SELECT * FROM Customers;
/* Only customers who wish to receive Emails, allows a given customer
to have multiple email addresses */
SELECT C.CustomerName, E.EmailAddress
FROM Customers C
INNER JOIN CustomersReceivingEmails E ON C.CustomerID = E.CustomerID
ORDER BY C.CustomerName, E.EmailAddress;
The SELECT returns rows like this:
This allows the Customers table to contain all customers regardless of their preference for emails.
The CustomersReceivingEmails table has a foreign key to Customers.CustomerID for customers who want to receive emails.

Your second solution is a variant of what is commonly known as Entity-Attribute-Value data model. This approach appears to be flexible. However, it essentially generates a schema within schema and is very slow to query as the number of attributes increases
If you're storing a lot of identical values, have a look at columnstore indexes. They work well in scenarios where selectivity is low (lots of rows & only a small number of distinct values)

Related

How to split data in SQL Server table row

I have table of transaction which contains a column transactionId that has values like |H000021|B1|.
I need to make a join with table Category which has a column CategoryID with values like H000021.
I cannot apply join unless data is same.
So I want to split or remove the unnecessary data contained in TransctionId so that I can join both tables.
Kindly help me with the solutions.
Create a computed column with the code only.
Initial scenario:
create table Transactions
(
transactionId varchar(12) primary key,
whatever varchar(100)
)
create table Category
(
transactionId varchar(7) primary key,
name varchar(100)
)
insert into Transactions
select'|H000021|B1|', 'Anything'
insert into Category
select 'H000021', 'A category'
Add computed column:
alter table Transactions add transactionId_code as substring(transactionid, 2, 7) persisted
Join using the new computed column:
select *
from Transactions t
inner join Category c on t.transactionId_code = c.transactionId
Get a straighforward query plan:
You should fix your data so the columns are the same. But sometimes we are stuck with other people's bad design decisions. In particular, the transaction data should contain a column for the category -- even if the category is part of the id.
In any case:
select . . .
from transaction t join
category c
on transactionid like '|' + categoryid + |%';
Or if the category id is always 7 characters:
select . . .
from transaction t join
category c
on categoryid = substring(transactionid, 2, 7)
You can do this using query :
CREATE TABLE #MyTable
(PrimaryKey int PRIMARY KEY,
KeyTransacFull varchar(50)
);
GO
CREATE TABLE #MyTransaction
(PrimaryKey int PRIMARY KEY,
KeyTransac varchar(50)
);
GO
INSERT INTO #MyTable
SELECT 1, '|H000021|B1|'
INSERT INTO #MyTable
SELECT 2, '|H000021|B1|'
INSERT INTO #MyTransaction
SELECT 1, 'H000021'
SELECT * FROM #MyTable
SELECT * FROM #MyTransaction
SELECT *
FROM #MyTable
JOIN #MyTransaction ON KeyTransacFull LIKE '|'+KeyTransac+'|%'
DROP TABLE #MyTable
DROP TABLE #MyTransaction

How do I search for ALL words within ANY columns of multiple Full Text indexes?

If I have two full text indexes on tables such as Contacts and Companies, how can I write a query that ensures ALL the words of the search phrase exist within either of the two indexes?
For example, if I'm searching for contacts where all the keywords exist in either the contact record or the company, how would I write the query?
I've tried doing CONTAINSTABLE on both the contact and company tables and then joining the tables together, but if I pass the search phrase in to each as '"searchTerm1*' AND '"searchTerm2*"' then it only matches when all the search words are on both indexes and returns too few records. If I pass it in like '"searchTerm1*' OR '"searchTerm2*"' then it matches where any (instead of all) of the search words are in either of the indexes and returns too many records.
I also tried creating an indexed view that joins contacts to companies so I could search across all the columns in one shot, but unfortunately a contact can belong to more than one company and so the ContactKey that I was going to use as the key for the view is no longer unique and so it fails to be created.
It seems like maybe I need to break the phrase apart and query for each word separately and then join the results back together to be able to ensure all the words were matched on, but I can't think of how I'd write that query.
Here's an example of what the model could look like:
Contact CompanyContact Company
-------------- -------------- ------------
ContactKey ContactKey CompanyKey
FirstName CompanyKey CompanyName
LastName
I have a Full Text index on FirstName,LastName and another on CompanyName.
This answer is rebuilt to address your issue such that multiple strings must exist ACROSS the fields. Note the single key in the CompanyContactLink linking table:
CREATE FULLTEXT CATALOG CompanyContact WITH ACCENT_SENSITIVITY = OFF
GO
CREATE TABLE Contact ( ContactKey INT IDENTITY, FirstName VARCHAR(20) NOT NULL, LastName VARCHAR(20) NOT NULL )
ALTER TABLE Contact ADD CONSTRAINT PK_Contact PRIMARY KEY NONCLUSTERED ( ContactKey )
CREATE TABLE Company ( CompanyKey INT IDENTITY, CompanyName VARCHAR(50) NOT NULL )
ALTER TABLE Company ADD CONSTRAINT PK_Company PRIMARY KEY NONCLUSTERED ( CompanyKey )
GO
CREATE TABLE CompanyContactLink ( CompanyContactKey INT IDENTITY NOT NULL, CompanyKey INT NOT NULL, ContactKey INT NOT NULL )
GO
INSERT INTO Contact ( FirstName, LastName ) VALUES ( 'Dipper', 'Pines' )
INSERT INTO Contact ( FirstName, LastName ) VALUES ( 'Mabel', 'Pines' )
INSERT INTO Contact ( FirstName, LastName ) VALUES ( 'Stanley', 'Pines' )
INSERT INTO Contact ( FirstName, LastName ) VALUES ( 'Soos', 'Ramirez' )
INSERT INTO Contact ( FirstName, LastName ) VALUES ( 'Wendy', 'Corduroy' )
INSERT INTO Contact ( FirstName, LastName ) VALUES ( 'Sheriff', 'Blubs' )
INSERT INTO Contact ( FirstName, LastName ) VALUES ( 'Bill', 'Cipher' )
INSERT INTO Contact ( FirstName, LastName ) VALUES ( 'Pine Dip', 'Nobody' )
INSERT INTO Contact ( FirstNAme, LastName ) VALUES ( 'Nobody', 'Pine Dip' )
INSERT INTO Company ( CompanyName ) VALUES ( 'Mystery Shack' )
INSERT INTO Company ( CompanyName ) VALUES ( 'Greesy Diner' )
INSERT INTO Company ( CompanyName ) VALUES ( 'Watertower' )
INSERT INTO Company ( CompanyName ) VALUES ( 'Manotaur Cave' )
INSERT INTO Company ( CompanyName ) VALUES ( 'Big Dipper Watering Hole' )
INSERT INTO Company ( CompanyName ) VALUES ( 'Lost Pines Dipping Pool' )
GO
INSERT INTO CompanyContactLink Values (3, 5), (1, 1), (1, 2), (1, 3), (1, 4), (1,5), (5,1), (3,1), (4,1)
GO
CREATE FULLTEXT INDEX ON Contact (LastName, FirstName)
KEY INDEX PK_Contact
ON CompanyContact
WITH STOPLIST = SYSTEM
CREATE FULLTEXT INDEX ON Company (CompanyName)
KEY INDEX PK_Company
ON CompanyContact
WITH STOPLIST = SYSTEM
GO
CREATE VIEW CompanyContactView
WITH SCHEMABINDING
AS
SELECT
CompanyContactKey,
CompanyName,
FirstName,
LastName
FROM
dbo.CompanyContactLink
INNER JOIN dbo.Company ON Company.CompanyKey = CompanyContactLink.CompanyKey
INNER JOIN dbo.Contact ON Contact.ContactKey = CompanyContactLink.ContactKey
GO
CREATE UNIQUE CLUSTERED INDEX idx_CompanyContactView ON CompanyContactView (CompanyContactKey);
GO
CREATE FULLTEXT INDEX ON CompanyContactView (CompanyName, LastName, FirstName)
KEY INDEX idx_CompanyContactView
ON CompanyContact
WITH STOPLIST = SYSTEM
GO
-- Wait a few moments for the FULLTEXT INDEXing to take place.
-- Check to see how the index is doing ... repeat the following line until you get a zero back.
DECLARE #ReadyStatus INT
SET #ReadyStatus = 1
WHILE (#ReadyStatus != 0)
BEGIN
SELECT #ReadyStatus = FULLTEXTCATALOGPROPERTY('CompanyContact', 'PopulateStatus')
END
SELECT
CompanyContactView.*
FROM
CompanyContactView
WHERE
FREETEXT((FirstName,LastName,CompanyName), 'Dipper') AND
FREETEXT((FirstName,LastName,CompanyName), 'Shack')
GO
And for the sake of your example with Wendy at the Watertower:
SELECT
CompanyContactView.*
FROM
CompanyContactView
WHERE
FREETEXT((FirstName,LastName,CompanyName), 'Wendy') AND
FREETEXT((FirstName,LastName,CompanyName), 'Watertower')
GO
I created a method that works with any number full text indexes and columns. Using this method, it is very easy to add additional facets to search for.
Split the search phrase into rows in a temp table
Join to this temp table to search for each search term using CONTAINSTABLE on each applicable full text index.
Union the results together and get the distinct count of the search terms found.
Filter out results where the number of search terms specified does not match the number of search terms found.
Example:
DECLARE #SearchPhrase nvarchar(255) = 'John Doe'
DECLARE #Matches Table(
MentionedKey int,
CoreType char(1),
Label nvarchar(1000),
Ranking int
)
-- Split the search phrase into separate words.
DECLARE #SearchTerms TABLE (Term NVARCHAR(100), Position INT)
INSERT INTO #SearchTerms (Term, Position)
SELECT dbo.ScrubSearchTerm(Term)-- Removes invalid characters and convert the words into search tokens for Full Text searching such as '"word*"'.
FROM dbo.SplitSearchTerms(#SearchPhrase)
-- Count the search words.
DECLARE #numSearchTerms int = (SELECT COUNT(*) FROM #SearchTerms)
-- Find the matching contacts.
;WITH MatchingContacts AS
(
SELECT
[ContactKey] = sc.[KEY],
[Ranking] = sc.[RANK],
[Term] = st.Term
FROM #SearchTerms st
CROSS APPLY dbo.SearchContacts(st.Term) sc -- I wrap my CONTAINSTABLE query in a Sql Function for convenience
)
-- Find the matching companies
,MatchingContactCompanies AS
(
SELECT
c.ContactKey,
Ranking = sc.[RANK],
st.Term
FROM #SearchTerms st
CROSS APPLY dbo.SearchCompanies(st.Term) sc
JOIN dbo.CompanyContact cc ON sc.CompanyKey = cc.CompanyKey
JOIN dbo.Contact c ON c.ContactKey = cc.ContactKey
)
-- Find the matches where ALL search words were found.
,ContactsWithAllTerms AS
(
SELECT
c.ContactKey,
Ranking = SUM(x.Ranking)
FROM (
SELECT ContactKey, Ranking, Term FROM MatchingContacts UNION ALL
SELECT ContactKey, Ranking, Term FROM MatchingContactCompanies
) x
GROUP BY c.ContactKey
HAVING COUNT(DISTINCT x.Term) = #numSearchTerms
)
SELECT
*
FROM ContactsWithAllTerms c
Update
Per the comments, here's an example of my SearchContacts function. It's just a simple wrapper function because I was using it in multiple procedures.
CREATE FUNCTION [dbo].[SearchContacts]
(
#contactsKeyword nvarchar(4000)
)
RETURNS #returntable TABLE
(
[KEY] int,
[RANK] int
)
AS
BEGIN
INSERT #returntable
SELECT [KEY],[RANK] FROM CONTAINSTABLE(dbo.Contact, ([FullName],[LastName],[FirstName]), #contactsKeyword)
RETURN
END
GO

UPDATE source table, AFTER Grouping?

I have a table (source) with payments for a person - called 'Item' in the example below.
This table will have payments for each person, added to it over a period.
I then generate invoices, which basically takes all the payments for a particular person, and sums them up into a single row.
This must be stored in an invoice table, for auditing reasons.
I do this in the example below.
What I am missing, though, as I am not sure how to do it, is that each payment, once assigned to the Invoice table, needs to had the Invoice ID that it was assigned to, stored in the Items table.
So, see the example below:
CREATE TABLE Items
(
ID INT NOT NULL IDENTITY(1,1),
PersonID INT NOT NULL,
PaymentValue DECIMAL(16,2) NOT NULL,
AssignedToInvoiceID INT NULL
)
CREATE TABLE Invoice
(
ID INT NOT NULL IDENTITY(1,1),
PersonID INT NOT NULL,
Value DECIMAL(16,2)
)
INSERT INTO Items (PersonID, PaymentValue) VALUES (1, 100)
INSERT INTO Items (PersonID, PaymentValue) VALUES (2, 132)
INSERT INTO Items (PersonID, PaymentValue) VALUES (2, 65)
INSERT INTO Items (PersonID, PaymentValue) VALUES (1, 25)
INSERT INTO Items (PersonID, PaymentValue) VALUES (3, 69)
SELECT * FROM Items
INSERT INTO Invoice (PersonID, Value)
SELECT PersonID, SUM(PaymentValue) FROM Items
WHERE AssignedToInvoiceID IS NULL
GROUP BY PersonID
SELECT * FROM Invoice
DROP TABLE Items
DROP TABLE Invoice
What I need to do is then update the Items table, to say that the first row has been assigned to Invoice.ID 1, row two was assigned to Invoice ID 2. Row 3, was assigned to Invoice ID 2 as well.
Note, there are many other columns in the table. This is a basic example.
Simply, I need to record which invoice, each source row was assigned to.
The key thing here to ensure payments are correctly linked to invoices is to ensure that:
A: No updates are made to Items between reading the unassigned items and updating AssignedToInvoiceID.
B: No new invoices are created with the Items being process before updating AssignedToInvoiceID.
As you are updating two tables it will have to be a two step process. To ensure A it will need to be run in a transaction with a least REPEATABLE READ isolation. To ensure B requires a transaction with SERIALIZABLE isolation. See SET TRANSACTION ISOLATION LEVEL
It can be done like this:
BEGIN TRAN
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE
DECLARE #newInvoices TABLE (PersonID INT, InvoiceID INT)
INSERT INTO Invoice (PersonID, Value)
OUTPUT inserted.ID, inserted.PersonID INTO #newInvoices(InvoiceID, PersonID)
SELECT PersonID, SUM(PaymentValue) FROM Items
WHERE AssignedToInvoiceID IS NULL
GROUP BY PersonID
UPDATE Items
SET AssignedToInvoiceID = InvoiceID
FROM Items
INNER JOIN #newInvoices newInvoice ON newInvoice.PersonID = Items.PersonID
WHERE AssignedToInvoiceID IS NULL
COMMIT
An alternative if you are using SQL Server 2012 or later is to use the SEQUNCE object, this will allow the Items to be assigned new invoice IDs before the Invoices are created, reducing the locking required.
It works like this:
-- Run once with your table setup.
CREATE SEQUENCE InvoiceIDs AS INT START WITH 1 INCREMENT BY 1
CREATE TABLE Items
(
ID INT NOT NULL IDENTITY(1,1),
PersonID INT NOT NULL,
PaymentValue DECIMAL(16,2) NOT NULL,
AssignedToInvoiceID INT NULL
)
CREATE TABLE Invoice
(
-- No longer a IDENTITY column
ID INT NOT NULL,
PersonID INT NOT NULL,
Value DECIMAL(16,2)
)
BEGIN TRAN
DECLARE #newInvoiceLines TABLE (PersonID INT, InvoiceID INT, PaymentValue DECIMAL(16,2))
-- Reading and updating AssignedToInvoiceID happens in one query so is thread safe.
UPDATE Items
SET AssignedToInvoiceID = newInvoices.InvoiceID
OUTPUT inserted.PersonID, inserted.AssignedToInvoiceID, inserted.PaymentValue
INTO #newInvoiceLines(PersonID, InvoiceID, PaymentValue)
FROM Items
INNER JOIN (
SELECT PersonID, NEXT VALUE FOR InvoiceIDs AS InvoiceID
FROM Items
GROUP BY PersonID
) AS newInvoices ON newInvoices.PersonID = Items.PersonID
WHERE Items.AssignedToInvoiceID IS NULL
INSERT INTO Invoice (ID, PersonID, Value)
SELECT InvoiceID, PersonID, SUM(PaymentValue) FROM #newInvoiceLines
GROUP BY PersonID, InvoiceID
COMMIT
You will still want to use a transaction to ensure the Invoice gets created.
Based on what I understand, you could run an update from join after you have inserted records into Invoices table, like so:
update items
set assignedtoinvoiceid = v.id
from
items m
inner join invoice v on m.personid = v.personid
Demo
Each time you do an invoice "run", select the most recent invoice for each person something like,
update items
set AssignedToInvoiceID = inv.id
from
(select personid, max(id) id
from invoice
group by personid) inv
where items.personid = inv.personid
and AssignedToInvoiceID is null
this assumes that AssignedToInvoiceID is null when it isn't populated, if it gets defaulted to an empty string or something then you would need to change the where condition.
1) Get MAX(ID) from Invoice table before inserting new rows from Items table. Store this value into a variable: #MaxInvoiceID
2) After inserting records into Invoice table, update AssignedToInvoiceID in Items table with Invoice.ID>#MaxInvoiceID
Refer below code:
CREATE TABLE #Items
(
ID INT NOT NULL IDENTITY(1,1),
PersonID INT NOT NULL,
PaymentValue DECIMAL(16,2) NOT NULL,
AssignedToInvoiceID INT NULL
)
CREATE TABLE #Invoice
(
ID INT NOT NULL IDENTITY(1,1),
PersonID INT NOT NULL,
Value DECIMAL(16,2)
)
DECLARE #MaxInvoiceID INT;
SELECT #MaxInvoiceID=ISNULL(MAX(ID),0) FROM #Invoice
SELECT #MaxInvoiceID
INSERT INTO #Items (PersonID, PaymentValue) VALUES (1, 100)
INSERT INTO #Items (PersonID, PaymentValue) VALUES (2, 132)
INSERT INTO #Items (PersonID, PaymentValue) VALUES (2, 65)
INSERT INTO #Items (PersonID, PaymentValue) VALUES (1, 25)
INSERT INTO #Items (PersonID, PaymentValue) VALUES (3, 69)
SELECT * FROM #Items
INSERT INTO #Invoice (PersonID, Value)
SELECT PersonID, SUM(PaymentValue)
FROM #Items
WHERE AssignedToInvoiceID IS NULL
GROUP BY PersonID
SELECT * FROM #Invoice
UPDATE Itm
SET Itm.AssignedToInvoiceID=Inv.ID
FROM #Items Itm
JOIN #Invoice Inv ON Itm.PersonID=Inv.PersonID AND Itm.AssignedToInvoiceID IS NULL AND Inv.ID>#MaxInvoiceID
SELECT * FROM #Items
DROP TABLE #Items
DROP TABLE #Invoice

How to convert a varchar value to datatype int in SQL Server 2008 with inner join

I have two lookup tables MyProviders and MyGroups. In my stored procedure, I have a temp table (replaced with an actual table for this example) with data. One column EntityId refers to either provider or a group. EntityTypeId tells me in that temp table if the entity is 1 = Provider or 2 = Group. EntityId can either have numeric GroupId or alphanumeric ExternalProviderId.
I want to check if there is any record in my temp table that has an invalid combination of clientOid + entityid from myprovider and mygroup table.
create table MyProviders
(
id int,
clientoid varchar(20),
externalproviderid varchar(20),
name varchar(25)
)
create table MyGroups
(
id int,
clientoid varchar(20),
name varchar(25)
)
create table MyJobDetails
(
clientoid varchar(20),
entityid varchar(20),
entitytypeid int,
entityname varchar(30)
)
insert into MyJobDetails values ('M.OID', 'MONYE', 1, 'Mark')
insert into MyJobDetails values ('M.OID', 2, 1, 'Lori')
insert into MyJobDetails values ('M.OID', 2, 2, 'Group 1')
insert into MyJobDetails values ('M.OID', 44444, 2, 'Group 2')
insert into MyProviders values (1, 'M.OID', 'MONY', 'Richard')
insert into MyProviders values (2, 'M.OID', '2', 'Mike')
insert into MyProviders values (3, 'M.OID', '3', 'Lori')
insert into MyGroups values (1, 'M.OID', 'Group 1')
insert into MyGroups values (2, 'M.OID', 'Group 2')
I tried the following query to determine if there is an invalid entity or not.
select
COUNT(*)
from
MyJobDetails as jd
where
not exists (select 1
from MyProviders as p
where p.ClientOID = jd.ClientOID
and p.ExternalProviderID = CAST(jd.EntityId as varchar(20))
and jd.EntityTypeId = 1)
and not exists (select 1
from MyGroups as g
where g.ClientOID = jd.ClientOID
and g.Id = jd.EntityId
and jd.EntityTypeId = 2)
This works as expected until I get an alphanumeric data in my temp table that doesn't exist in provider table. I get the following error message:
Conversion failed when converting the varchar value 'MONYE' to data type int.
I have tried to update the solutions mentioned in other threads to use IsNumeric but it didn't work either. In this example, I need to return 1 for one invalid entry of MONYE which doesn't exist either in MyProvider or MyGroup table.
Also, if I can optimize the query in better way to achieve what I want?
This is a really bad design in my opinion.
Since you're referencing one out of two tables, you cannot enforce referential integrity.
And having different datatypes for your keys makes things even more horrible.
I would use
two separate foreign keys in MyJobDetails - one to MyProvider (varchar(20)) and another one to MyGroup (int)
make them both nullable
establish a proper foreign key relationship to the referenced table for each of those two
This way, both can be the correct datatype for each referenced table, and you won't need the EntityTypeId column anymore.
As a side note: whenever you use Varchar in SQL Server, whether you're defining a parameter, a variable, or using it in a CAST statement, I would recommend to always explicitly define a length for that varchar.
Or do you know what length this varchar in your conversion here is going to be?
CAST(jd.EntityId as varchar)
Use an explicit length - always - it's just a good, safe practice to employ:
CAST(jd.EntityId as varchar(15))
In the second AND NOT EXISTS section you compare g.Id, an int, with jd.EntityId, a varchar. Cast the g.Id as a varchar.
and not exists (select 1
from #MyGroups as g
where g.ClientOID = jd.ClientOID
and CAST(g.Id AS VARCHAR(20)) = jd.EntityId
and jd.EntityTypeId = 2)
Try this
select count(*)
from (
select clientoid,entityid from #MyJobDetails where entitytypeid=1
except
select p.ClientOID ,convert(varchar(200),p.ExternalProviderID) from #MyProviders p inner join #MyJobDetails jd on p.ClientOID = jd.ClientOID and p.ExternalProviderID = CAST(jd.EntityId as varchar(20)) where jd.EntityTypeId = 1
except
select g.ClientOID,convert(varchar(200),g.Id) from #MyGroups g inner join #MyJobDetails jd on g.ClientOID = jd.ClientOID and g.Id = jd.EntityId where jd.EntityTypeId = 2
)a

How to automatically generate unique id in SQL like UID12345678?

I want to automatically generate unique id with per-defined code attach to it.
ex:
UID12345678
CUSID5000
I tried uniqueidentifier data type but it generate a id which is not suitable for a user id.
Any one have suggestions?
The only viable solution in my opinion is to use
an ID INT IDENTITY(1,1) column to get SQL Server to handle the automatic increment of your numeric value
a computed, persisted column to convert that numeric value to the value you need
So try this:
CREATE TABLE dbo.tblUsers
(ID INT IDENTITY(1,1) NOT NULL PRIMARY KEY CLUSTERED,
UserID AS 'UID' + RIGHT('00000000' + CAST(ID AS VARCHAR(8)), 8) PERSISTED,
.... your other columns here....
)
Now, every time you insert a row into tblUsers without specifying values for ID or UserID:
INSERT INTO dbo.tblUsersCol1, Col2, ..., ColN)
VALUES (Val1, Val2, ....., ValN)
then SQL Server will automatically and safely increase your ID value, and UserID will contain values like UID00000001, UID00000002,...... and so on - automatically, safely, reliably, no duplicates.
Update: the column UserID is computed - but it still OF COURSE has a data type, as a quick peek into the Object Explorer reveals:
CREATE TABLE dbo.tblUsers
(
ID INT IDENTITY(1,1) NOT NULL PRIMARY KEY CLUSTERED,
UserID AS 'UID' + RIGHT('00000000' + CAST(ID AS VARCHAR(8)), 8) PERSISTED,
[Name] VARCHAR(50) NOT NULL,
)
marc_s's Answer Snap
Reference:https://learn.microsoft.com/en-us/sql/t-sql/functions/newid-transact-sql?view=sql-server-2017
-- Creating a table using NEWID for uniqueidentifier data type.
CREATE TABLE cust
(
CustomerID uniqueidentifier NOT NULL
DEFAULT newid(),
Company varchar(30) NOT NULL,
ContactName varchar(60) NOT NULL,
Address varchar(30) NOT NULL,
City varchar(30) NOT NULL,
StateProvince varchar(10) NULL,
PostalCode varchar(10) NOT NULL,
CountryRegion varchar(20) NOT NULL,
Telephone varchar(15) NOT NULL,
Fax varchar(15) NULL
);
GO
-- Inserting 5 rows into cust table.
INSERT cust
(CustomerID, Company, ContactName, Address, City, StateProvince,
PostalCode, CountryRegion, Telephone, Fax)
VALUES
(NEWID(), 'Wartian Herkku', 'Pirkko Koskitalo', 'Torikatu 38', 'Oulu', NULL,
'90110', 'Finland', '981-443655', '981-443655')
,(NEWID(), 'Wellington Importadora', 'Paula Parente', 'Rua do Mercado, 12', 'Resende', 'SP',
'08737-363', 'Brasil', '(14) 555-8122', '')
,(NEWID(), 'Cactus Comidas para Ilevar', 'Patricio Simpson', 'Cerrito 333', 'Buenos Aires', NULL,
'1010', 'Argentina', '(1) 135-5555', '(1) 135-4892')
,(NEWID(), 'Ernst Handel', 'Roland Mendel', 'Kirchgasse 6', 'Graz', NULL,
'8010', 'Austria', '7675-3425', '7675-3426')
,(NEWID(), 'Maison Dewey', 'Catherine Dewey', 'Rue Joseph-Bens 532', 'Bruxelles', NULL,
'B-1180', 'Belgium', '(02) 201 24 67', '(02) 201 24 68');
GO
If you want to add the id manually you can use,
PadLeft() or String.Format() method.
string id;
char x='0';
id=id.PadLeft(6, x);
//Six character string id with left 0s e.g 000012
int id;
id=String.Format("{0:000000}",id);
//Integer length of 6 with the id. e.g 000012
Then you can append this with UID.
The 'newid()' method unique id generate for per record.
AddColumn("dbo.Foo", "Key", c => c.String(nullable: false, maxLength: 250, defaultValueSql: "newid()"));
Table Creating
create table emp(eno int identity(100001,1),ename varchar(50))
Values inserting
insert into emp(ename)values('narendra'),('ajay'),('anil'),('raju')
Select Table
select * from emp
Output
eno ename
100001 narendra
100002 rama
100003 ajay
100004 anil
100005 raju