SQL Server : how do do HAVING criteria on bit value - sql

The below code demonstrates the problem I'm trying to solve. Basically I only want to select transactions where a customer has never purchased a product where isActive is now 0.
I tried using GROUP BY and HAVING MIN(p.isActive) = 1 to get only the customers associated with active products. This query is close but MIN does not like bit type. How can I do this?
The goal is to only see transactions associated with just Lisa and Fred. Bill should be removed from the results since one of his transactions is currently associated with a InActive product.
CustomerName ProductName (No column name)
Bill Cheerios 1
Lisa Cheerios 1
Bill Corn Flakes 1
Fred Corn Flakes 1
Bill Granola 0
ERROR: Operand data type bit is invalid for min operator.
CREATE TABLE #Customer (
CustomerId int,
CustomerName nvarchar(100),
Address nvarchar(100),
)
INSERT INTO #Customer
VALUES (1, 'Bill', '123 1st St'),
(2, 'Fred', '111 Market St'),
(3, 'Lisa', '01 Boulevard')
CREATE TABLE #Product (
ProductId int,
ProductName nvarchar(100),
isActive bit
)
INSERT INTO #Product
VALUES (1, 'Corn Flakes', 1),
(2, 'Cheerios', 1),
(3, 'Granola', 0)
CREATE TABLE #TransactionLog (
LogId int,
ProductId int,
CustomerId int,
Amount float
)
INSERT INTO #TransactionLog
VALUES (1, 1, 1, 2.00),
(2, 2, 1, 2.40),
(3, 3, 1, 1.80),
(4, 1, 1, 2.00),
(5, 1, 2, 2.00),
(6, 2, 3, 2.40)
SELECT * from #Customer
SELECT * from #Product
SELECT * from #TransactionLog
SELECT
c.CustomerName,
p.ProductName,
MIN(p.isActive)
FROM #TransactionLog t
join #Product p on t.ProductId = p.ProductId
join #Customer c on t.CustomerId = c.CustomerId
GROUP BY
c.CustomerName,
p.ProductName
HAVING
MIN(p.isActive) = 1
DROP TABLE #Customer
DROP TABLE #Product
DROP TABLE #TransactionLog
I'm not sure why but when I try HAVING MIN(CAST(p.isActive AS INT)) = 1, I still get 2 rows of data associated with Bill. I'd like to eliminate Bill from the result.
Bill Cheerios 1
Lisa Cheerios 1
Bill Corn Flakes 1
Fred Corn Flakes 1

with data as (
SELECT
c.CustomerName, p.ProductName,
count(case when p.isActive = 0 then 1 end)
over (partition by c.CustomerID) as countInactive
FROM #TransactionLog t
inner join #Product p on t.ProductId = p.ProductId
inner join #Customer c on t.CustomerId = c.CustomerId
)
select * from data where countInactive = 0;
You can accomplish it with min() over () as well if you prefer that logic.

You can always convert to a number:
HAVING MIN(CONVERT(tinyint, p.isActive)) = 1

This works for me - casting to INT as suggested in my comment:
CREATE TABLE #Customer (
CustomerId int,
CustomerName nvarchar(100),
Address nvarchar(100),
)
INSERT INTO #Customer
VALUES (1, 'Bill', '123 1st St'),
(2, 'Fred', '111 Market St'),
(3, 'Lisa', '01 Boulevard')
CREATE TABLE #Product (
ProductId int,
ProductName nvarchar(100),
isActive bit
)
INSERT INTO #Product
VALUES (1, 'Corn Flakes', 1),
(2, 'Cheerios', 1),
(3, 'Granoloa', 0)
CREATE TABLE #TransactionLog (
LogId int,
ProductId int,
CustomerId int,
Amount float
)
INSERT INTO #TransactionLog
VALUES (1, 1, 1, 2.00),
(2, 2, 1, 2.40),
(3, 3, 1, 1.80),
(4, 1, 1, 2.00),
(5, 1, 2, 2.00),
(6, 2, 3, 2.40)
SELECT * from #Customer
SELECT * from #Product
SELECT * from #TransactionLog
SELECT
c.CustomerName,
p.ProductName,
MIN(CAST(p.isActive AS int))
FROM #TransactionLog t
join #Product p on t.ProductId = p.ProductId
join #Customer c on t.CustomerId = c.CustomerId
GROUP BY
c.CustomerName,
p.ProductName
HAVING
MIN(CAST(p.isActive AS INT)) = 1
DROP TABLE #Customer
DROP TABLE #Product
DROP TABLE #TransactionLog

I would propose a join instead:
CREATE TABLE #Customer (
CustomerId int,
CustomerName nvarchar(100),
Address nvarchar(100),
)
INSERT INTO #Customer ( CustomerId, CustomerName, Address )
VALUES (1, 'Bill', '123 1st St'),
(2, 'Fred', '111 Market St'),
(3, 'Lisa', '01 Boulevard')
CREATE TABLE #Product (
ProductId int,
ProductName nvarchar(100),
isActive bit
)
INSERT INTO #Product (ProductId, ProductName, isActive)
VALUES (1, 'Corn Flakes', 1),
(2, 'Cheerios', 1),
(3, 'Granoloa', 0)
CREATE TABLE #TransactionLog (
LogId int,
ProductId int,
CustomerId int,
Amount float
)
INSERT INTO #TransactionLog (LogId, ProductId ,CustomerId ,Amount )
VALUES (1, 1, 1, 2.00),
(2, 2, 1, 2.40),
(3, 3, 1, 1.80),
(4, 1, 1, 2.00),
(5, 1, 2, 2.00),
(6, 2, 3, 2.40)
--SELECT * from #Customer
--SELECT * from #Product
--SELECT * from #TransactionLog
SELECT
c.CustomerName,
p.ProductName,
p.isActive
FROM #TransactionLog t
INNER JOIN #Product p on t.ProductId = p.ProductId AND p.isActive = 1
INNER JOIN #Customer c on t.CustomerId = c.CustomerId
GROUP BY
c.CustomerName,
p.ProductName
DROP TABLE #Customer
DROP TABLE #Product
DROP TABLE #TransactionLog
EDIT: PER NOTE, Get rid of Bill if he every ordered an inactive product
CREATE TABLE #Customer (
CustomerId int,
CustomerName nvarchar(100),
Address nvarchar(100),
)
INSERT INTO #Customer ( CustomerId, CustomerName, Address )
VALUES (1, 'Bill', '123 1st St'),
(2, 'Fred', '111 Market St'),
(3, 'Lisa', '01 Boulevard')
CREATE TABLE #Product (
ProductId int,
ProductName nvarchar(100),
isActive bit
)
INSERT INTO #Product (ProductId, ProductName, isActive)
VALUES (1, 'Corn Flakes', 1),
(2, 'Cheerios', 1),
(3, 'Granoloa', 0)
CREATE TABLE #TransactionLog (
LogId int,
ProductId int,
CustomerId int,
Amount float
)
INSERT INTO #TransactionLog (LogId, ProductId ,CustomerId ,Amount )
VALUES (1, 1, 1, 2.00),
(2, 2, 1, 2.40),
(3, 3, 1, 1.80),
(4, 1, 1, 2.00),
(5, 1, 2, 2.00),
(6, 2, 3, 2.40)
--SELECT * from #Customer
--SELECT * from #Product
--SELECT * from #TransactionLog
SELECT
c.CustomerName,
p.ProductName,
p.isActive
FROM #TransactionLog AS t
INNER JOIN #Product AS p on t.ProductId = p.ProductId
AND NOT EXISTS (
SELECT 1
FROM #TransactionLog AS ts
INNER JOIN #Product AS ps
ON ts.ProductId = ps.ProductId
AND ps.isActive = 0
WHERE ts.CustomerId = t.CustomerId
)
INNER JOIN #Customer c on t.CustomerId = c.CustomerId
GROUP BY
c.CustomerName,
p.ProductName,
p.isActive
DROP TABLE #Customer
DROP TABLE #Product
DROP TABLE #TransactionLog

Related

SQL LEFT JOIN to many categories

Suppose the following easy scenario, where a product row gets connected to one primary category, subcategory, and sub-subcategory.
DECLARE #PRODUCTS TABLE (ID int, DESCRIPTION varchar(50), CAT varchar(30), SUBCAT varchar(30), SUBSUBCAT varchar(30));
INSERT #PRODUCTS (ID, DESCRIPTION, CAT, SUBCAT, SUBSUBCAT) VALUES
(1, 'NIKE MILLENIUM', '1', '10', '100'),
(2, 'NIKE CORTEZ', '1', '12', '104'),
(3, 'ADIDAS PANTS', '2', '27', '238'),
(4, 'PUMA REVOLUTION 5', '3', '35', '374'),
(5, 'SALOMON SHELTER CS', '4', '15', '135'),
(6, 'NIKE EBERNON LOW', '2', '14', '157');
DECLARE #CATS TABLE (ID int, DESCR varchar(100));
INSERT #CATS (ID, DESCR) VALUES
(1, 'MEN'),
(2, 'WOMEN'),
(3, 'UNISEX'),
(4, 'KIDS'),
(5, 'TEENS'),
(6, 'BACK TO SCHOOL');
DECLARE #SUBCATS TABLE (ID int, DESCR varchar(100));
INSERT #SUBCATS (ID, DESCR) VALUES
(10, 'FOOTWEAR'),
(12, 'OUTERWEAR'),
(14, 'SWIMWEAR'),
(15, 'HOODIES'),
(27, 'CLOTHING'),
(35, 'SPORTS');
DECLARE #SUBSUBCATS TABLE (ID int, DESCR varchar(100));
INSERT #SUBSUBCATS (ID, DESCR) VALUES
(100, 'RUNNING'),
(104, 'ZIP TOPS'),
(135, 'FLEECE'),
(157, 'BIKINIS'),
(238, 'PANTS'),
(374, 'JOGGERS');
SELECT prod.ID,
prod.DESCRIPTION,
CONCAT(cat1.DESCR, ' > ', cat2.DESCR, ' > ', cat3.DESCR) AS CATEGORIES
FROM #PRODUCTS AS prod
LEFT JOIN #CATS AS cat1 ON cat1.ID = prod.CAT
LEFT JOIN #SUBCATS AS cat2 ON cat2.ID = prod.SUBCAT
LEFT JOIN #SUBSUBCATS AS cat3 ON cat3.ID = prod.SUBSUBCAT;
Now suppose that the foreign keys on #PRODUCTS table aren't just indices to their respective tables. They are comma-separated indices to more than one categories, subcategories, and sub-subcategories like here.
DECLARE #PRODUCTS TABLE (ID int, DESCRIPTION varchar(50), CAT varchar(30), SUBCAT varchar(30), SUBSUBCAT varchar(30));
INSERT #PRODUCTS (ID, DESCRIPTION, CAT, SUBCAT, SUBSUBCAT) VALUES
(1, 'NIKE MILLENIUM', '1, 2', '10, 12', '100, 135'),
(2, 'NIKE CORTEZ', '1, 5', '12, 15', '104, 374'),
(3, 'ADIDAS PANTS', '2, 6', '27, 35', '238, 374');
DECLARE #CATS TABLE (ID int, DESCR varchar(100));
INSERT #CATS (ID, DESCR) VALUES
(1, 'MEN'),
(2, 'WOMEN'),
(3, 'UNISEX'),
(4, 'KIDS'),
(5, 'TEENS'),
(6, 'BACK TO SCHOOL');
DECLARE #SUBCATS TABLE (ID int, DESCR varchar(100));
INSERT #SUBCATS (ID, DESCR) VALUES
(10, 'FOOTWEAR'),
(12, 'OUTERWEAR'),
(14, 'SWIMWEAR'),
(15, 'HOODIES'),
(27, 'CLOTHING'),
(35, 'SPORTS');
DECLARE #SUBSUBCATS TABLE (ID int, DESCR varchar(100));
INSERT #SUBSUBCATS (ID, DESCR) VALUES
(100, 'RUNNING'),
(104, 'ZIP TOPS'),
(135, 'FLEECE'),
(157, 'BIKINIS'),
(238, 'PANTS'),
(374, 'JOGGERS');
SELECT prod.ID,
prod.DESCRIPTION
--CONCAT(cat1.DESCR, ' > ', cat2.DESCR, ' > ', cat3.DESCR) AS CATEGORIES
FROM #PRODUCTS AS prod
--LEFT JOIN #CATS AS cat1 ON cat1.ID = prod.CAT
--LEFT JOIN #SUBCATS AS cat2 ON cat2.ID = prod.SUBCAT
--LEFT JOIN #SUBSUBCATS AS cat3 ON cat3.ID = prod.SUBSUBCAT;
In this case I want to achieve the following:
Be able to retrieve the respective names of the cats, subcats, sub-subcats, ie. for cats '1, 2' be able to retrieve their names (I tried LEFT JOIN #CATS AS cat1 ON cat1.ID IN prod.CAT but it doesn't work)
Create triplets of the corresponding cats, subcats, sub-subcats, ie. for
cats '1, 2'
subcats '12, 17'
sub-subcats '239, 372'
(after retrieving the appropriate names) create pipe-separated category routes like name of cat 1 > name of subcat 12 > name of sub-subcat 239 | name of cat 2 > name of subcat 17 > name of sub-subcat 372
So, for a row like (1, 'NIKE MILLENIUM', '1, 2', '10, 12', '100, 135'),
I would like to get the following result
ID
DESCRIPTION
CATEGORIES
1
NIKE MILLENIUM
MEN > FOOTWEAR > RUNNING # WOMEN > OUTERWEAR > FLEECE (I had to use # as the delimiter of the two triplets because pipe messed with the table's columns)
In case the user stupidly stores more cat IDs than subcat IDs, or sub-subcat IDs, the query should just match the ones that have a corresponding position match, ie for
cats '1, 2'
subcats '12'
sub-subcats '239, 372'
it should just create one triplet, like name of 1 > name of 12 > name of 239
STRING_SPLIT() does not promise to return the values in a specific order, so it won't work in this case as ordinal position matters.
Use OPENJSON() split the string into separate rows to ensure the values are returned in the same order.
OPENJSON() also returns a key field, so you can join on the row number within each grouping. You'll want an INNER JOIN since your requirement is that all values in that "column" must exist.
Use STUFF() to assemble the various cat>subcat>subsubcat values.
DECLARE #PRODUCTS TABLE (ID int, DESCRIPTION varchar(50), CAT varchar(30), SUBCAT varchar(30), SUBSUBCAT varchar(30));
INSERT #PRODUCTS (ID, DESCRIPTION, CAT, SUBCAT, SUBSUBCAT) VALUES
(1, 'NIKE MILLENIUM', '1, 2', '10, 12', '100, 135'),
(2, 'NIKE CORTEZ', '1, 5', '12, 15', '104, 374'),
(3, 'ADIDAS PANTS', '2, 6, 1', '27, 35, 10', '238, 374, 100'),
(4, 'JOE THE PLUMBER JEANS', '1, 5', '27', '238, 374');
DECLARE #CATS TABLE (ID int, DESCR varchar(100));
INSERT #CATS (ID, DESCR) VALUES
(1, 'MEN'),
(2, 'WOMEN'),
(3, 'UNISEX'),
(4, 'KIDS'),
(5, 'TEENS'),
(6, 'BACK TO SCHOOL');
DECLARE #SUBCATS TABLE (ID int, DESCR varchar(100));
INSERT #SUBCATS (ID, DESCR) VALUES
(10, 'FOOTWEAR'),
(12, 'OUTERWEAR'),
(14, 'SWIMWEAR'),
(15, 'HOODIES'),
(27, 'CLOTHING'),
(35, 'SPORTS');
DECLARE #SUBSUBCATS TABLE (ID int, DESCR varchar(100));
INSERT #SUBSUBCATS (ID, DESCR) VALUES
(100, 'RUNNING'),
(104, 'ZIP TOPS'),
(135, 'FLEECE'),
(157, 'BIKINIS'),
(238, 'PANTS'),
(374, 'JOGGERS');
;
with prod as (
SELECT p.ID,
p.DESCRIPTION
--CONCAT(cat1.DESCR, ' > ', cat2.DESCR, ' > ', cat3.DESCR) AS CATEGORIES
, c.value as CatId
, c.[key] as CatKey
, sc.value as SubCatId
, sc.[key] as SubCatKey
, ssc.value as SubSubCatId
, ssc.[key] as SubSubCatKey
FROM #PRODUCTS p
cross apply OPENJSON(CONCAT('["', REPLACE(cat, ', ', '","'), '"]')) c
cross apply OPENJSON(CONCAT('["', REPLACE(subcat, ', ', '","'), '"]')) sc
cross apply OPENJSON(CONCAT('["', REPLACE(subsubcat, ', ', '","'), '"]')) ssc
where c.[key] = sc.[key]
and c.[key] = ssc.[key]
)
, a as (
select p.ID
, p.DESCRIPTION
, c.DESCR + ' > ' + sc.DESCR + ' > ' + ssc.DESCR as CATEGORIES
, p.CatKey
from prod p
inner join #CATS c on c.ID = p.CatId
inner join #SUBCATS sc on sc.ID = p.SubCatId
inner join #SUBSUBCATS ssc on ssc.ID = p.SubSubCatId
)
select DISTINCT ID
, DESCRIPTION
, replace(STUFF((SELECT distinct ' | ' + a2.CATEGORIES
from a a2
where a.ID = a2.ID
FOR XML PATH(''))
,1,2,''), '>', '>') CATEGORIES
from a
Totally separate answer because of the change to older technology. I think my original answer is still good for folks using current SQL Server versions, so I don't want to remove it.
I don't remember where I got the function. When I found it today it was named split_delimiter. I changed the name, added some comments, and incorporated the ability to have a delimiter that is more than one character long.
CREATE FUNCTION [dbo].[udf_split_string](#delimited_string VARCHAR(8000), #delimiter varchar(10))
RETURNS TABLE AS
RETURN
WITH cte10(num) AS ( -- 10 rows
SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL
SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL
SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1
)
, cte100(num) AS ( -- 100 rows
SELECT 1
FROM cte10 t1, cte10 t2
)
, cte10000(num) AS ( -- 10000 rows
SELECT 1
FROM cte100 t1, cte100 t2
)
, cte1(num) AS ( -- 1 row per character
SELECT TOP (ISNULL(DATALENGTH(#delimited_string), 0)) ROW_NUMBER() OVER (ORDER BY (SELECT NULL))
FROM cte10000
)
, cte2(num) AS ( -- locations of strings
SELECT 1
UNION ALL
SELECT t.num + len(replace(#delimiter, ' ', '_'))
FROM cte1 t
WHERE SUBSTRING(#delimited_string, t.num, len(replace(#delimiter, ' ', '_'))) = #delimiter
)
, cte3(num, [len]) AS (
SELECT t.num
, ISNULL(NULLIF(CHARINDEX(#delimiter, #delimited_string, t.num), 0) - t.num, 8000)
FROM cte2 t
)
SELECT [Key] = ROW_NUMBER() OVER (ORDER BY t.num)
, [Value] = SUBSTRING(#delimited_string, t.num, t.[len])
FROM cte3 t;
GO
DECLARE #PRODUCTS TABLE (ID int, DESCRIPTION varchar(50), CAT varchar(30), SUBCAT varchar(30), SUBSUBCAT varchar(30));
INSERT #PRODUCTS (ID, DESCRIPTION, CAT, SUBCAT, SUBSUBCAT) VALUES
(1, 'NIKE MILLENIUM', '1, 2', '10, 12', '100, 135'),
(2, 'NIKE CORTEZ', '1, 5', '12, 15', '104, 374'),
(3, 'ADIDAS PANTS', '2, 6, 1', '27, 35, 10', '238, 374, 100'),
(4, 'JOE THE PLUMBER JEANS', '1, 5', '27', '238, 374');
DECLARE #CATS TABLE (ID int, DESCR varchar(100));
INSERT #CATS (ID, DESCR) VALUES
(1, 'MEN'),
(2, 'WOMEN'),
(3, 'UNISEX'),
(4, 'KIDS'),
(5, 'TEENS'),
(6, 'BACK TO SCHOOL');
DECLARE #SUBCATS TABLE (ID int, DESCR varchar(100));
INSERT #SUBCATS (ID, DESCR) VALUES
(10, 'FOOTWEAR'),
(12, 'OUTERWEAR'),
(14, 'SWIMWEAR'),
(15, 'HOODIES'),
(27, 'CLOTHING'),
(35, 'SPORTS');
DECLARE #SUBSUBCATS TABLE (ID int, DESCR varchar(100));
INSERT #SUBSUBCATS (ID, DESCR) VALUES
(100, 'RUNNING'),
(104, 'ZIP TOPS'),
(135, 'FLEECE'),
(157, 'BIKINIS'),
(238, 'PANTS'),
(374, 'JOGGERS');
;
with prod as (
SELECT p.ID,
p.DESCRIPTION
, c.value as CatId
, c.[key] as CatKey
, sc.value as SubCatId
, sc.[key] as SubCatKey
, ssc.value as SubSubCatId
, ssc.[key] as SubSubCatKey
FROM #PRODUCTS p
cross apply dbo.udf_split_string(cat, ', ') c
cross apply dbo.udf_split_string(subcat, ', ') sc
cross apply dbo.udf_split_string(subsubcat, ', ') ssc
where c.[key] = sc.[key]
and c.[key] = ssc.[key]
)
, a as (
select p.ID
, p.DESCRIPTION
, c.DESCR + ' > ' + sc.DESCR + ' > ' + ssc.DESCR as CATEGORIES
, p.CatKey
from prod p
inner join #CATS c on c.ID = p.CatId
inner join #SUBCATS sc on sc.ID = p.SubCatId
inner join #SUBSUBCATS ssc on ssc.ID = p.SubSubCatId
)
select DISTINCT ID
, DESCRIPTION
, replace(STUFF((SELECT distinct ' | ' + a2.CATEGORIES
from a a2
where a.ID = a2.ID
FOR XML PATH(''))
,1,2,''), '>', '>') CATEGORIES
from a
Well that should do work, i changed your character ">" for "-" just for see the data more simple.
the design of your tables is not perfect but the first try almost never is.
select mainp.ID, mainp.DESCRIPTION, stuff(ppaths.metapaths, len(ppaths.metapaths),1,'') metalinks
from #PRODUCTS mainp
cross apply(
select
(select
c.DESCR + '-' + sc.DESCR + '-' + sbc.DESCR + '|'
from #PRODUCTS p
cross apply (select row_number() over(order by Value) id, Value from split(p.CAT, ','))cat_ids
inner join #cats c on c.ID = cat_ids.Value
cross apply (select row_number() over(order by Value) id, Value from split(p.SUBCAT, ','))subcat_ids
inner join #SUBCATS sc on sc.ID = subcat_ids.Value
and subcat_ids.id = subcat_ids.id
cross apply (select row_number() over(order by Value) id, Value from split(p.SUBSUBCAT, ','))subsubcat_ids
inner join #SUBSUBCATS sbc on sbc.ID = subsubcat_ids.Value
and subsubcat_ids.id = subcat_ids.id
where p.id = mainp.ID
for xml path('')) metapaths
) ppaths
the link for split function
https://desarrolladores.me/2014/03/sql-server-funcion-split-para-dividir-un-string/

SQL Server : SELECT query to get DISTINCT and MAX display order value

I have a product table, Category table, and Mapping table. Category saved as a category tree. If a single product has mapped with the last category in a hierarchy of level three. All the levels saved in the mapping table with the same product id.
eg : Assume there is category tre like this Electronic>LapTops>DELL and when product id = 1 assigned to category 'DELL' mapping will save as [1,Electronic],[1,LapTops],[1,DELL]
When I get data with a select query all the category levels appear with the same product Id.
My problem is I need to retrieve data as [productId, ProductName, LastCategortLevel, CategoryName, CategoryId].
Refer actual result below. I just need to pick the highlighted product with the last category level which is the highest category order level.
I can't use another stored procedure or function because it's a small part of a large stored procedure.
The actual database tables are very big. But I have tried to implement the same scenario with small temp tables. see the below queries.
DECLARE #Products TABLE (ProductId INT NOT NULL)
INSERT INTO #Products(ProductId)
SELECT ProductId
FROM (VALUES (1), (2), (3), (4)) as x (ProductId)
DECLARE #Categories TABLE (CategoId INT NOT NULL,
Name VARCHAR(MAX) NOT NULL,
ParentCategoryId INT NOT NULL,
DisplayOrder INT NOT NULL)
-- 1st category tree
INSERT INTO #Categories VALUES (10, 'Electronic', 0, 1)
INSERT INTO #Categories VALUES (11, 'LapTops', 10, 2)
INSERT INTO #Categories VALUES (12, 'DELL', 11, 3)
INSERT INTO #Categories VALUES (13, 'HP', 11, 3)
-- 2st category tree
INSERT INTO #Categories VALUES (14, 'Clothes', 0, 1)
INSERT INTO #Categories VALUES (15, 'T-Shirts', 14, 2)
INSERT INTO #Categories VALUES (16, 'Red', 15, 3)
INSERT INTO #Categories VALUES (17, 'Denim', 14, 2)
INSERT INTO #Categories VALUES (18, 'Levise', 17, 3)
DECLARE #Product_Category_Mappings TABLE(MappingId INT NOT NULL,
ProductId INT NOT NULL,
CategoryId INT NOT NULL)
INSERT INTO #Product_Category_Mappings VALUES (100, 1, 10)
INSERT INTO #Product_Category_Mappings VALUES (101, 1, 11)
INSERT INTO #Product_Category_Mappings VALUES (102, 1, 12)
INSERT INTO #Product_Category_Mappings VALUES (103, 2, 10)
INSERT INTO #Product_Category_Mappings VALUES (104, 2, 11)
INSERT INTO #Product_Category_Mappings VALUES (105, 2, 12)
INSERT INTO #Product_Category_Mappings VALUES (106, 3, 14)
INSERT INTO #Product_Category_Mappings VALUES (107, 3, 15)
INSERT INTO #Product_Category_Mappings VALUES (108, 3, 16)
INSERT INTO #Product_Category_Mappings VALUES (109, 4, 14)
INSERT INTO #Product_Category_Mappings VALUES (110, 4, 17)
INSERT INTO #Product_Category_Mappings VALUES (111, 4, 18)
SELECT *
FROM #Products P
INNER JOIN #Product_Category_Mappings M ON M.ProductId = P.ProductId
INNER JOIN #Categories C ON C.CategoId = M.CategoryId
WHERE M.ProductId = P.ProductId
ORDER BY P.ProductId, C.DisplayOrder
Result of the above script. How I get highlighted rows?
For each ProductId, you want the row with highest DisplayOrder. You can use window functions:
SELECT *
FROM (
SELECT *, ROW_NUMBER() OVER(PARTITION BY P.ProductId ORDER BY C.DisplayOrder DESC) rn
FROM #Products P
INNER JOIN #Product_Category_Mappings M ON M.ProductId = P.ProductId
INNER JOIN #Categories C ON C.CategoId = M.CategoryId
WHERE M.ProductId = P.ProductId
) t
WHERE rn = 1
ORDER BY P.ProductId, C.DisplayOrder

Using SQL - How to aggregate Payment Type

How can I aggregate the PaymentType field so that if a customer has used all payment types previously, the payment type screen displays 'All' instead of 'Cash', 'Check', 'Credit/Debit Card', or 'Corporate Account'? Below is the script necessary to demonstrate the issue.
CREATE TABLE #Customer (
CustomerId int,
CustomerName nvarchar(100),
Address nvarchar(100),
)
INSERT INTO #Customer
VALUES (1, 'Bill', '123 1st St'),
(2, 'Fred', '111 Market St'),
(3, 'Lisa', '01 Boulevard')
CREATE TABLE #Product (
ProductId int,
ProductName nvarchar(100)
)
INSERT INTO #Product
VALUES (1, 'Corn Flakes'),
(2, 'Cheerios'),
(3, 'Granoloa')
CREATE TABLE #PaymentType (
PaymentId int,
PaymentTypeName nvarchar(100)
)
INSERT INTO #PaymentType
VALUES (1, 'Cash'),
(2, 'Check'),
(3, 'Credit/Debit Card'),
(4, 'Corporate Account')
CREATE TABLE #TransactionLog (
LogId int,
ProductId int,
CustomerId int,
PaymentId int,
Amount float
)
INSERT INTO #TransactionLog
VALUES (1, 1, 1, 1, 2.00),
(2, 2, 1, 2, 2.40),
(3, 3, 1, 3, 1.80),
(4, 1, 1, 4, 2.00),
(5, 1, 2, 4, 2.00),
(6, 2, 3, 2, 2.40)
SELECT * from #Customer
SELECT * from #Product
SELECT * from #PaymentType
SELECT * from #TransactionLog
SELECT
c.CustomerName,
p.ProductName,
pt.PaymentTypeName as PaymentType
FROM #TransactionLog t
join #Product p on t.ProductId = p.ProductId
join #Customer c on t.CustomerId = c.CustomerId
join #PaymentType pt on t.PaymentId = pt.PaymentId
DROP TABLE #Customer
DROP TABLE #Product
DROP TABLE #PaymentType
DROP TABLE #TransactionLog
Something like this will work. You might need to tune it if you have a lot of data or an underpowered database instance though.
;WITH
Data AS(
SELECT
c.CustomerName,
p.ProductName,
pt.PaymentTypeName as PaymentType
FROM #TransactionLog t
join #Product p on t.ProductId = p.ProductId
join #Customer c on t.CustomerId = c.CustomerId
join #PaymentType pt on t.PaymentId = pt.PaymentId
)
SELECT DISTINCT
d.CustomerName,
d.ProductName,
PaymentType = IIF( ( (SELECT COUNT(DISTINCT sd.PaymentType) FROM Data sd WHERE sd.CustomerName = d.CustomerName) = 4),
'All',
d.PaymentType
)
FROM
Data d
select CustomerName,case when count(distinct(PaymentType)) = 4 then 'All' end as PaymentType from (SELECT
c.CustomerName,
p.ProductName,
pt.PaymentTypeName as PaymentType
FROM #TransactionLog t
join #Product p on t.ProductId = p.ProductId
join #Customer c on t.CustomerId = c.CustomerId
join #PaymentType pt on t.PaymentId = pt.PaymentId )
group by CustomerName
This is one way of doing this you can tweak it as per your need.

Joining two different tables with a common third table on a common column

Here are the tables
Table: Status
ID, StatusDesc
1, New
2, Active
3, Cancelled
4, Complete
Table: Order (foreign key relationship with Status table above)
ID, OrderNumber, StatusID
1, 1001 , 1
2, 1002, 1
3, 1003, 2
4, 1004, 3
5, 1500, 4
Table: LineItem(foreign key relationship with Order and Status tables above)
ID, OrderNumber, LineItemNumber, StatusID
1, 1001 , 1, 1
2, 1001 , 2, 1
3, 1002 , 1, 2
4, 1002 , 2, 1
5, 1003 , 1, 2
6, 1004 , 1, 3
7, 1004 , 2, 4
8, 1500 , 1, 3
As you can see, the table Status holds the statuses common for both Order and LineItem tables.
I want to produce the result which will include columns like this, status description for both Order and LineItem:
OrderNumber, LineItemNumber, StatusDesc_Order, StatusDesc_LineItem
How to do this?
You could join the Status table twice to achieve this:
SELECT
o.OrderNumber
, li.LineItemNumber
, orderStatus.StatusDesc AS StatusDesc_Order
, lineItemStatus.StatusDesc AS StatusDesc_LineItem
FROM [LineItem] AS li
INNER JOIN [Status] AS lineItemStatus ON li.StatusID = lineItemStatus.ID
INNER JOIN [Order] AS o ON li.OrderNumber = o.OrderNumber
INNER JOIN [Status] AS orderStatus ON o.StatusID = orderStatus.ID
I do suggest however you try and stay away from table names using reserved keywords like Order and Status, it also is good practice to explcitly add schema prefixes before the table names in the query (i.e. dbo.Status or another user defined schema).
If the required results really are that simple then just use a couple of sub-queries e.g.
-- SETUP TEST DATA
declare #Order table (id int, OrderNumber int, StatusId int)
insert into #Order (id, OrderNumber, StatusId)
values (1, 1001, 1), (2, 1002, 1), (3, 1003, 2), (4, 1004, 3), (5, 1500, 4)
declare #LineItem table (id int, OrderNumber int, LineItemNumber int, StatusId int)
insert into #LineItem (id, OrderNumber, LineItemNumber, StatusId)
values (1, 1001, 1, 1), (2, 1001, 2, 1), (3, 1002, 1, 2), (4, 1002, 2, 1), (5, 1003, 1, 2), (6, 1004, 1, 3), (7, 1004, 2, 4), (8, 1500, 2, 3)
declare #Status table (id int, StatusDesc varchar(32))
insert into #Status(id, StatusDesc)
values (1,'New'), (2,'Active'), (3,'Cancelled'), (4,'Complete')
-- QUERY DATA
select LI.OrderNumber, LI.LineItemNumber
, (select S.StatusDesc from #Status S where S.id = StatusId) [StatusDesc_Order]
, (select S.StatusDesc from #Status S where S.id = (select O.StatusId from #Order O where O.OrderNumber = LI.OrderNumber)) [StatusDesc_LineItem]
from #LineItem LI
order by LI.OrderNumber, LI.LineItemNumber
Note: If you provide your sample data in this format in future questions you make your question much easier to answer.

SQL Randomise rows based on date and int to only change results order daily

I would like to generate a "random" integer for each row returned from a select statement where the random int only changes once per day (before and after 4am).
Example
declare #Date datetime
set #Date = dateadd(dd, 8, GETDATE())
declare #DateNumber int
set #DateNumber = LEFT(CONVERT(VARCHAR(8), #Date, 112),10)
+ cast(CASE WHEN DATEPART(hh, #Date) > 4 THEN 1 ELSE 0 END as varchar(1))
declare #Customers table (Id int, Customer varchar(150), OrderNo int)
insert into #Customers (Id, Customer) values (1, 'Cust A'), (2, 'Cust B'),
(3, 'Cust C'), (4, 'Cust D'), (5, 'Cust E'), (6, 'Cust F'),
(7, 'Cust G'), (8, 'Cust H'), (9, 'Cust I')
-- my 1st attempt which doesnt work
update #Customers set OrderNo = rand(#DateNumber) / rand(Id) * 100
select * from
#Customers order by OrderNo
The order of the results should remain constant until I change the dd value in the set #Date statement at the top.
Any ideas? Is this possible?
(outside of calculating this daily via a SQL job)
updated solution with HABO's recomendation
declare #Date datetime = dateadd(hh, 36, GETDATE())
declare #DateNumber int = LEFT(CONVERT(VARCHAR(8), #Date, 112),10) +
cast(CASE WHEN DATEPART(hh, #Date) > 4 THEN 1 ELSE 0 END as varchar(1))
declare #Customers table (Id int, Customer varchar(150), OrderNo int)
insert into #Customers (Id, Customer) values (1, 'Cust A'), (2, 'Cust B'),
(3, 'Cust C'), (4, 'Cust D'), (5, 'Cust E'), (6, 'Cust F'),
(7, 'Cust G'), (8, 'Cust H'), (9, 'Cust I')
declare #ThrowAway as Float = Rand(#DateNumber)
declare #ID int
set #ID = (select min(ID) from #Customers)
while #ID is not null
begin
update #Customers set OrderNo = Floor(Rand() * 100) + 1 where ID = #ID
set #ID = (select min(ID) from #Customers where ID > #ID)
end
select * from #Customers order by OrderNo
When you provide a seed to RAND( Seed ) it will return the same result. If you use a seed value prior to your UPDATE query it will initialize the sequence. Thereafter just use RAND() without an argument. Something like:
declare #ThrowAway as Float = Rand( #DateNumber )
update #Customers
set OrderNo = Floor( Rand() * 100 ) + 1
Do keep in mind that random values include duplicates.