Count unique field when another field is all NULL - sql-server

I have a table with three columns, which can contain duplicate rows
org - int NULL
id - int NULL
complete - bit NULL
So I might have data like so:
org | id | complete
-------------------
1 | 1 | 1
1 | 2 | NULL
1 | 2 | 1
1 | 3 | 1
2 | 3 | 1
2 | 4 | 1
2 | 4 | NULL
I want to get a count of all distinct id by org. That's easy enough to do with a COUNT(DISTINCT id) expression. Where I'm running into trouble now is I also want a count of all distinct id where any of the complete values isn't 1.
So from the above I'd want this output:
org | distinct id | distinct incomplete id
------------------------------------------
1 | 3 | 1
2 | 2 | 1
So for org 2, because id of 4 included a NULL value, then I can't count id 4 as fully complete, thus just id 3 is complete, thus resulting in a 1 in the distinct incomplete id column. So I don't know how to fill in the ???? part of the below query.
SELECT org, COUNT(DISTINCT id) TotalPeople, ???? IncompletePeople
FROM table
GROUP BY org

Try the following approach
DECLARE #T TABLE
(
Org INT,
Id INT,
Complete BIT
)
INSERT INTO #T
VALUES(1,1,1),(1,2,NULL),(1,2,1),(1,3,1),(2,3,1),(2,4,1),(2,4,NULL)
SELECT
Org,
DistinctId = COUNT(DISTINCT Id),
DistinctIncompleteId = SUM(CASE Complete WHEN 1 THEN 0 ELSE 1 END)
FROM #T
GROUP BY Org

You may try this way
create table #temptable ( org int, id int, comp int )
Go
insert into #temptable ( org, id, comp )
values ( 1,1,1)
,( 1, 2, null)
,( 1, 2, 1)
,( 1, 3, 1)
,( 2, 3, 1)
,( 2, 4, null)
,( 2, 4, 1)
select d.org, d.DistinctId, f.incompleteId from (
select COUNT (distinct id) as DistinctId , org from #temptable group by org) as d full outer join (
select COUNT (distinct id) as incompleteId , org from #temptable where comp is null group by org) as f on d.org=f.org
go
drop table #temptable

Group it by "org" and by "complete". Then put HAVING complete=1. Hope the code below helps you:
SELECT org, COUNT(id) TotalPeople, complete
FROM table
GROUP BY org,complete
HAVING complete=1 (complete IS NULL *for incomplete*)

Related

SQL SERVER update or insert after left join

I have a Table Animals
Id | Name | Count | -- (other columns not relevant)
1 | horse | 11
2 | giraffe | 20
I want to try to insert or update values from a CSV string
Is it possible to do something like the following in 1 query?
;with results as
(
select * from
(
values ('horse'), ('giraffe'), ('lion')
)
animal_csv(aName)
left join animals on
animals.[Name] = animal_csv.aName
)
update results
set
[Count] = 1 + animals.[Count]
-- various other columns are set here
where Id is not null
--else
--insert into results ([Name], [Count]) values (results.aName, 1)
-- (essentially Where id is null)
It looks like what you're looking for is a table variable or temporary table rather than a common table expression.
If I understand your problem correctly, you are building a result set based on data you're getting from a CSV, merging it by incrementing values, and then returning that result set.
As I read your code, it looks as if your results would look like this:
aName | Id | Name | Count
horse | 1 | horse | 12
giraffe | 2 | giraffe | 21
lion | | |
I think what you're looking for in your final result set is this:
Name | Count
horse | 12
giraffe | 21
lion | 1
First, you can get from your csv and table to a resultset in a single CTE statement:
;WITH animal_csv AS (SELECT * FROM (VALUES('horse'),('giraffe'), ('lion')) a(aName))
SELECT ISNULL(Name, aName) Name
, CASE WHEN [Count] IS NULL THEN 1 ELSE 1 + [Count] END [Count]
FROM animal_csv
LEFT JOIN animals
ON Name = animal_csv.aName
Or, if you want to build your resultset using a table variable:
DECLARE #Results TABLE
(
Name VARCHAR(30)
, Count INT
)
;WITH animal_csv AS (SELECT * FROM (VALUES('horse'),('giraffe'), ('lion')) a(aName))
INSERT #Results
SELECT ISNULL(Name, aName) Name
, CASE WHEN [Count] IS NULL THEN 1 ELSE 1 + [Count] END [Count]
FROM animal_csv
LEFT JOIN animals
ON Name = animal_csv.aName
SELECT * FROM #results
Or, if you just want to use a temporary table, you can build it like this (temp tables are deleted when the connection is released/closed or when they're explicitly dropped):
;WITH animal_csv AS (SELECT * FROM (VALUES('horse'),('giraffe'), ('lion')) a(aName))
SELECT ISNULL(Name, aName) Name
, CASE WHEN [Count] IS NULL THEN 1 ELSE 1 + [Count] END [Count]
INTO #results
FROM animal_csv
LEFT JOIN animals
ON Name = animal_csv.aName
SELECT * FROM #results

How to join multiple tables , one table as dynamic columns and the other table as the values of these columns

I'm new to SQL Server pivot, and i'm trying to solve a problem where i need to output the following tables into one table that includes the values based on one table's columns
Here's my tables
ContactGroup
Title ID
---------- -----------
Group A 1
ContactsInGroups
ContactId GroupId
----------- -----------
1 1
2 1
3 1
ContactVariables
ID Name GroupId Order
----------- ---------- ----------- ------
1 Invoice 1 1
2 Due Date 1 1
ContactsVariablesValues
ContactVariableId ContactId Value
----------------- ----------- -----
1 1 600
Desired output
GroupId ContactId Invoice Due Date
----------- ----------- ----------- -----------
1 1 600 NULL
1 2 NULL NULL
1 3 NULL NULL
Ali, here is an example that will at least get you started. You can run the following in SSMS.
Create some table variables and insert your sample data.
DECLARE #ContactGroup TABLE ( id INT, title VARCHAR(50) );
INSERT INTO #ContactGroup ( id, title ) VALUES ( 1, 'Group A' );
DECLARE #ContactsInGroup TABLE ( ContactID INT, GroupID INT );
INSERT INTO #ContactsInGroup ( ContactID, GroupID ) VALUES ( 1, 1 ), ( 2, 1 ), ( 3, 1 );
DECLARE #ContactVariables TABLE ( id INT, [name] VARCHAR(50), GroupID INT, [Order] INT );
INSERT INTO #ContactVariables ( id, [name], GroupID, [Order] ) VALUES ( 1, 'Invoice', 1, 1 ), ( 2, 'Due Date', 1, 1 );
DECLARE #ContactsVariablesValues TABLE ( ContactVariableID INT, ContactID INT, [value] INT );
INSERT INTO #ContactsVariablesValues ( ContactVariableID, ContactID, [value] ) VALUES ( 1, 1, 600 );
Then query the data as follows:
SELECT
ContactGroup.id AS GroupID
, ContactsInGroup.ContactID
, ContactVars.Invoice
, ContactVars.[Due Date]
FROM #ContactGroup AS ContactGroup
INNER JOIN #ContactsInGroup AS ContactsInGroup
ON ContactGroup.id = ContactsInGroup.GroupID
OUTER APPLY (
SELECT
[Invoice], [Due Date]
FROM (
SELECT
Vars.[name]
, Vals.[value]
FROM #ContactVariables AS Vars
LEFT OUTER JOIN #ContactsVariablesValues Vals
ON Vars.id = Vals.ContactVariableID
WHERE
Vars.GroupID = 1
AND Vals.ContactID = ContactsInGroup.ContactID
) AS ContactData
PIVOT (
MIN( [value] )
FOR [name] IN (
[Invoice], [Due Date]
)
) AS pvt
) AS ContactVars
ORDER BY
ContactGroup.id, ContactsInGroup.ContactID;
Which returns:
+---------+-----------+---------+----------+
| GroupID | ContactID | Invoice | Due Date |
+---------+-----------+---------+----------+
| 1 | 1 | 600 | NULL |
| 1 | 2 | NULL | NULL |
| 1 | 3 | NULL | NULL |
+---------+-----------+---------+----------+
Things to note
The "magic" here is in the OUTER APPLY. This allows us to query a subset of data based on the primary data returned, in this case the GroupID and ContactID. OUTER APPLY will also return rows with NULL values like you desire.
You're going to have some challenges here, namely that to use a PIVOT as shown in my example, you will need to know all the values ( Invoice, Due Date, etc... ) that will become column headers. Based on your setup, I'm thinking this may not be the case, so you will be forced to resort to an technique that creates and executes a dynamic PIVOT statement for you within the OUTER APPLY.
You also might consider using a TABLE VALUED FUNCTION that does the PIVOT work that can then be JOINed on vs. an OUTER APPLY.
You have several options, but hopefully this helps jumpstart your thinking.

Field equal 1 display

I am using SQL Server 2008 and I would like to only get the activityCode for the orderno when it equals 1 if there are duplicate orderno with the activityCode equals 0.
Also, if the record for orderno activityCode equals 0 then display those records also. But I would only like to display the orderno when the activityCode equals 0 if the same orderno activityCode does not equal 1 or the activityCode only equals 0. I hope this is clear and makes sense but let me know if I need to provide more details. Thanks
--create table
create table po_v
(
orderno int,
amount number,
activityCode number
)
--insert values
insert into po_v values
(170268, 2774.31, 0),
(17001988, 288.82, 0),
(17001988, 433.23, 1),
(170271, 3786, 1),
(170271, 8476, 0),
(170055, 34567, 0)
--Results
170268 | 2774.31 | 0
17001988 | 433.23 | 1
170271 | 3786 | 1
170055 | 34567 | 0
*****Updated*****
I have inserted two new records and the results have been updated. The data in the actual table has other numbers besides 0 and 1. The select statement displays the correct orderno's but I would like the other records for the orderno to display also. The partition only populates one record per orderno. If possible I would like to see the records with the same activityCode.
--insert values
insert into po_v values
(170271, 3799, 1),
(172525, 44445, 2)
--select statement
SELECT Orderno,
Amount,
Activitycode
FROM (SELECT orderno,
amount,
activitycode,
ROW_NUMBER()
OVER(
PARTITION BY orderno
ORDER BY activitycode DESC) AS dup
FROM Po_v)dt
WHERE dt.dup = 1
ORDER BY 1
--select statement results
170055 | 34567 | 0
170268 | 2774.31 | 0
170271 | 3786 | 1
172525 | 44445 | 2
17001988 | 433.23 | 1
--expected results
170055 | 34567 | 0
170268 | 2774.31 | 0
170271 | 3786 | 1
170271 | 3799 | 1
172525 | 44445 | 2
17001988 | 433.23 | 1
Not totally clear what you are trying to do here but this returns the output you are expecting.
select orderno
, amount
, activityCode
from
(
select *
, RowNum = ROW_NUMBER() over(partition by orderno order by activityCode desc)
from po_v
) x
where x.RowNum = 1
---EDIT---
With the new details this is a very different question. As I understand it now you want all row for that share the max activity code for each orderno. You can do this pretty easily with a cte.
with MyGroups as
(
select orderno
, Activitycode = max(activitycode)
from po_v
group by orderno
)
select *
from po_v p
join MyGroups g on g.orderno = p.orderno
and g.Activitycode = p.Activitycode
Try this
SELECT Orderno,
Amount,
Activitycode
FROM (SELECT orderno,
amount,
activitycode,
ROW_NUMBER()
OVER(
PARTITION BY orderno
ORDER BY activitycode DESC) AS dup
FROM Po_v)dt
WHERE dt.dup = 1
ORDER BY 1
Result
Orderno Amount Activitycode
------------------------------------
170055 34567.00 0
170268 2774.31 0
170271 3786.00 1
17001988 433.23 1

Converting multiple rows into one in SQL Server

I have 2 tables:
Product:
ProductId | Name | Description
----------+-------+-------------------------------------
1 | shirt | this is description for shirt
2 | pent | this is description for pent
ProductOverride:
ProductOverrideId | ColumnId | Value | ProductId
------------------+-----------+------------------------+-----------
1 | 1 | overridden name | 1
2 | 2 | overridden description | 1
where ColumnId is column_id from sys.columns.
I want to select all the products with the following requirement:
if product name or product description is overridden in ProductOverride table, get the overridden value of name/description, otherwise get the name/description value from the product table.
Sample output:
ProductId | Name | Description
----------+-----------------+---------------------------
1 | overridden name | overridden description
2 | pent | this is description for pent
I have the following query which returns the exact result.
DECLARE #productNameColumnId INT = 1;
DECLARE #productDescriptionColumnId INT = 2;
WITH OverriddenProductNameCTE ([Value], [ProductId]) AS
(
SELECT
temp.[Value], temp.ProductId
FROM
ProductOverride temp
WHERE
temp.ColumnId = #productNameColumnId
), OverriddenProductDescriptionCTE ([Value], [ProductId]) AS
(
SELECT
temp.[Value], temp.ProductId
FROM
ProductOverride temp
WHERE
temp.ColumnId = #productDescriptionColumnId
)
SELECT
p.ProductId,
CASE
WHEN EXISTS(SELECT [Value]
FROM OverriddenProductNameCTE opnc
WHERE opnc.ProductId = p.ProductId)
THEN (SELECT [Value]
FROM OverriddenProductNameCTE opnc
WHERE opnc.ProductId = p.ProductId)
ELSE p.[Name]
END AS [Name],
CASE
WHEN EXISTS(SELECT [Value]
FROM OverriddenProductDescriptionCTE opdc
WHERE opdc.ProductId = p.ProductId)
THEN (SELECT [Value]
FROM OverriddenProductDescriptionCTE opdc
WHERE opdc.ProductId = p.ProductId)
ELSE p.[Description]
END AS [Description]
FROM
product p
but in the CASE statements, I have the following repetitive code:
SELECT [Value]
FROM OverriddenProductNameCTE opnc
WHERE opnc.ProductId = p.ProductId
which means if the CASE statement's first condition is true DBMS will execute the same query again in the THEN part.
I want to improve this query both in terms of simplifying the query and in terms of processing.
Also if there is any advantage of using CTEs in this situation?
If it's only 2 columns I think the simplest thing you can do is left join twice with coalesce:
SELECT p.ProductId
,COALESCE(poN.Value, p.Name) As Name
,COALESCE(poD.Value, p.Description) As Description
FROM Product p
LEFT JOIN ProductOverride poN ON p.ProductId = poN.ProductId AND poN.ColumnId = 1
LEFT JOIN ProductOverride poD ON p.ProductId = poD.ProductId AND poD.ColumnId = 2
If it's more columns I would suggest pivoting the ProductOverride table and left join to that - Like this (a complete example):
Create and populate sample tables (Please save us this step in your future questions)
CREATE TABLE Product
(
ProductId int,
Name varchar(100),
Description varchar(100),
price int null
);
INSERT INTO Product VALUES
(1, 'shirt', 'Description for shirts', 1),
(2, 'Pants', 'Description for pants', 4),
(3, 'Socks', 'Description for socks', 5)
CREATE TABLE ProductOverride
(
ProductOverrideId int,
ColumnId int,
Value varchar(100),
ProductId int
);
INSERT INTO ProductOverride VALUES
(1,1,'product 1 name',1),
(2,2,'product 1 desc',1),
(3,3,'7',1),
(4,1,'pants name',2),
--Note: no pants description in the override tabl
(6,3,'8',2);
-- Note: no socks at all in override table
The query:
SELECT p.ProductId
,COALESCE(override.[1], p.Name) As Name
,COALESCE(override.[2], p.Description) As Description
,COALESCE(CAST(override.[3] as int), p.Price) As Price
FROM Product p
LEFT JOIN
(
SELECT *
FROM
(
SELECT ProductId, Value, ColumnId -- Columns To use for pivot
FROM ProductOverride
) ColumnsToPivot
PIVOT (
max (Value)
for ColumnId in ([1], [2], [3]) -- Values in ColumnId column to make the column names
) as pivotedData
) as override ON p.ProductId = override.ProductId
Results:
ProductId Name Description Price
1 product 1 name product 1 desc 7
2 pants name Description for pants 8
3 Socks Description for socks 5
You can see a live demo on rextester.

SQL Recursive CTE: Finding objects linked by property

I'm just trying to understand CTE and recursion to solve an issue that I would previously have used a cursor for.
create table ##ACC (
AccNo int,
Property char
)
Insert into ##ACC
VALUES (1,'A'),(1,'B'),(2,'A'),(2,'C'),(3,'C'),(4,'D')
What I'm trying to achieve is to get a list of all AccNo's, and all AccNo's they're related to via Property. So my expected results are
PrimaryAccNo | LinkedAccNo
1 | 1
1 | 2
1 | 3
2 | 1
2 | 2
2 | 3
3 | 1
3 | 2
3 | 3
4 | 4
I've attempted the following code and variations but I either get 4 results (PrimaryAccNo=LinkedAccNo) only or I hit 100 recursions.
WITH Groups(PrimaryAccNo, LinkedAccNo)
AS
(
Select distinct AccNo, AccNo from ##ACC
UNION ALL
Select g.PrimaryAccNo, p.AccNo from
##ACC p inner join Groups g on p.AccNo=g.LinkedAccNo
inner join ##ACC pp on p.Property=pp.Property
where p.AccNo<> pp.AccNo
)
Select PrimaryAccNo,LinkedAccNo
from Groups
What am I doing wrong?
You're running into an infinite loop caused by cycles within your data, e.g.: 1 > 2 > 3 > 2 > ... . The solution is to keep track of the rows that have already been "consumed". Due to limitations in CTEs, this has to be done by including the history within each CTE row, e.g. by assembling the path followed to arrive at each row. You can uncomment the , Path on the final select to see what is going on.
-- Sample data.
declare #ACC as Table ( AccNo Int, Property Char );
insert into #ACC values
( 1, 'A' ), ( 1, 'B' ), ( 2, 'A' ), ( 2, 'C' ), ( 3, 'C' ), ( 4, 'D' );
select * from #ACC;
-- Recursive CTE.
with Groups as (
select distinct AccNo, AccNo as LinkedAccNo,
Cast( '|' + Cast( AccNo as VarChar(10) ) + '|' as VarChar(1024) ) as Path
from #ACC
union all
select G.AccNo, A.AccNo, Cast( Path + Cast( A.AccNo as VarChar(10) ) + '|' as VarChar(1024) )
from Groups as G inner join -- Take the latest round of new rows ...
#ACC as AP on AP.AccNo = G.LinkedAccNo inner join -- ... and get the Property for each ...
#ACC as A on A.Property = AP.Property -- ... to find new linked rows.
where G.Path not like '%|' + Cast( A.AccNo as VarChar(10) ) + '|%' )
select AccNo, LinkedAccNo -- , Path
from Groups
order by AccNo, LinkedAccNo;
Another approach similar to yours but differs in the following:
The property value is included in the recursive CTE so that it can be used later
The < is used to prevent duplicates and the resulting infinite recursion
Another CTE is added AccGroups to provide the mirror of the relations
A demo fiddle has been included below:
CREATE TABLE ##ACC (
AccNo int,
Property char
);
INSERT INTO ##ACC
VALUES (1,'A'),(1,'B'),(2,'A'),(2,'C'),(3,'C'),(4,'D');
WITH Groups(PrimaryAccNo, LinkedAccNo, Property) AS (
SELECT AccNo, AccNo, Property FROM ##ACC
UNION ALL
SELECT g.PrimaryAccNo, pp.AccNo, pp.Property
FROM Groups g
INNER JOIN ##ACC p ON g.Property=p.Property AND
g.LinkedAccNo < p.AccNo
INNER JOIN ##ACC pp ON p.AccNo = pp.AccNo
),
AccGroups AS (
SELECT DISTINCT * FROM (
SELECT PrimaryAccNo, LinkedAccNo FROM Groups
UNION ALL
SELECT LinkedAccNo, PrimaryAccNo FROM Groups
) t
)
SELECT * FROM AccGroups
ORDER BY PrimaryAccNo,LinkedAccNo
GO
PrimaryAccNo | LinkedAccNo
-----------: | ----------:
1 | 1
1 | 2
1 | 3
2 | 1
2 | 2
2 | 3
3 | 1
3 | 2
3 | 3
4 | 4
db<>fiddle here

Resources