SQL Server 2017, Thanks to Graph Database, can express certain kinds of queries more easily than a relational database by transforming complex relationships into graphs (#354)

* Added some media for incoming demos on SQL Graph database

* Added the file README.md and the first demo of the session SQL Server 2017 Graph Database that I have done at SQL Saturday Parma 2017 #sqlsat675

* Fixed a bug in SQL text visualization

* Deleted some Unicode characters

* Update file README.md

* Update README.md

* Update README.md

* Update README.md

* Update README.md

* Update README.md

* Update README.md

* Update README.md

* Update README.md

* Update README.md

* Update README.md

* Update README.md

* Update README.md

* Update README.md added the demo "Build a sample recommendation system using SQL Graph"

* Added the file sqlsat675 40 Recommendation system.sql that contains source code of the demo "Build a sample recommendation system using SQL Graph"

* Update README.md

* Added the file sqlsat675 30 Many-to-many relationships.sql

* Delete folder sql-saturday-675-parma-italy

* Add folder recommendation-system

* Rename files

* Update README.md

* Update README.md

* Update media file related to SQL Graph demos

* Used function JSON_VALUE instead of OPENJSON to extract the first language of a Person

* Update README.md

* Update README.md

* Update README.md

* Update README.md
This commit is contained in:
Sergio Govoni
2018-01-29 14:44:32 -08:00
committed by Perry Skountrianos
parent af5b694a66
commit 96f947b085
10 changed files with 715 additions and 0 deletions
Binary file not shown.

After

Width:  |  Height:  |  Size: 74 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 70 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 108 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 KiB

@@ -0,0 +1,166 @@
# SQL Server 2017 Graph Database
SQL Server has always provided tools to manage hierarchies and relationships, facilitating query execution on hierarchical data, but sometimes relationships can become complex. Think about many-to-many relationships, relational databases don't have a native solution for many-to-many associations. A common approach to realize many-to-many associations is to introduce a table that holds such relationships.
SQL Server 2017, thanks to Graph Database, can express certain kinds of queries more easily than a relational database by transforming complex relationships into graphs.
These demos, based on [WideWorldImporters](https://github.com/Microsoft/sql-server-samples/tree/master/samples/databases/wide-world-importers) sample database, are related to the session that [Sergio Govoni](https://mvp.microsoft.com/it-it/PublicProfile/4029181?fullName=Sergio%20Govoni) has done at the PASS SQL Saturday 675 in Parma (Italy).
For those who don't already know the [SQL Saturday](http://www.sqlsaturday.com) events: Since 2007, the PASS SQL Saturday program provides to users around the world the opportunity to organize free training sessions on SQL Server and related technologies. SQL Saturday is an event sponsored by PASS and therefore offers excellent opportunities for training, professional exchange and networking. You can find all details in this page: [About PASS SQL Saturday](http://www.sqlsaturday.com/about.aspx).
### Contents
[About this sample](#about-this-sample)<br/>
[Before you begin](#before-you-begin)<br/>
[Run this sample](#run-this-sample)<br/>
[Disclaimers](#disclaimers)<br/>
[Related links](#related-links)<br/>
<a name=about-this-sample></a>
## About this sample
1. **Applies to:**
- Azure SQL Database v12 (or higher)
- SQL Server 2017 (or higher)
2. **Demos:**
- Build and populating nodes and edges tables
- The new MATCH function
- Build a recommendation system for sales offers
3. **Workload:** Queries executed on [WideWorldImporters](https://github.com/Microsoft/sql-server-samples/releases/tag/wide-world-importers-v1.0)
4. **Programming Language:** T-SQL
5. **Author:** [Sergio Govoni](https://mvp.microsoft.com/it-it/PublicProfile/4029181?fullName=Sergio%20Govoni)
<a name=before-you-begin></a>
## Before you begin
To run these demos, you need the following prerequisites.
**Account and Software prerequisites:**
1. Either
- Azure SQL Database v12 (or higher)
- SQL Server 2017 (or higher)
2. SQL Server Management Studio 17.x (or higher)
**Azure prerequisites:**
1. An Azure subscription. If you don't already have an Azure subscription, you can get one for free here: [get Azure free trial](https://azure.microsoft.com/en-us/free/)
2. When your Azure subscription is ready to use, you have to create an Azure SQL Database, to do that, you must have completed the first three steps explained in [Design your first Azure SQL database](https://docs.microsoft.com/en-us/azure/sql-database/sql-database-design-first-database)
<a name=run-this-sample></a>
## Run this sample
### Setup
#### Azure SQL Database Setup
1. Download the **WideWorldImporters-Standard.bacpac** from the WideWorldImporters database [page](https://github.com/Microsoft/sql-server-samples/releases/tag/wide-world-importers-v1.0)
2. Import the **WideWorldImporters-Standard.bacpac** bacpac file to your Azure subscription. This [article](https://www.sqlshack.com/import-sample-bacpac-file-azure-sql-database/) on SQL Shack explains how to import WideWorldImporters database to an Azure SQL Database, anyway, the instructions are valid for any bacpac file
3. Launch SQL Server Management Studio and connect to the newly created WideWorldImporters-Standard database
#### SQL Server Setup
1. Download **WideWorldImporters-Full.bak** from the WideWorldImporters database [page](https://github.com/Microsoft/sql-server-samples/releases/tag/wide-world-importers-v1.0)
2. Launch SQL Server Management Studio, connect to your SQL Server instance (2017) and restore **WideWorldImporters-Full.bak**. For further information about how to restore a database backup using SQL Server Management Studio, you can refer to this article: [Restore a Database Backup Using SSMS](https://docs.microsoft.com/en-us/sql/relational-databases/backup-restore/restore-a-database-backup-using-ssms). Once you have restored the WideWorldImporters database, you can connect it using the **USE** command like this:
```SQL
USE [WideWorldImporters]
```
The purpose of the file [before-you-begin.sql](./before-you-begin.sql) is to connect the database WideWorldImporters and create two new schema: **Edges** and **Nodes**.
### Create and populate graph objects
The first demo consists in creating graph objects such as Nodes and Edges, this is the purpose of the file [demo1-create-and-populate-nodes-and-edges.sql](./demo1-create-and-populate-nodes-and-edges.sql). Let's start with the Node table named **Nodes.Person**. A node table represents an entity in a Graph DB, every time a node is created, in addition to the user defined columns, SQL Server Engine will create an implicit column named **$node_id** that uniquely identifies a given node in the database, it contains a combination of the **object_id** of the node and an internally bigint stored in an hidden column named **graph_id**.
The following picture shows the CREATE statement with the new DDL extension **AS NODE**, this extension tells to the engine that we want to create a Node table.
![Picture 1](../../../../media/demos/sql-graph/Create%20a%20Node%20Table.png)
Now, it's the time to create the Edge table named **Edges.Friends**. Every Edge represents a relationship in a graph, it may or may not have any user defined attributes, Edges are always directed and connected with two nodes. In the first release of SQL Graph, constraints are not available on the Edge table, so an Edge table can connect any two nodes on the graph. Every time an Edge table is created, in addition to the user defined columns, the Engine will create three implicit columns:
1. **$edge_id** is a combination of the **object_id** of the Edge and an internally bigint stored in an hidden column named **graph_id**
2. **$from_id** stores the **$node_id** of the node where the Edge starts from
3. **$to_id** stores the **$node_id** of the node at which the Edge ends
The following picture shows the CREATE statement with the new DDL extension **AS EDGE**, this extension tells to the engine that we want to create an Edge table.
![Picture 2](../../../../media/demos/sql-graph/Create%20an%20Edge%20Table.png)
The node **Nodes.Person** and the edge **Edges.Friends** are populated starting from the table **Application.People** of WideWorldImporters DB.
### The first look to the MATCH clause
The second demo allows you to do a first look to the MATCH clause used to perform some query on Nodes and Edges we have just created (in the first demo).
The new T-SQL MATCH function allows you to specify the search pattern for a graph schema, it can be used only with graph Node and Edge tables in SELECT statements as a part of the WHERE clause. Based on the node **Nodes.Person** and the edge **Edges.Friends**, the file [demo2-using-the-match-clause.sql](./demo2-using-the-match-clause.sql) contains the following sample queries:
1. List of all guys that speak finnish with friends (Pattern: Node > Relationship > Node)
2. List of the top 5 people who have friends that speak Greek in the first and second connections
3. People who have common friends that speak Croatian
The search pattern, provided in the MATCH function, goes through one node to another by an edge, in the direction provided by the arrow. Edge names or aliases are provided inside parenthesis. Node names or aliases appear at the two ends of the arrow.
### Build a sample recommendation system using SQL Graph
Supposing to have a customer (of the table Sales.Customers) connected to our e-commerce, this customer is looking for the product (of the table Warehouse.StockItems) "USB food flash drive - pizza slice" or he/she has just bought that product. Our goal is finding the similar products to the one he/she is looking at, based on the behavior of other customers. In short words, we have to find products that are recommended for another one.
The following picture shows a possible scenario for our sales recommendation system.
![Picture 3](../../../../media/demos/sql-graph/Sales%20Recommendation%20Scenario.png)
This is the algorithm:
1. Identify the customer and the product he/she is purchasing
2. Identify the other customers who have purchased the same item he/she is looking for
3. Find the other products that customers, at step two, have purchased
4. Recommend to the current customer the top items from the previous step, ordered by the number of times they were purchased
The following picture shows the graphical representation of the algorithm.
![Picture 4](../../../../media/demos/sql-graph/Sales%20Recommendation%20System.png)
The file [demo3-create-and-populate-nodes-and-edges.sql](./demo3-create-and-populate-nodes-and-edges.sql) contains the statements to create and populate the nodes **Nodes.Customers**, **Nodes.StockItems** and the edge **Edges.Bought** starting from the tables of WideWorldImporters DB.
How can Graph Database helps us to implement this algorithm?
MATCH clause can express certain kinds of queries more easily than relational JOINs; let's start, we will use the counts to prioritize the recommendations that is the simplest possible algorithm for a recommendation service, in reality more complex filters are applied on top, for example text analysis of the product reviews to arrive at similarly measures.
The file [demo3-recommendation-system-for-sales.sql](./demo3-recommendation-system-for-sales.sql) contains the query that is able to extract top 5 products that are recommended for "USB food flash drive - pizza slice" using the MATCH clause.
The last query of the file [demo3-recommendation-system-for-sales.sql](./demo3-recommendation-system-for-sales.sql) shows you the implementation of the algorithm in the relational database using JOINs.. so you will know how many lines of code you would have wrote prior to SQL Graph Database.
<a name=disclaimers></a>
## Disclaimers
The code included in this sample is not intended to be a set of best practices on how to build scalable enterprise grade applications. This is beyond the scope of this quick start sample.
<a name=related-links></a>
## Related Links
For more information about Graph DB in SQL Server 2017, see these articles:
1. [Graph processing with SQL Server and Azure SQL Database](https://docs.microsoft.com/en-us/sql/relational-databases/graphs/sql-graph-overview)
2. [SQL Graph Architecture](https://docs.microsoft.com/en-us/sql/relational-databases/graphs/sql-graph-architecture)
3. [Arvind Shyamsundar's Blog](https://blogs.msdn.microsoft.com/arvindsh/)
@@ -0,0 +1,25 @@
------------------------------------------------------------------------
-- Event: SQL Saturday #675 Parma, November 18 2017 -
-- http://www.sqlsaturday.com/675/EventHome.aspx -
-- Session: SQL Server 2017 Graph Database -
-- Demo: Setup -
-- Author: Sergio Govoni -
-- Notes: -- -
------------------------------------------------------------------------
-- Full backup of WideWorldImporters sample database is available on GitHub
-- https://github.com/Microsoft/sql-server-samples/releases/tag/wide-world-importers-v1.0
-- Documentation about WideWorldImporters sample database for SQL Server
-- and Azure SQL Database
-- https://github.com/Microsoft/sql-server-samples/tree/master/samples/databases/wide-world-importers
USE [WideWorldImporters];
GO
CREATE SCHEMA [Nodes];
GO
CREATE SCHEMA [Edges];
GO
@@ -0,0 +1,173 @@
------------------------------------------------------------------------
-- Event: SQL Saturday #675 Parma, November 18 2017 -
-- http://www.sqlsaturday.com/675/EventHome.aspx -
-- Session: SQL Server 2017 Graph Database -
-- Demo: Demo1: Create and populate Nodes and Edges -
-- Author: Sergio Govoni -
-- Notes: -- -
------------------------------------------------------------------------
USE [WideWorldImporters];
GO
------------------------------------------------------------------------
-- Nodes -
------------------------------------------------------------------------
DROP TABLE IF EXISTS Nodes.Person;
GO
-- Create a node table
CREATE TABLE Nodes.Person
(
PersonID INTEGER NOT NULL PRIMARY KEY
,FullName NVARCHAR(50) NOT NULL
,[Language] NVARCHAR(50) NOT NULL
) AS NODE;
GO
SELECT * FROM sys.tables WHERE is_node = 1;
GO
SELECT
*
FROM
INFORMATION_SCHEMA.COLUMNS
WHERE
Table_Schema = 'Nodes'
AND Table_Name = 'Person';
GO
/*
SELECT FullName, CustomFields, *
FROM [Application].[People];
GO
*/
INSERT INTO Nodes.Person
(
PersonID
,FullName
,[Language]
)
SELECT
PersonID
,FullName
,[Language] = (SELECT JSON_VALUE(CustomFields, '$.OtherLanguages[0]'))
FROM
[Application].[People]
WHERE
(CustomFields IS NOT NULL)
AND ((SELECT JSON_VALUE(CustomFields, '$.OtherLanguages[0]')) <> '');
GO
SELECT * FROM Nodes.Person
------------------------------------------------------------------------
-- Edges -
------------------------------------------------------------------------
DROP TABLE IF EXISTS Edges.Friends;
-- Create an edge table
CREATE TABLE Edges.Friends
(
StartDate DATETIME NOT NULL
)
AS EDGE;
GO
SELECT * FROM sys.tables WHERE is_edge = 1;
GO
SELECT
C.*
FROM
sys.tables AS T
JOIN
INFORMATION_SCHEMA.COLUMNS AS C ON C.Table_Name = T.[name] AND C.Table_Schema = SCHEMA_NAME(T.[schema_id])
WHERE
(T.is_edge = 1)
ORDER BY
C.Table_Schema, C.Table_Name
GO
-- Insert friends who speak the same language
-- (one direction)
-- If a person speaks Finnish, I take for granted that
-- their friends speak Finnish
WITH Friends_Same_Language AS
(
SELECT
P1.$node_id AS From_Node_Id
,P2.$node_id AS To_Node_Id
,GETDATE() AS StartDate
,Direction = ROW_NUMBER() OVER (PARTITION BY P1.[Language], P2.[language] ORDER BY (SELECT NULL))
,From_FullName = P1.FullName
,From_Language = P1.[Language]
,To_FullName = P2.FullName
,To_Language = P2.[Language]
FROM
Nodes.Person AS P1
INNER JOIN
Nodes.Person AS P2 ON P1.[Language] = P2.[Language]
WHERE
-- The person itself isn't included
(P1.$node_id <> P2.$node_id)
)
INSERT INTO Edges.Friends
(
$from_id
,$to_id
,StartDate
)
SELECT
From_Node_Id
,To_Node_Id
,StartDate
FROM
Friends_Same_Language
WHERE
(Direction = 1);
GO
SELECT * FROM Nodes.Person;
GO
-- Insert some random connections
INSERT INTO Edges.Friends
(
$from_id
,$to_id
,StartDate
)
SELECT
From_Node_Id
,To_Node_Id
,StartDate
FROM
(
SELECT DISTINCT TOP 40
New_ID = NEWID()
,P1.$node_id AS From_Node_Id
,P2.$node_id AS To_Node_Id
,GETDATE() AS StartDate
FROM
Nodes.Person AS P1
INNER JOIN
Nodes.Person AS P2 ON P1.[Language] < P2.[Language]
WHERE
(P1.$node_id <> P2.$node_id)
ORDER BY
New_ID
) AS T;
GO
@@ -0,0 +1,77 @@
------------------------------------------------------------------------
-- Event: SQL Saturday #675 Parma, November 18 2017 -
-- http://www.sqlsaturday.com/675/EventHome.aspx -
-- Session: SQL Server 2017 Graph Database -
-- Demo: Demo1: Using the MATCH clause -
-- Author: Sergio Govoni -
-- Notes: -- -
------------------------------------------------------------------------
USE [WideWorldImporters];
GO
SELECT * FROM Edges.Friends;
-- List of all guys that speak finnish with friends
-- Pattern: Node > Relationship > Node
SELECT
P1.FullName
,P1.[Language]
,Friends_Number = COUNT(*)
FROM
Nodes.Person AS P1
,Edges.Friends AS Friends
,Nodes.Person AS P2
WHERE
MATCH(P1-(Friends)->P2)
AND (P1.[Language] = 'Finnish')
GROUP BY
P1.FullName, P1.[Language]
ORDER BY
Friends_Number DESC, P1.[Language];
GO
-- List of the top 5 people who have friends that speak Greek
-- in the first and second connections
SELECT
TOP 5
P1.FullName
,P1.[Language]
,GreekFriends = COUNT(*)
FROM
Nodes.Person AS P1
,Edges.Friends AS F1
,Nodes.Person AS P2
,Edges.Friends AS F2
,Nodes.Person AS P3
WHERE
MATCH(P1-(F1)-> P2-(F2)-> P3)
AND ((P2.[Language] = 'Greek') OR (P3.[Language] = 'Greek'))
GROUP BY
P1.FullName, P1.[Language]
ORDER BY
GreekFriends DESC, P1.[Language];
GO
-- People who have common friends that speak Croatian
SELECT
P1.FullName
,P2.FullName
,P2.[Language]
--,P3.FullName
FROM
Nodes.Person AS P1
,Edges.Friends AS F1
,Nodes.Person AS P2
,Edges.Friends AS F2
,Nodes.Person AS P3
WHERE
MATCH(P1-(F1)-> P2 <-(F2)-P3)
AND (P2.[Language] = 'Croatian')
AND (P1.$node_id <> P3.$node_id);
GO
@@ -0,0 +1,119 @@
------------------------------------------------------------------------
-- Event: SQL Saturday #675 Parma, November 18 2017 -
-- http://www.sqlsaturday.com/675/EventHome.aspx -
-- Session: SQL Server 2017 Graph Database -
-- Demo: Demo2: Create and populate nodes and edges -
-- Author: Sergio Govoni -
-- Notes: -- -
------------------------------------------------------------------------
USE [WideWorldImporters];
GO
-- Create Customers node
-- This node holds the main details of the Customer
/*
DROP TABLE IF EXISTS Nodes.Customers;
GO
*/
CREATE TABLE Nodes.Customers
(
[CustomerID] INTEGER NOT NULL
,[CustomerName] NVARCHAR(100) NOT NULL
,[WebsiteURL] NVARCHAR(256) NOT NULL
)
AS NODE;
GO
INSERT INTO Nodes.Customers
(
[CustomerID]
,[CustomerName]
,[WebsiteURL]
)
SELECT
[CustomerID]
,[CustomerName]
,[WebsiteURL]
FROM
Sales.Customers;
GO
-- Create StockItems node
/*
DROP TABLE IF EXISTS Nodes.StockItems;
GO
*/
IF OBJECT_ID('Nodes.StockItems', 'U') IS NULL
CREATE TABLE Nodes.StockItems
(
StockItemID INTEGER IDENTITY(1, 1) NOT NULL
,StockItemName NVARCHAR(100) NOT NULL
,Barcode NVARCHAR(50) NULL
,Photo VARBINARY(MAX)
,LastEditedBy INTEGER NOT NULL
)
AS NODE;
GO
IF OBJECT_ID('Nodes.StockItems', 'U') IS NOT NULL
BEGIN
SET IDENTITY_INSERT Nodes.StockItems ON;
INSERT INTO Nodes.StockItems
(
StockItemID
,StockItemName
,LastEditedBy
)
SELECT
StockItemID
,StockItemName
,LastEditedBy
FROM
Warehouse.StockItems;
SET IDENTITY_INSERT Nodes.StockItems OFF;
END;
-- Create the edge from nodes Customers and StockItems
/*
DROP TABLE IF EXISTS Edges.Bought;
GO
*/
CREATE TABLE Edges.Bought
(
[PurchasedCount] BIGINT
)
AS EDGE;
GO
INSERT INTO Edges.Bought
(
$from_id
,$to_id
,[PurchasedCount]
)
SELECT
-- $from_id
C.$node_id
-- $to_id
,P.$node_id
-- PurchasedCount
,PurchasedCount = COUNT(OD.OrderLineID)
FROM
Sales.OrderLines AS OD
JOIN
Sales.Orders AS OH ON OH.OrderID = OD.OrderID
JOIN
Nodes.Customers AS C ON C.CustomerID = OH.CustomerID
JOIN
Nodes.StockItems AS P ON P.StockItemID = OD.StockItemID
GROUP BY
C.$node_id
,P.$node_id;
GO
@@ -0,0 +1,155 @@
------------------------------------------------------------------------
-- Event: SQL Saturday #675 Parma, November 18 2017 -
-- http://www.sqlsaturday.com/675/EventHome.aspx -
-- Session: SQL Server 2017 Graph Database -
-- Demo: Recommendation system -
-- Author: Sergio Govoni -
-- Notes: -- -
------------------------------------------------------------------------
USE [WideWorldImporters];
GO
------------------------------------------------------------------------
-- Recommended items for sales with Graph Database -
------------------------------------------------------------------------
-- Supposing to have a user connected to your e-commerce, this user is
-- looking for the product "USB food flash drive - pizza slice"
-- or he/she has just bought that product
-- Our goal is finding the similar products to the one he/she is
-- looking at based on the behavior of other customers
-- We will use the counts to prioritize the recommendations
-- that is the simplest possible algorithm for a recommendation service
-- In reality more complex filters are applied on top, for example text
-- analysis of the product reviews to arrive at similarly measures
-- Find the top 5 products that are recommended for "USB food flash drive - pizza slice"
-- using MATCH clause
SELECT
TOP 5
RecommendedItem.StockItemName
,COUNT(*)
FROM
Nodes.StockItems AS Item
,Nodes.Customers AS Customers
,Edges.Bought AS BoughtOther
,Edges.Bought AS BoughtThis
,Nodes.StockItems AS RecommendedItem
WHERE
MATCH(RecommendedItem<-(BoughtOther)-Customers-(BoughtThis)->Item)
AND (Item.StockItemName LIKE 'USB food flash drive - pizza slice') -- Current product
AND (Customers.CustomerID <> 18) -- Current user
AND (Item.StockItemName <> RecommendedItem.StockItemName)
GROUP BY
RecommendedItem.StockItemName
ORDER BY COUNT(*) DESC;
GO
------------------------------------------------------------------------
-- Recommended items for sales in the relational database -
------------------------------------------------------------------------
-- Find products that are recommended for 'USB food flash drive - pizza slice'
-- using common JOIN operations
-- Identify the user and the product he/she is purchasing
WITH Current_Usr AS
(
SELECT
CustomerID = 18
,StockItemID = 7 -- USB food flash drive - pizza slice
,PurchasedCount = 1
/*
SELECT
C.CustomerID
,P.StockItemID
,PurchasedCount = COUNT(*)
FROM
Sales.OrderLines AS OD
JOIN
Sales.Orders AS OH ON OH.OrderID=OD.OrderID
JOIN
Sales.Customers AS C ON OH.CustomerID=C.CustomerID
JOIN
Warehouse.StockItems AS P ON P.StockItemID=OD.StockItemID
WHERE
C.CustomerID=18
AND P.StockItemName='USB food flash drive - pizza slice'
GROUP BY
C.CustomerID
,P.StockItemID
*/
),
-- Identify the other users who have also purchased the item he/she is looking for
Other_Usr AS
(
SELECT
C.CustomerID
,P.StockItemID
,Purchased_by_others = COUNT(*)
FROM
Sales.OrderLines AS OD
JOIN
Sales.Orders AS OH ON OH.OrderID=OD.OrderID
JOIN
Nodes.Customers AS C ON OH.CustomerID=C.CustomerID
JOIN
--Warehouse.StockItems AS P ON P.StockItemID=OD.StockItemID
Current_Usr AS P ON P.StockItemID=OD.StockItemID
WHERE
C.CustomerID<>P.CustomerID
--C.CustomerID<>18
--AND P.StockItemName='USB food flash drive - pizza slice'
GROUP BY
C.CustomerID
,P.StockItemID
),
-- Find the other items which those other customers have also purchased
Other_Items AS
(
SELECT
C.CustomerID
,P.StockItemID
,Other_purchased = COUNT(*)
FROM
Sales.OrderLines AS OD
JOIN
Sales.Orders AS OH ON OH.OrderID=OD.OrderID
JOIN
Other_Usr AS C ON OH.CustomerID=C.CustomerID
JOIN
Nodes.StockItems AS P ON P.StockItemID=OD.StockItemID
WHERE
P.StockItemName<>'USB food flash drive - pizza slice'
GROUP BY
C.CustomerID
,P.StockItemID
)
-- Outer query
-- Recommend to the current user to the top items from those other items,
-- ordered by the number of times they were purchased
SELECT
TOP 5
P.StockItemName
,COUNT(Other_purchased)
FROM
Other_Items
JOIN
Nodes.StockItems AS P ON P.StockItemID=Other_Items.StockItemID
GROUP BY
P.StockItemName
ORDER BY
COUNT(Other_purchased) DESC;
GO