SQL Insert Price with better coincidence of code - c#

I have an Excel file where I have Products, and in the same file, I have prices that applied for some products based on a column where I have the code of products where applied.
For example, I have a product with codeAAA123and I have other product with codeAAA124. And I have 2 prices, one for products that start withAAAand another for productAAA124.
So, the price for productAAAmust be the price which applied for products that start withAAAand the price for productAAA124must be the price for product that applied exactly for products with code AAA124 because is the better coincidence. If not finds an exactly coincidence, remove one character and make another search, until find best coincidence.
I don't know how to accomplish this. I was thinking in save products in my table products, and for prices create a temporal table, then with a stored procedure find the best coincidence with something like this:
SELECT Price,
(CASE WHEN code LIKE 'AAA124' THEN 4 ELSE 0 END) +
(CASE WHEN code LIKE 'AAA12' THEN 3 ELSE 0 END) +
(CASE WHEN code LIKE 'AAA1' THEN 2 ELSE 0 END) +
(CASE WHEN code LIKE 'AAA' THEN 1 ELSE 0 END) + as rank
FROM Product
ORDER BY rank DESC
Instead of code hard coded, will be a parameter pass to a stored procedure.
If Anyone could guide me it will be much appreciated.

The code below should find a pretty decent match. You could put it into a scalar function so the price is automatically updated when you import the latest price list into a db table in your server or use it to save the price to the products table if you don't want price changes retroactive.
DECLARE #Prices TABLE (CodeMatch VARCHAR(10), Price MONEY )
DECLARE #Products TABLE (ProductCode VARCHAR(10))
INSERT #Prices VALUES ('AAA124', 10), ('AAA', 20)
INSERT #Products VALUES ('AAA123'), ('AAA124')
SELECT
ProductCode,
( SELECT TOP 1 Price FROM #Prices ORDER BY LEN(REPLACE(ProductCode, CodeMatch, '')) )
FROM #Products

Related

Update all rows in sql table with unique random value without using primary key or unique key in c#

In my application, I fetch all tables in Database.
User will select table name and colum names to be masked.
Now i want to update sql table-columns with random generate string , which must be unique for each row without using primary key or unique key.
For example, In my Employeedb i have a table Employee.
Out of columns in Employee table, i want to mask data in name and city columns.
If table conatins 1000 rows, i want change name and city columns with 1000 unique values each. That means i want to update row by row.
Name Address City
Raghav flatno34 mumbai
Ranveer flatno23 chennai
This is orignal data
Name Adress City
Sbgha flatno34 mmjgujj
Lkhhvh flatno23 huughh
This is expected out
The table have primarykey sometimes.. There may be chances of not having primary key.
I have one more qn, I have this expected output in a datatable. Since i cannot predefine the table name and number of fields how will i write an update qry.
I think you will find my blog post entitled How to pre-populate a random strings pool very helpful for this requirement.
(Inspired by this SO answer from Martin Smith, to give credit where credit is due)
It describes an inline table valued user defined function that generates a table of random values, which you can use to update your data.
However, it does not guarantee uniqueness of these values. For that, you must use DISTINCT when selecting from it.
One problem you might encounter because of that is having a result with less values than you generated, but for 1,000 records per table as you wrote in the question it's probably not going to be a problem, since the function can generate up to 1,000,000 records each time you call it.
For the sake of completeness, I'll post the code here as well, but you should probably read the post at my blog.
Also, there's another version of this function in another blog post entitled A more controllable random string generator function for SQL Server - which gives you better control over the content of the random strings - i.e a string containing only numbers, or only lower digits.
The first thing you need to do is create a view that will generate a new guid for you, because this can't be done inside a user-defined function:
CREATE VIEW GuidGenerator
AS
SELECT Newid() As NewGuid
Then, the function code: (Note: this is the simpler version)
CREATE FUNCTION dbo.RandomStringGenerator
(
#Length int,
#Count int -- Note: up to 1,000,000 rows
)
RETURNS TABLE
AS
RETURN
-- An inline tally table with 1,000,000 rows
WITH E1(N) AS (SELECT N FROM (VALUES (1), (2), (3), (4), (5), (6), (7), (8), (9), (10)) V(N)), -- 10
E2(N) AS (SELECT 1 FROM E1 a, E1 b), --100
E3(N) AS (SELECT 1 FROM E2 a, E2 b), --10,000
Tally(N) AS (SELECT ROW_NUMBER() OVER (ORDER BY ##SPID) FROM E3 a, E2 b) --1,000,000
SELECT TOP(#Count) (
SELECT TOP (#Length) CHAR(
-- create a random number from a guid using the GuidGenerator view, mod 3.
CASE Abs(Checksum(NewGuid)) % 3
WHEN 0 THEN 65 + Abs(Checksum(NewGuid)) % 26 -- Random upper case letter
WHEN 1 THEN 97 + Abs(Checksum(NewGuid)) % 26 -- Random lower case letter
ELSE 48 + Abs(Checksum(NewGuid)) % 10 -- Random digit
END
)
FROM Tally As t0
CROSS JOIN GuidGenerator
WHERE t0.n != -t1.n -- Needed for the subquery to get re-evaluated for each row
FOR XML PATH('')
) As RandomString
FROM Tally As t1
Then, you can use it like this to get a distinct random string:
SELECT DISTINCT RandomString
FROM dbo.RandomStringGenerator(50, 5000);

Checking multiple columns have any of multiple values in a Table value parameter

I am trying to create a query to help get a list of accounts from an existing database. I will have two lists of integers passed in through two Table Value Parameters(TVP) from C#. I then need to see if multiple columns have any of the values in the corresponding TVP tables. The TVP lists of integers are provided by different clients and may differ between clients. That is why they are TVP's to allow the values to be passed in as parameters.
The data structure cannot be changed, it is created based on data from another system. Comments about changing the data structure won't be helpful. To help I will talk about an example table that would help show what I need.
Looking at a table like the following:
Table Accounts
varchar(200) AccountId
int StatusId1
int StatusId2
int StatusId3
int StatusId4
int Identifier1
int Identifier2
int Identifier3
int Identifier4
int Identifier5
I know that I can do a sql statement like:
Select AccountId from Accounts where StatusId1 In (1,2,3)
I was able to learn that I can reverse the In command as well:
Select AccountId from Accounts where 1 In (StatusId1, StatusId2, StatusId3, StatusId4)
This only lets me check one value against each column. The problem is I need to mix the two while using the TVP for the list of integers.
The closest I have been able to create is the following:
--Load the TVP lists
SELECT * INTO #StatusCodes FROM #StatusId
SELECT * INTO #IdentityCodes FROM #IdentifierId
--Find the Accounts that have the chosen Ids
SELECT AccountId
FROM Accounts
WHERE StatusId1 IN( SELECT Id FROM #StatusCodes)
OR StatusId2 IN( SELECT Id FROM #StatusCodes)
OR StatusId3 IN( SELECT Id FROM #StatusCodes)
OR StatusId4 IN( SELECT Id FROM #StatusCodes)
OR Identifier1 IN (SELECT Id FROM #IdentityCodes)
OR Identifier2 IN (SELECT Id FROM #IdentityCodes)
OR Identifier3 IN (SELECT Id FROM #IdentityCodes)
OR Identifier4 IN (SELECT Id FROM #IdentityCodes)
OR Identifier5 IN (SELECT Id FROM #IdentityCodes)
This query worked in my prototype and I got back the list of accounts that had at least one of these ids. I see a lot of select statements and it doesn't look very good. I am not sure how well it performs either. I am wondering if there is a better way to do this?
This is for a system that creates a report based on conditions our clients make. Each client runs from a couple to 100 reports each night. That means this is run possibly hundreds of times each night. While it isn't a system running thousands of times per hour, it does process a lot of data. Some of the databases it will search will be big with lots of accounts to search.
One option uses exists:
select a.acountId
from accounts a
where
exists (
select 1
from #StatusCodes s
where s.id in (a.StatusId1, a.StatusId2, a.StatusId3, a.StatusId4)
)
or exists (
select 1
from #IdentityCodes i
where i.id in (a.Identifier1, a.Identifier2, a.Identifier3, a.Identifier4)
)

Updating sql records

Now I add this tables with these columns:
DrugID, BatchNo, ManufacreDate, ExpireDate, Quantity.
Note: (DrugID, BatchNo) constitute the primary key.
For example: there are 2 records as follow:
(101, 1234, 1-7-2014, 1-7-2016, 50)
(101, 7654, 1-7-2015, 1-7-2017, 80)
If, as example, one customer wants 80 item from drug with drugID=101, how could I update the table so that the first record will be removed, the second one will remain but the quantity will be modified to 30?
Any help, please?
The approach to solving this is to calculate the cumulative quantity of each drug. Then compare the cumulative amount to the desired amount to determine the rows that need updating.
The rest is a bit of arithmetic. In SQL Server 2012+, the code looks like:
declare #val int = 80;
with t as (
select t.*,
sum(qty) over (partition by drugid order by batchno) as cumeqty
from t
)
update t
set qty = (case when cumeqty - #val < 0 then 0
else cumeqty - #val
end)
where (cumeqty - #val) < qty;
In earlier versions, you have to work a bit harder to get the cumulative quantity, but the idea is the same.
Here is an example of the code (or something very similar) working in SQL Fiddle.

Records for Sales Person

I am designing this database and c# app, that a record gets saved to database. now say we have three Sales Person and each should be assigned a record in strict rotation so they get to work on equal amount of records.
What I have done so far was to create one table called Records and one SalesPerson, the Records would have salesperson id as foreign key and another column that would say which agent it is assigned to and will increment this column.
Do you think this is a good design, if not can you give any ideas?
To do this I would use the analytical functions ROW_NUMBER and NTILE (assuming your RDBMS supports them). This way you can allocate each available sales person a pseudo id incrementing upwards from 1, then randomly allocate each unassigned record one of these pseudo ids to assign them equally between sales people. Using pseudo ids rather than actual ids allows for the SalesPersonID field not being continuous. e.g.
-- CREATE SOME SAMPLE DATA
DECLARE #SalesPerson TABLE (SalesPersonID INT IDENTITY(1, 1) NOT NULL PRIMARY KEY, Name VARCHAR(50) NOT NULL, Active BIT NOT NULL)
DECLARE #Record TABLE (RecordID INT IDENTITY(1, 1) NOT NULL PRIMARY KEY, SalesPersonFK INT NULL, SomeOtherInfo VARCHAR(100))
INSERT #SalesPerson VALUES ('TEST1', 1), ('TEST2', 0), ('TEST3', 1), ('TEST4', 1);
INSERT #Record (SomeOtherInfo)
SELECT Name
FROM Sys.all_Objects
With this sample data the first step is to find the number of available sales people to allocate records to:
DECLARE #Count INT = (SELECT COUNT(*) FROM #SalesPerson WHERE Active = 1)
Next using CTEs to contain the window functions (as they can't be used in join clauses)
;WITH Records AS
( SELECT *,
NTILE(#Count) OVER(ORDER BY NEWID()) [PseudoSalesPersonID]
FROM #Record
WHERE SalesPersonFK IS NULL -- UNALLOCATED RECORDS
), SalesPeople AS
( SELECT SalesPersonID,
ROW_NUMBER() OVER (ORDER BY SalesPersonID) [RowNumber]
FROM #SalesPerson
WHERE Active = 1 -- ACTIVE SALES PEOPLE
)
Finally update the records CTE with the actual sales personID rather than a pseudo id
UPDATE Records
SET SalesPersonFK = SalesPeople.SalesPersonID
FROM Records
INNER JOIN SalesPeople
ON PseudoSalesPersonID = RowNumber
ALL COMBINED IN AN SQL FIDDLE
This is quite confusing as I suspect you're using the database term 'record' aswell as an object/entity 'Record'.
The simple concept of having a unique identifier in one table that also features as a foreign key in another table is fine though, yes. It avoids redundancy.
Basics of normalisation
Its mostly as DeeMac said. But if your Record is an object (i.e. it has all the work details or its a sale or a transaction) then you need to separate that table. Have a table Record with all the details to that particular object. Have another table `Salesman' with all the details about the Sales Person. (In a good design, you would only add particular business related attributes of the position in this table. All the personal detail will go in a different table)
Now for your problem, you can build two separate tables. One would be Record_Assignment where you will assign a Record to a Salesman. This table will hold all the active jobs. Another table will be Archived_Record_Assignment which will hold all the past jobs. You move all the completed jobs here.
For equal assignment of work, you said you want circular assignment. I am not sure if you want to spread work amongst all sales person available or only certain number. Usually assignments are given by team. Create a table (say table SalesTeam)with the Salesman ids of the sales persons you want to assign the jobs (add team id, if you have multiple teams working on their own assigned work areas or customers. That's usually the case). When you want to assign new job, query the Record_Assignment table for last record, get the Salesman id and assign the job to the next salesman in the SalesTeam table. The assignment will be done through business logic (coding).
I am not fully aware of your scenario. These are all my speculations so if you see something off according to your scenario, let me know.
Good Luck!

Passing multiple rows of data to a stored procedure

I have a list of objects (created from several text files) in C#.net that I need to store in a SQL2005 database file. Unfortunately, Table-Valued Parameters began with SQL2008 so they won't help. I found from MSDN that one method is to "Bundle multiple data values into delimited strings or XML documents and then pass those text values to a procedure or statement" but I am rather new to stored procedures and need more help than that. I know I could create a stored procedure to create one record then loop through my list and add them, but that's what I'm trying to avoid. Thanks.
Input file example (Other files contain pricing and availability):
Matnr ShortDescription LongDescription ManufPartNo Manufacturer ManufacturerGlobalDescr GTIN ProdFamilyID ProdFamily ProdClassID ProdClass ProdSubClassID ProdSubClass ArticleCreationDate CNETavailable CNETid ListPrice Weight Length Width Heigth NoReturn MayRequireAuthorization EndUserInformation FreightPolicyException
10000000 A&D ENGINEERING SMALL ADULT CUFF FOR UA-767PBT UA-279 A&D ENGINEERING A&D ENG 093764011542 GENERAL General TDINTERNL TD Internal TDINTERNL TD Internal 2012-05-13 12:18:43 N 18.000 .350 N N N N
10000001 A&D ENGINEERING MEDIUM ADULT CUFF FOR UA-767PBT UA-280 A&D ENGINEERING A&D ENG 093764046070 GENERAL General TDINTERNL TD Internal TDINTERNL TD Internal 2012-05-13 12:18:43 N 18.000 .450 N N N N
Some DataBase File fields:
EffectiveDate varchar(50)
MfgName varchar(500)
MfgPartNbr varchar(500)
Cost varchar(200)
QtyOnHand varchar(200)
You can split multiple values from a single string quite easily. Say you can bundle the string like this, using a comma to separate "columns", and a semi-colon to separate "rows":
foo, 20120101, 26; bar, 20120612, 32
(This assumes that colons and semi-colons can't appear naturally in the data; if they can, you'll need to choose other delimiters.)
You can build a split routine like this, which includes an output column that allows you to determine the order the value appeared in the original string:
CREATE FUNCTION dbo.SplitStrings
(
#List NVARCHAR(MAX),
#Delimiter NVARCHAR(255)
)
RETURNS TABLE
AS
RETURN (SELECT Number = ROW_NUMBER() OVER (ORDER BY Number),
Item FROM (SELECT Number, Item = LTRIM(RTRIM(SUBSTRING(#List, Number,
CHARINDEX(#Delimiter, #List + #Delimiter, Number) - Number)))
FROM (SELECT ROW_NUMBER() OVER (ORDER BY [object_id])
FROM sys.all_objects) AS n(Number)
WHERE Number <= CONVERT(INT, LEN(#List))
AND SUBSTRING(#Delimiter + #List, Number, 1) = #Delimiter
) AS y);
GO
Then you can query it like this (for simplicity and illustration I'm only handling 3 properties but you can extrapolate this for 11 or n):
DECLARE #x NVARCHAR(MAX); -- a parameter to your stored procedure
SET #x = N'foo, 20120101, 26; bar, 20120612, 32';
;WITH x AS
(
SELECT ID = s.Number, InnerID = y.Number, y.Item
-- parameter and "row" delimiter here:
FROM dbo.SplitStrings(#x, ';') AS s
-- output and "column" delimiter here:
CROSS APPLY dbo.SplitStrings(s.Item, ',') AS y
)
SELECT
prop1 = x.Item,
prop2 = x2.Item,
prop3 = x3.Item
FROM x
INNER JOIN x AS x2
ON x.InnerID = x2.InnerID - 1
AND x.ID = x2.ID
INNER JOIN x AS x3
ON x2.InnerID = x3.InnerID - 1
AND x2.ID = x3.ID
WHERE x.InnerID = 1
ORDER BY x.ID;
Results:
prop1 prop2 prop3
------ -------- -------
foo 20120101 26
bar 20120612 32
We use XML data types like this...
declare #contentXML xml
set #contentXML=convert(xml,N'<ROOT><V a="124694"/><V a="124699"/><V a="124701"/></ROOT>')
SELECT content_id,
FROM dbo.table c WITH (nolock)
JOIN #contentXML.nodes('/ROOT/V') AS R ( v ) ON c.content_id = R.v.value('#a', 'INT')
Here is what it would look like if calling a stored procedure...
DbCommand dbCommand = database.GetStoredProcCommand("MyStroredProcedure);
database.AddInParameter(dbCommand, "dataPubXML", DbType.Xml, dataPublicationXml);
CREATE PROC dbo.usp_get_object_content
(
#contentXML XML
)
AS
BEGIN
SET NOCOUNT ON
SELECT content_id,
FROM dbo.tblIVContent c WITH (nolock)
JOIN #contentXML.nodes('/ROOT/V') AS R ( v ) ON c.content_id = R.v.value('#a', 'INT')
END
SQL Server does not parse XML very quickly so the use of the SplitStrings function might be more performant. Just wanted to provide an alternative.
I can think of a few options, but as I was typing one of them (the Split option) was posted by Mr. #Bertrand above. The only problem with it is that SQL just isn't that good at string manipulation.
So, another option would be to use a #Temp table that your sproc assumes will be present. Build dynamic SQL to the following effect:
Start a transaction, CREATE TABLE #InsertData with the shape you need, then loop over the data you are going to insert, using INSERT INTO #InsertData SELECT <values> UNION ALL SELECT <values>....
There are some limitations to this approach, one of which is that as the data set becomes very large you may need to split the INSERTs into batches. (I don't recall the specific error I got when I learned this myself, but for very long lists of values I have had SQL complain.) The solution, though, is simple: just generate a series of INSERTs with a smaller number of rows each. For instance, you might do 10 INSERT SELECTs with 1000 UNION ALLs each instead of 1 INSERT SELECT with 10000 UNION ALLs. You can still pass the entire batch as a part of a single command.
The advantage of this (despite its various disadvantages-- the use of temporary tables, long command strings, etc) is that it offloads all the string processing to the much more efficient C# side of the equation and doesn't require an additional persistent database object (the Split function; though, again, who doesn't need one of these sometimes)?
If you DO go with a Split() function, I'd encourage you to offload this to a SQLCLR function, and NOT a T-SQL UDF (for the performance reasons illustrated by the link above).
Finally, whatever method you choose, note that you'll have more problems if your data can include strings that contain the delimiter (for instance, In Aaron's answer you run into problems if the data is:
'I pity the foo!', 20120101, 26; 'bar, I say, bar!', 20120612, 32
Again, because C# is better at string handling than T-SQL, you'll be better off without using a T-SQL UDF to handle this.
Edit
Please note the following additional point to think about for the dynamic INSERT option.
You need to decide whether any input here is potentially dangerous input and would need to be cleaned before use. You cannot easily parameterize this data, so this is a significant one. In the place I used this strategy, I already had strong guarantees about the type of the data (in particular, I have used it for seeding a table with a list of integer IDs to process, so I was iterating over integers and not arbitrary, untrusted strings). If you don't have similar assurances, be aware of the dangers of SQL injection.

Categories

Resources