Joins

A join is used to combine columns from two or more tables into a single result set. To join data from two tables you write the names of two tables in the FROM clause along with JOIN keyword and an ON phrase that specifies the join condition. The join condition indicates how two tables should be compared. In most cases they are compares on the base on the relationship of primary key of the first table and foreign key of the second table. The important Join types are :

1.       Inner Join
2.   Cross Join
3.       Outer Join
4.   Self-Join
I have two tables - Vendor table and Advance table . please refer the below Tables,


Inner Joins
An inner join requires each record in the two joined tables to have a matching record. An inner join essentially combines the records from two tables (A and B) based on a given join-predicate.

The result of the join can be defined as the outcome of first taking the Cartesian product (or cross-join) of all records in the tables (combining every record in table A with every record in table B) - then return all records which satisfy the join predicate.
Actual SQL implementations will normally use other approaches where possible, since computing the Cartesian product is not very efficient. This type of join occurs most commonly in applications, and represents the default join-type.
Example: This is explicit inner join:

Type of inner joins
1. Equi-Join
An equi-join, also known as an equijoin, is a specific type of comparator-based join, or theta join that uses only equality comparisons in the join-predicate. Using other comparison operators (such as <) disqualifies a join as an equi-join. The query shown above has already provided an example of an equi-join:





2. Natural Join

A natural join offers a further specialization of equi-joins. The join predicate arises implicitly by comparing all columns in both tables that have the same column-name in the joined tables. The resulting joined table contains only one column for each pair of equally-named columns

You can specify the required column names using sql natural join query.

Natural join query example:



Cross Join

A cross join, Cartesian join or product provides the foundation upon which all types of inner joins operate. A cross join returns the Cartesian product of the sets of records from the two joined tables. Thus, it equates to an inner join where the join-condition always evaluates to True or join-condition is absent in statement

Let left table has 6 rows and right table has 7 rows then SQL CROSS Join will return 42 rows combining each record of left table with all records of right side table. Consider the following example of CROSS Join





Equi Join returns all the columns from both tables and filters the records satisfying the matching condition specified in Join “ON” statement of sql inner join query.

Note: By just adding the where clause with Cross join sql query it turns the output result into inner join.

Outer Joins

An outer join retrieves all rows that satisfy the join condition plus unmatched rows in one or both tables. In most cases you use the equal operator to retrieve rows with matching columns. However you can also use any of the other comparison operators. When row with unmatched columns is retrieved any columns from the other table that are included in the result are given null values.
Note1: The OUTER keyword is optional and typically omitted
Note2: You can also code left outer joins and right outer joins using the implicit syntax.
Three types of outer joins:

1. Left Outer Join
The result of a left outer join (or simply left join) for tables A and B always contains all records of the "left" table (A), even if the join-condition does not find any matching record in the "right" table (B). This means that if the ON clause matches 0 (zero) records in B, the join will still return a row in the result but with NULL in each column from B. This means that a left outer join returns all the values from the left table, plus matched values from the right table (or NULL in case of no matching join predicate).




2. Right Outer Join

A right outer join (or right join) closely resembles a left outer join, except with the tables reversed. Every record from the "right" table (B) will appear in the joined table at least once. If no matching row from the "left" table (A) exists, NULL will appear in columns from A for those records that have no match in A


3. Full outer join
A full outer join combines the results of both left and right outer joins. The joined table will contain all records from both tables, and fill in NULLs for missing matches on either side.

Self-JOIN

In this particular case, one table JOINs to itself with one or two aliases to stave off confusion. A self-JOIN can be of any type, as long as the joined tables are the same. A self-JOIN is unique in the sense that it involves a relationship with only one table. A common example is when a company has a hierarchal reporting structure whereby a member of staff reports to another member. Self-JOIN can either be an Outer JOIN or an Inner JOIN.

Example :

Select EmpID,EmployeeName,ReportingToID,ReportingToName From Employee E INNER JOIN Employee E1 ON E.EmpID = E1.ReportingToID ( the reportingToID is the Empld)

Most Cases of Self join : The Table PK refers itself with different Column name in the same table.


For More Reference :



The Bulk update is used to send a raw data into DB. The below structure shows the structure to send data from .Net ot DB.

    /// <summary>
    /// Update the Bulk Collection details into DB.
    /// </summary>
    /// <param name="objTable">Input DatatTable</param>
    public void BulkEntryUpdate(DataTable objTable)
    {
        using (System.Data.SqlClient.SqlConnection objCon =
                                                CommonBehavior.GetMainConnection())
        {
            objCon.Open();
            using (System.Data.SqlClient.SqlBulkCopy bulkCopy = new 
                System.Data.SqlClient.SqlBulkCopy(objCon))
            {
                bulkCopy.DestinationTableName = "DB Table Name";
                                 //Ex : dbo.T_TransactionTable
                // Write from the source to the destination.
                bulkCopy.WriteToServer(objTable);
            }
        }

    }

Exameple :

In Codebehind, First we need to generate the source data table as per our needs.For example,


using (System.IO.StreamReader objReader = new System.IO.StreamReader(sFilename.Trim()))
{
InputLine = objReader.ReadLine();
string sUpdateMsg = string.Empty;

DataTable dtAdjData = new DataTable();
DataColumn dc;
DataRow dr;


dc = new DataColumn();
dc.DataType = System.Type.GetType("System.Int32");
dc.ColumnName = "LocationID";
//dc.Unique = false;
dtAdjData.Columns.Add(dc);

dc = new DataColumn();
dc.DataType = System.Type.GetType("System.String");
dc.ColumnName = "System";
dtAdjData.Columns.Add(dc);

dc = new DataColumn();
dc.DataType = System.Type.GetType("System.String");
dc.ColumnName = "Customer";
dtAdjData.Columns.Add(dc);

dc = new DataColumn();
dc.DataType = System.Type.GetType("System.String");
dc.ColumnName = "Project";
dtAdjData.Columns.Add(dc);

dc = new DataColumn();
dc.DataType = System.Type.GetType("System.Int32");
dc.ColumnName = "FileID";
dtAdjData.Columns.Add(dc);

while ((InputLine = objReader.ReadLine()) != null)
{
//Do your checking some coditions here…
try
{
string[] ProcessDate = arrAdj[6].Trim().Split('/');
dr = dtAdjData.NewRow();
dr["LocationID"] = Convert.ToInt32(Locationid);
dr["System"] = arrAdj[7].Trim();
dr["Customer"] = arrAdj[3].Trim();
dr["Project"] = arrAdj[4].Trim();
dr["FileID"] = Convert.ToInt32(_fileid);
dtAdjData.Rows.Add(dr);
}
catch (Exception exErrorLog)
{
objErrorList.Add("Error Record No: " + (Successcnt + ErrorCount) + ". Error:" + exErrorLog.Message);
sUploadMsg.Text = "Uploaded Successfully!. Found wrong entries, view log file.";
}

}


Then we will map our generated Table columns with Database table columns and directly update the bulk data into DB.

using(SqlConnection objCon = new SqlConnection(CommonBehavior.GetMainConnectionString()))
{

using (SqlBulkCopy bulkCopy = new SqlBulkCopy(objCon))
{
bulkCopy.ColumnMappings.Add(new SqlBulkCopyColumnMapping("LocationID", "LocationID"));
bulkCopy.ColumnMappings.Add(new SqlBulkCopyColumnMapping("System", "System"));
bulkCopy.ColumnMappings.Add(new SqlBulkCopyColumnMapping("Customer", "Customer"));
bulkCopy.ColumnMappings.Add(new SqlBulkCopyColumnMapping("Project", "Project"));
bulkCopy.ColumnMappings.Add(new SqlBulkCopyColumnMapping("FileID", "FileID"));

bulkCopy.DestinationTableName = "dbo.[T_AdjData]";
bulkCopy.WriteToServer(dtAdjData);
System.Threading.Thread.Sleep(1000);
}
}


Suppose the table contains any Identity Column, no need to map that column, just leave that and mapping the remaining columns, the above case will works fine.


 
    /// <summary>
    /// Send the IP Address of the Client
    /// </summary>
    /// <returns>string</returns>
    public string GetIPAddress()
    {
       
        string sIP = HttpContext.Current.Request.ServerVariables["HTTP_X_FORWARDED_FOR"];
        if (String.IsNullOrEmpty(sIP))
        {
            return HttpContext.Current.Request.ServerVariables["REMOTE_ADDR"];
        }
        else
            return sIP;

    }
CREATE TABLE #temp(Names varchar(10),Description varchar(20))
insert
into #temp values('suresh','dfsdf')
insert into #temp values('vijayans','12212')
insert into #temp values('suresh','232132')
insert into #temp values('suresh','=====')
insert into #temp values('vijayans','10000')SELECT
Names
,

REPLACE(RTRIM((SELECT [description] + ' ' FROM #temp WHERE (Names = Results.Names) FOR XML PATH (''))),' ',', ') AS NameValues FROM #temp Results GROUP BY NAMES


DROP
TABLE #temp


The simple Example for Dynamic Page Creation
------------------------------------------------------------

In aspx Page add the below tag
<div align="center"><asp:PlaceHolder ID="phUploadEntry" runat="server" EnableTheming="true"></asp:PlaceHolder>
</div>
Before Creating Dynamic Control we need to override the Init

override protected void OnInit(EventArgs e)          
    {
        //InitializeComponent();
        base.OnInit(e);
    }


In the Page Load we will initializes the Control based upon our need ,

Ex :

protected void Page_Load(object sender, EventArgs e)
{
        InitializeComponent();
}

/// <summary>
/// Initialize the Page on Pre-Init for Dynamic Page Load
/// </summary>
private void InitializeComponent()
{
    BindUserUpload ();
    this.Load += new System.EventHandler(this.Page_Load);
}



/// <summary>
/// Bind the dynamic User Upload Screen
/// </summary>
private void BindUserUpload()
{
        this.phUploadEntry.Controls.Add(new LiteralControl("<br><br><table cellspacing='0' cellpadding='0' width='60%' border='0'>"));
        this.phUploadEntry.Controls.Add(new LiteralControl("<tr SkinID='002lblNormal' valign='top'><td align='right' style='height: 22px;width:25%'>Select CSV File :</td><td align='left' style='width:450px;height:24px'>"));
        FileUpload objFileUpload = new FileUpload();
        objFileUpload.ID = "uplFile";
        objFileUpload.Width = 350;
        objFileUpload.SkinID = "Browse";
        this.phUploadEntry.Controls.Add(objFileUpload);
        this.phUploadEntry.Controls.Add(new LiteralControl("&nbsp;&nbsp;"));
        Button objUpload = new Button();
        objUpload.ID = "btnUpload";

        objUpload.Text = "Upload";
        objUpload.SkinID = "btnSubmit";
        objUpload.CssClass = "ThemeButtonsGo";
        objUpload.ValidationGroup = "FileUpload";
        objUpload.Click += new EventHandler(btnUpload_Click);

        this.phUploadEntry.Controls.Add(objUpload);

        this.phUploadEntry.Controls.Add(new LiteralControl("<br>"));

        RegularExpressionValidator objRegx = new RegularExpressionValidator();
        objRegx.ID = "revFileUpload";
        objRegx.ControlToValidate = "uplFile";
        objRegx.ValidationExpression = "^.*" + "\\" + ".(CSV|csv)$";
        objRegx.ErrorMessage = "Please select valid .csv file";
        objRegx.ValidationGroup = "FileUpload";
        objRegx.Display = ValidatorDisplay.Dynamic;
        objRegx.Font.Size = 10;

        this.phUploadEntry.Controls.Add(objRegx);

        RequiredFieldValidator objRfv = new RequiredFieldValidator();
        objRfv.ID = "rfvFileUpload";
        objRfv.ControlToValidate = "uplFile";
        objRfv.Font.Size = 10;
        objRfv.Display = ValidatorDisplay.Dynamic;
        objRfv.ErrorMessage = "File is required for Upload";
        objRfv.ValidationGroup = "FileUpload";

        this.phUploadEntry.Controls.Add(objRfv);
        this.phUploadEntry.Controls.Add(new LiteralControl("<br><br></td></tr>"));//<tr><td colspan='2'>



        //&nbsp;</td></tr>
        this.phUploadEntry.Controls.Add(new LiteralControl("<tr><td  align='center' colspan='2'>"));
        this.phUploadEntry.Controls.Add(new LiteralControl("<div style='width: 400px; height: 120px; border: solid 1px #A9D0F5; background-color: #EFEFFB;text-align: left'>"));
        this.phUploadEntry.Controls.Add(new LiteralControl("&nbsp;&nbsp;<b style='color: #A52A2A'>The CSV file should be in the following format:</b>"));
        this.phUploadEntry.Controls.Add(new LiteralControl("<ul id='listNotes' type='Square' style='list-style-image: url(Images/Bullets.gif);'>"));
        this.phUploadEntry.Controls.Add(new LiteralControl("<li><a onclick=\"javascript:window.open(\'../UploadFiles/UploadNonLicmLeave/SampleLeaveDetails.csv\');\" style='cursor:hand'>"));
        this.phUploadEntry.Controls.Add(new LiteralControl("<img src='../Images/LogFile.jpg' style='height: 25px; width: 25px' alt='Log' />Click here to view Sample CSV File</a> </li>"));
        this.phUploadEntry.Controls.Add(new LiteralControl("<li>DomainID(user1) and AvailedLeave</li>"));
        this.phUploadEntry.Controls.Add(new LiteralControl("<li>Column Separator is '<b>^</b>'</li>"));
        this.phUploadEntry.Controls.Add(new LiteralControl("<li>DomainID should contain only alphabets, numerals, Hypen and underscore. </li>"));
        this.phUploadEntry.Controls.Add(new LiteralControl("<li>AvailedLeave should be numeric.</li>"));
        this.phUploadEntry.Controls.Add(new LiteralControl("<li>Entry should not have duplicate or previous upload Entries.</li></ul></div></td></tr></table>"));

    }


In the above example, Every time of page loading the page load the new controls but it will keep the previous values that is loaded in the viewstate.
Encrypted View: (References from site)
The definition of schema will be encrypted and stored as object in the database. This can be done using the ENCRYPTION option in the view creation.

IF
OBJECT_ID('[DBO].Vw_SqlObjects_Encrypted') IS NOT NULLBEGIN   DROP VIEW [DBO].Vw_SqlObjects_Encrypted
   PRINT '<< [DBO].Vw_SqlObjects_Encrypted View dropped >>'ENDGO
CREATE VIEW [DBO].Vw_SqlObjects_Encrypted
WITH ENCRYPTION
AS
   SELECT       O.Object_ID      ,O.Name
      ,'Type' = CASE O.type WHEN 'S' THEN 'Scalar Functions'                          WHEN 'F' THEN 'Functions'                          WHEN 'V' THEN 'Views'                          WHEN 'PK' THEN 'Primary keys'                          WHEN 'TR' THEN 'Triggers'                          WHEN 'P' THEN 'Procedures'                          WHEN 'U' THEN 'User Defined Functions'                          WHEN 'TF' THEN 'Table Valued Functions'                          WHEN 'IF' THEN 'Inline Functions' END      ,O.create_date
      ,O.modify_date
      ,CASE WHEN SC.encrypted = 0 THEN 'No'
ELSE 'Yes' END AS [IsEncrypted]
      ,SC.text
  FROM      SYS.OBJECTS O
  INNER JOIN      SYSCOMMENTS SC ON SC.id = O.object_id
 GO
IF OBJECT_ID('[DBO].Vw_SqlObjects_Encrypted') IS NOT NULLBEGIN   PRINT '<< [DBO].Vw_SqlObjects_Encrypted View created >>'ENDGO

Now if you want to see the view schema definition for the above view is not possible. We have stored in the encrypted format. This is a significant option to hide the important calculations inside the view from the others.

In case of any alter in the view must be stored externally somewhere else.

SELECT
text FROM SYSCOMMENTS WHERE id = OBJECT_ID('[DBO].Vw_SqlObjects_Encrypted')
SELECT definition FROM SYS.sql_modules WHERE object_id = OBJECT_ID('[DBO].Vw_SqlObjects_Encrypted')
sp_helptext Vw_SqlObjects_Encrypted
If you execute the above queries then it will say like view is encrypted.

There are three types of views in the sql server 2005.

They are 
  1. Normal or Standard view
  2. Indexed or permanent view
  3. Partitioned view
Normal or Standard view:
This view is most frequently used by the developers. When create the view the schema will be stored an object in the database. When we retrieve the content from this virtual table, it will be executed the schema and stored the data from the parent table.

Here if you have the result from the same table then it can be updated and inserted. The deleted row will be reflected in the original table.

USE
[Northwind]
GO
IF OBJECT_ID('[DBO].vw_ViewProducts','V') IS NOT NULLBEGIN  DROP VIEW [DBO].vw_ViewProducts
  PRINT '<< [DBO].vw_ViewProducts view dropped.. >>'ENDGO
CREATE VIEW [DBO].vw_ViewProducts
AS SELECT
    ProductID,ProductName,SupplierID,CategoryID,QuantityPerUnit,UnitPrice,UnitsInStock,UnitsOnOrder,ReorderLevel,Discontinued
 FROM Products
GO
IF OBJECT_ID('[DBO].vw_ViewProducts','V') IS NOT NULLBEGIN  PRINT '<< [DBO].vw_ViewProducts view created.. >>'ENDGO
--O/PSELECT * FROM [DBO].vw_ViewProducts
--INSERTINSERT INTO [DBO].vw_ViewProducts(ProductName,SupplierID,CategoryID,QuantityPerUnit,UnitPrice,UnitsInStock,UnitsOnOrder,ReorderLevel,Discontinued)VALUES('Test View',1,2,'100 per bag',25.45,89,57,15,0)--DELETEDELETE FROM [DBO].vw_ViewProducts WHERE ProductID = 81

Here you can do the DML operations in the view when you have only one table.

Indexed views:

The indexed or permanent view is one of the new features introduced in the sql server 2005 version. We have seen that the view only store the schema definition and it will get execute and load the data into the virtual table at the time of view used. But this view creates the permanent view and we can create the indexes on the table. It allows us to create the instead of trigger.

The indexed view can be created with the WITH SCHEMA BINDING option while creating the view.

The indexed view has some restrictions like cannot use the TOP, DISTINCT, UNION, ORDER BY and aggregate functions.

It allows us to use the GROUP BY statement but we cannot use COUNT statement. Instead of that COUNT_BIG statement can be used.

IF
EXISTS(SELECT OBJECT_ID FROM SYS.OBJECTS WHERE OBJECT_ID = OBJECT_ID(N'[DBO].Vw_Product_Sales_Report',N'V'))BEGIN  DROP VIEW [DBO].Vw_Product_Sales_Report
  PRINT '<< [DBO].Vw_Product_Sales_Report view dropped >>'ENDGO
CREATE VIEW [DBO].Vw_Product_Sales_Report
WITH SCHEMABINDING
AS
  SELECT
      O.OrderID
     ,C.CustomerID
     ,C.CompanyName
     ,C.Address+', '+C.City AS [Customer Address]
     ,OD.ProductID
     ,P.ProductName
     ,OD.UnitPrice
     ,OD.Quantity
     ,(OD.UnitPrice * OD.Quantity) AS [Total]
     ,(OD.UnitPrice * OD.Quantity) * OD.Discount/100 AS [Discount]
   FROM     [DBO].Orders O (NOLOCK)   INNER JOIN [DBO]."Order Details" OD (NOLOCK) ON OD.OrderID = O.OrderID
   INNER JOIN [DBO].Customers C (NOLOCK) ON C.CustomerID = O.CustomerID
   INNER JOIN [DBO].Products P (NOLOCK) ON P.ProductID = OD.ProductID
GO
IF EXISTS(SELECT OBJECT_ID FROM SYS.OBJECTS WHERE OBJECT_ID = OBJECT_ID(N'[DBO].Vw_Product_Sales_Report',N'V'))BEGIN  PRINT '<< [DBO].Vw_Product_Sales_Report view created >>'ENDGO

Here the  indexed view has created. When you retrieve the data from this table, it will execute like normal table.

There are some retrictions while creating this indexed view like the name of the view must be two part name and we cannot use select * in the view schema defintion.

Normally view cannot have the triggers but from the sql server 2005 onwards We can create the Instead of trigger on the instead of trigger.

Partitioned Views:

The partitioned view and its execution is like normal view. It will work across the database and across the server.

There are two types of Partitioned views. They are 
  1. Local Partitioned View
  2. Global Partitioned View

1. Local Partitioned View:
The local partitioned view can be created within same server but different database.

The view schema definition will be stored in the executed database. But when we try to retrieve the data from the table, it has to execute the schema definition internally and load the data.

Let us see an example.

USE
[Northwind]
GO
CREATE TABLE EmployeeList
(  iEmployeeID
INT IDENTITY(1,1),  vFirstName VARCHAR(25) NOT NULL,  vLastName VARCHAR(25) NOT NULL,  iDeptID INT,  vAddress VARCHAR(25) NOT NULL,  vCity VARCHAR(25) NOT NULL,  vState VARCHAR(25) NOT NULL,  vCountry VARCHAR(25) NOT NULL,
)
GO
USE [Master]
GO
CREATE TABLE Department
(  iDeptID
INT IDENTITY(1,1) PRIMARY KEY,  vDeptName VARCHAR(50),  vDeptDesc VARCHAR(25),  vDeptAddedBy VARCHAR(50),  vPostedDate DATETIME DEFAULT GETDATE()
)
GO
--SELECT * FROM DepartmentUSE [Northwind]
GO
IF OBJECT_ID('[DBO].vw_LocalPartion_View','V') IS NOT NULLBEGIN  DROP VIEW [DBO].vw_LocalPartion_View
  PRINT '[DBO].vw_LocalPartion_View view dropped...'ENDGO
CREATE VIEW [DBO].vw_LocalPartion_View
AS
SELECT
E.iEmployeeID,E.vFirstName+SPACE(1)+E.vLastName AS [Name],       D.vDeptName,E.vAddress,E.vCity,E.vState
FROM EmployeeList E
--INNER JOIN Master..Department D ON D.iDeptID = E.iDeptID --Either one of the way will be used.INNER JOIN Master.dbo.Department D ON D.iDeptID = E.iDeptID
GO
IF OBJECT_ID('[DBO].vw_LocalPartion_View','V') IS NOT NULLBEGIN  PRINT '[DBO].vw_LocalPartion_View view created...'ENDGO
--O/pSELECT * FROM [DBO].vw_LocalPartion_View


2. Global Partitioned View

The global Partitioned view will work across the server. The view can be created to join the table across the server.

The accessing format will be like this.

[Server Name].  Database Name. Table Name

When we execute the view if it is not linked with the current server then it will ask us to link the external server.

The following system stored procedure will be used to link the server.

sp_addlinkedserver
'Server name'
The following system catalog table is used to see the list of linked servers.

SELECT
* FROM SYS.SERVERS
INSTEAD OF Triggers on the Indexed View

Normally the triggers cannot be created on the view. But sql server 2005 onwards we can create the INSTEAD OF trigger on the indexed views.

USE
[Northwind]
GO
IF OBJECT_ID('[DBO].[VW_Trigger_Example') IS NOT NULLBEGIN   DROP VIEW [DBO].[VW_Trigger_Example]
   PRINT '[DBO].[VW_Trigger_Example view dropped..'ENDGO
CREATE VIEW [DBO].[VW_Trigger_Example]
WITH SCHEMABINDING
AS
  SELECT P.ProductID,P.ProductName,P.SupplierID,         OD.OrderID,OD.UnitPrice,OD.Quantity
  FROM [DBO].Products P
  INNER JOIN [DBO].[Order Details] OD ON OD.ProductID = P.ProductID
GO
IF OBJECT_ID('[DBO].[VW_Trigger_Example') IS NOT NULLBEGIN   PRINT '[DBO].[VW_Trigger_Example view created..'ENDGO
--SELECT * FROM VW_Trigger_ExampleIF OBJECT_ID('[DBO].Tr_Delete_TriggerExample','TR') IS NOT NULLBEGIN  DROP TRIGGER [DBO].Tr_Delete_TriggerExample
  PRINT '[DBO].Tr_Delete_TriggerExample trigger dropped..'ENDGO
CREATE TRIGGER [DBO].Tr_Delete_TriggerExample
ON [DBO].VW_Trigger_Example
INSTEAD OF DELETE
AS
BEGIN
   PRINT '----------------------------------------'   PRINT 'This is an example of INSTEAD OF Trigger'   PRINT '----------------------------------------'   SELECT TOP 1 * FROM DELETED
ENDGO
IF OBJECT_ID('[DBO].Tr_Delete_TriggerExample','TR') IS NOT NULLBEGIN  PRINT '[DBO].Tr_Delete_TriggerExample trigger created..'ENDGO
--O/P
--SELECT * FROM [DBO].[VW_Trigger_Example] WHERE ProductID = 11
DELETE FROM [DBO].[VW_Trigger_Example] WHERE ProductID=11