Must have a value in each section - tsql

Find records where value for supplied categories does not exist
So I have a requirement to produce a query that references data entered by users into a user form. The form is a tick box type and if no value is selected then a tick should be entered that says 'None of the above'. The form is divided into 4 different sections each needing a tick in relevant boxes or if no box is relevant then each section should have a tick in the 'None of the above' box for that section.
Now i am looking at the tables in the background and trying to construct a query that highlights records that do not have a tick in one of the boxes in one or more sections, not even the 'None of the above' box.
essentially we are looking to make sure that the form (each form has its own id) has been populated completely.
For the purposes of this site assume that my data is all in a single table:
Any idea how i can construct this query?
In the example above i would want to identify customer 789 as they are missing a value for section 3
this is a simplified version of what i am trying to do but hopefully will be clear

Ideally your input form would have validation that would not allow this. However, I realize that may not be under your control. This will work for the simplified example you provided. Hopefully you can modified it to your needs.
The syntax may vary depending on your database platform. This is on SQL Server.
CREATE TABLE #Section (ID INT)
INSERT INTO #Section (ID) VALUES (1)
INSERT INTO #Section (ID) VALUES (2)
INSERT INTO #Section (ID) VALUES (3)
INSERT INTO #Section (ID) VALUES (4)
CREATE TABLE #CustomerResponse (CustomerID INT, FormID INT, SectionID INT, value VARCHAR(25))
INSERT INTO #CustomerResponse (CustomerID, FormID, SectionID, value) VALUES (789, 1, 1, '1')
INSERT INTO #CustomerResponse (CustomerID, FormID, SectionID, value) VALUES (789, 1, 2, 'none of the above')
INSERT INTO #CustomerResponse (CustomerID, FormID, SectionID, value) VALUES (789, 1, 4, '1')
INSERT INTO #CustomerResponse (CustomerID, FormID, SectionID, value) VALUES (454, 2, 1, '1')
INSERT INTO #CustomerResponse (CustomerID, FormID, SectionID, value) VALUES (454, 2, 2, '1')
INSERT INTO #CustomerResponse (CustomerID, FormID, SectionID, value) VALUES (454, 2, 3, '1')
INSERT INTO #CustomerResponse (CustomerID, FormID, SectionID, value) VALUES (454, 2, 4, '1')
SELECT DISTINCT cr1.CustomerID, cr1.FormID, 1 AS MissingSection FROM #CustomerResponse cr1
LEFT JOIN #CustomerResponse cr2 ON cr1.CustomerID = cr2.CustomerId AND cr2.SectionID = 1
WHERE cr2.CustomerID IS null
UNION
SELECT DISTINCT cr1.CustomerID, cr1.FormID, 2 AS MissingSection FROM #CustomerResponse cr1
LEFT JOIN #CustomerResponse cr2 ON cr1.CustomerID = cr2.CustomerId AND cr2.SectionID = 2
WHERE cr2.CustomerID IS null
UNION
SELECT DISTINCT cr1.CustomerID, cr1.FormID, 3 AS MissingSection FROM #CustomerResponse cr1
LEFT JOIN #CustomerResponse cr2 ON cr1.CustomerID = cr2.CustomerId AND cr2.SectionID = 3
WHERE cr2.CustomerID IS null
UNION
SELECT DISTINCT cr1.CustomerID, cr1.FormID, 4 AS MissingSection FROM #CustomerResponse cr1
LEFT JOIN #CustomerResponse cr2 ON cr1.CustomerID = cr2.CustomerId AND cr2.SectionID = 4
WHERE cr2.CustomerID IS null
I do not like this solution, but it does work. Maybe someone will come along with something better.

Related

PostgreSQL: recursively join a second table

I'm struggling with recursion in PostgreSQL. I need to join a first table with a second one, and then recursively join within the second table. I looked at quite a number of examples, but most are about finding the parent records within a single table, and this has left me utterly confused.
Here's a minimal example with tables thing and category. Records in thing may or may not have a category:
id
name
category
1
a5
3
2
passat
2
3
apple
NULL
Records in category may have one or more parents in the same table:
id
name
parent_category
1
vehicle
NULL
2
car
1
3
coupe
2
The result I'm looking for is the combination of all things with their categories, as well as the category level (1 for the direct parent, 2 for the level above).
thing_name
category_name
level
a5
coupe
1
a5
car
2
a5
vehicle
3
passat
car
1
passat
vehicle
2
apple
NULL
NULL
I have a DB Fiddle here: https://www.db-fiddle.com/f/b7V8ddragZZ9x2RsMkdFYn/5
CREATE TABLE category (
id INT,
name TEXT,
parent_category INT
);
INSERT INTO category VALUES (1, 'vehicle', null);
INSERT INTO category VALUES (2, 'car', 1);
INSERT INTO category VALUES (3, 'coupe', 2);
CREATE TABLE thing (
id INT,
name TEXT,
category INT
);
INSERT INTO thing VALUES (1, 'a5', 3);
INSERT INTO thing VALUES (2, 'passat', 2);
INSERT INTO thing VALUES (3, 'apple', null);
Use a CTE to join the tables, giving you a tree-like view of combined thing_categories, which you can then use with a normal recursive CTE.
with recursive join_thing_category as (
select thing.id as thing_id,
thing.name as thing_name,
thing.category as thing_category,
category.id as category_id,
category.name as category_name,
category.parent_category as parent_category
from thing left join category on thing.category=category.id
),
recursive_part(n) as (
select thing_id, thing_name, thing_category, category_id, category_name, parent_category, (0*parent_category) + 1 as level from join_thing_category
union all
select 1, thing_name, thing_category, cat.id category_id, cat.name, cat.parent_category as parent, level+1 as level from recursive_part rp cross join category cat
where cat.id=rp.parent_category
)
select thing_name, category_name, level from recursive_part order by 1, 2, 3 limit 1024;
thing_name
category_name
level
a5
car
2
a5
coupe
1
a5
vehicle
3
apple
passat
car
1
passat
vehicle
2
View on DB Fiddle
The (0*parent_category) + 1 as level bit is so that things with no category get NULL as their level instead of 1.

TSQL: How to return two rows if Column = Null

I have to build a procedure that returns a table at the end, which contains a list of fields where specific substances were applied. I need to return one row for each field and the applied substance.
This works great for all fields where something was actually applied, but I also need to display the same amount of rows for those fields, were nothing was applied.
At the moment I get a table like this:
Field 1 | Substance 1 | 12345 kg
Field 1 | Substance 2 | 23423 kg
Field 2 | Substance 1 | 23236 kg
Field 2 | Substance 2 | 12312 kg
Field 3 | NULL | NULL
I know that I could swap the NULL value with at least one Substance by making a Case-Condition, but I need two rows (one for Substance 1 and one for Substance 2) containing the names of each substance.
Is there any way to achieve this?
Or maybe you have something like this:
CREATE TABLE Fields (
FieldID INT PRIMARY KEY,
FieldName VARCHAR(50) NOT NULL UNIQUE,
)
INSERT INTO dbo.Fields (FieldID, FieldName) VALUES
(1, 'Field 1'),
(2, 'Field 2'),
(3, 'Field 3')
CREATE TABLE dbo.Substances (
SubstanceID INT PRIMARY KEY,
Substance VARCHAR(50) NOT NULL UNIQUE
)
INSERT INTO dbo.Substances (SubstanceID, Substance) VALUES
(1, 'Substance 1'),
(2, 'Substance 2')
CREATE TABLE AppliedSubstances (
FieldID INT NOT NULL REFERENCES dbo.Fields,
SubstanceID INT NOT NULL REFERENCES dbo.Substances,
Quantity INT NOT NULL
)
INSERT INTO dbo.AppliedSubstances (FieldID, SubstanceID, Quantity) VALUES
(1, 1, 12345),
(1, 2, 23423),
(2, 1, 23236),
(2, 2, 12312)
Then you can use the following query:
SELECT f.FieldName, s.Substance, a.Quantity
FROM dbo.AppliedSubstances a
INNER JOIN dbo.Fields f ON f.FieldID = a.FieldID
INNER JOIN dbo.Substances s ON s.SubstanceID = a.SubstanceID
UNION ALL
SELECT f.FieldName, s.Substance, NULL AS Quantity
FROM dbo.Fields f
CROSS JOIN dbo.Substances s
WHERE NOT EXISTS (
SELECT * FROM dbo.AppliedSubstances a
WHERE a.FieldID=f.FieldID AND a.SubstanceID=s.SubstanceID
)
Or a shorter stranger version (with a different meaning if you have some substances that were applied only for some fields):
SELECT f.FieldName, s.Substance, a.Quantity
FROM dbo.AppliedSubstances a
RIGHT JOIN dbo.Fields f ON f.FieldID = a.FieldID
INNER JOIN dbo.Substances s ON s.SubstanceID = ISNULL(a.SubstanceID,s.SubstanceID)
I'm not sure if I understand your question correctly, but try this:
CREATE TABLE SourceData (
FieldName VARCHAR(50),
Substance VARCHAR(50),
Quantity INT
)
INSERT INTO dbo.SourceData (FieldName, Substance, Quantity) VALUES
('Field 1', 'Substance 1', 12345),
('Field 1', 'Substance 2', 23423),
('Field 2', 'Substance 1', 23236),
('Field 2', 'Substance 2', 12312),
('Field 3', NULL, NULL)
SELECT FieldName, Substance, Quantity
FROM dbo.SourceData WHERE Substance IS NOT NULL
UNION ALL
SELECT s1.FieldName, x.Substance, NULL AS Quantity
FROM dbo.SourceData s1 CROSS JOIN (
SELECT DISTINCT s2.Substance
FROM dbo.SourceData s2
WHERE s2.Substance IS NOT NULL
) x
WHERE s1.Substance IS NULL

T-SQL query, multiple values in a field

I have two tables in a database. The first table tblTracker contains many columns, but the column of particular interest is called siteAdmin and each row in that column can contain multiple loginIDs of 5 digits like 21457, 21456 or just one like 21444. The next table users contains columns like LoginID, fname, and lname.
What I would like to be able to do is take the loginIDs contained in tblTracker.siteAdmin and return fname + lname from users. I can successfully do this when there is only one loginID in the row such as 21444 but I cannot figure out how to do this when there is more than one like 21457, 21456.
Here is the SQL statement I use for when there is one loginID in that column
SELECT b.FName + '' '' + b.LName AS siteAdminName,
FROM tblTracker a
LEFT OUTER JOIN users b ON a.siteAdmin= b.Login_Id
However this doesn't work when it tries to join a siteAdmin with more than one LoginID in it
Thanks!
I prefer the number table approach to split a string in TSQL
For this method to work, you need to do this one time table setup:
SELECT TOP 10000 IDENTITY(int,1,1) AS Number
INTO Numbers
FROM sys.objects s1
CROSS JOIN sys.objects s2
ALTER TABLE Numbers ADD CONSTRAINT PK_Numbers PRIMARY KEY CLUSTERED (Number)
Once the Numbers table is set up, create this split function:
CREATE FUNCTION [dbo].[FN_ListToTable]
(
#SplitOn char(1) --REQUIRED, the character to split the #List string on
,#List varchar(8000)--REQUIRED, the list to split apart
)
RETURNS TABLE
AS
RETURN
(
----------------
--SINGLE QUERY-- --this will not return empty rows
----------------
SELECT
ListValue
FROM (SELECT
LTRIM(RTRIM(SUBSTRING(List2, number+1, CHARINDEX(#SplitOn, List2, number+1)-number - 1))) AS ListValue
FROM (
SELECT #SplitOn + #List + #SplitOn AS List2
) AS dt
INNER JOIN Numbers n ON n.Number < LEN(dt.List2)
WHERE SUBSTRING(List2, number, 1) = #SplitOn
) dt2
WHERE ListValue IS NOT NULL AND ListValue!=''
);
GO
You can now easily split a CSV string into a table and join on it:
select * from dbo.FN_ListToTable(',','1,2,3,,,4,5,6777,,,')
OUTPUT:
ListValue
-----------------------
1
2
3
4
5
6777
(6 row(s) affected)
Your can now use a CROSS APPLY to split every row in your table like:
DECLARE #users table (LoginID int, fname varchar(5), lname varchar(5))
INSERT INTO #users VALUES (1, 'Sam', 'Jones')
INSERT INTO #users VALUES (2, 'Don', 'Smith')
INSERT INTO #users VALUES (3, 'Joe', 'Doe')
INSERT INTO #users VALUES (4, 'Tim', 'White')
INSERT INTO #users VALUES (5, 'Matt', 'Davis')
INSERT INTO #users VALUES (15,'Sue', 'Me')
DECLARE #tblTracker table (RowID int, siteAdmin varchar(50))
INSERT INTO #tblTracker VALUES (1,'1,2,3')
INSERT INTO #tblTracker VALUES (2,'2,3,4')
INSERT INTO #tblTracker VALUES (3,'1,5')
INSERT INTO #tblTracker VALUES (4,'1')
INSERT INTO #tblTracker VALUES (5,'5')
INSERT INTO #tblTracker VALUES (6,'')
INSERT INTO #tblTracker VALUES (7,'8,9,10')
INSERT INTO #tblTracker VALUES (8,'1,15,3,4,5')
SELECT
t.RowID, u.LoginID, u.fname+' '+u.lname AS YourAdmin
FROM #tblTracker t
CROSS APPLY dbo.FN_ListToTable(',',t.siteAdmin) st
LEFT OUTER JOIN #users u ON st.ListValue=u.LoginID --to get all rows even if missing siteAdmin
--INNER JOIN #users u ON st.ListValue=u.LoginID --to remove rows without any siteAdmin
ORDER BY t.RowID,u.fname,u.lname
OUTPUT:
RowID LoginID YourAdmin
----------- ----------- -----------
1 2 Don Smith
1 3 Joe Doe
1 1 Sam Jones
2 2 Don Smith
2 3 Joe Doe
2 4 Tim White
3 5 Matt Davis
3 1 Sam Jones
4 1 Sam Jones
5 5 Matt Davis
7 NULL NULL
7 NULL NULL
7 NULL NULL
8 3 Joe Doe
8 5 Matt Davis
8 1 Sam Jones
8 15 Sue Me
8 4 Tim White
(18 row(s) affected)

Concatenated columns should not match in 2 tables

I'll just put this in layman's terms since I'm a complete noobie:
I have 2 tables A and B, both having 2 columns of interest namely: employee_number and salary.
What I am looking to do is to extract rows of 'combination' of employee_number and salary from A that are NOT present in B, but each of employee_number and salary should be present in both.
I am looking to doing it with the 2 following conditions(please forgive the wrong function
names.. this is just to present the problem 'eloquently'):
1.) A.unique(employee_number) exists in B.unique(employee_number) AND A.unique(salary)
exists in B.unique(salary)
2.) A.concat(employee_number,salary) <> B.concat(employee_number,salary)
Note: A and B are in different databases, so I'm looking to use dblink to do this.
This is what I tried doing:
SELECT distinct * FROM dblink('dbname=test1 port=5432
host=test01 user=user password=password','SELECT employee_number,salary, employee_number||salary AS ENS FROM empsal.A')
AS A(employee_number int8, salary integer, ENS numeric)
LEFT JOIN empsalfull.B B on B.employee_number = A.employee_number AND B.salary = A.salary
WHERE A.ENS not in (select distinct employee_number || salary from empsalfull.B)
but it turned out to be wrong as I had it cross-checked by using spreadsheets and I don't get the same result.
Any help would be greatly appreciated. Thanks.
For easier understanding I left out the dblink.
Because, the first one selects lines in B that equal the employeenumber in A as well as the salery in A, so their concatenated values will equal as well (if you expect this to not be true, please provide some test data).
SELECT * from firsttable A
LEFT JOIN secondtable B where
(A.employee_number = B.employee_number AND a.salery != b.salery) OR
(A.salery = B.salery AND A.employee_number != B.employee_number)
If you have troubles with lines containing nulls, you might also try somthing like this:
AND (a.salery != b.salery OR (a.salery IS NULL AND b.salery IS NOT NULL) or (a.salery IS NOT
NULL and b.salery IS NULL))
I think you're looking for something along these lines.
(Sample data)
create table A (
employee_number integer primary key,
salary integer not null
);
create table B (
employee_number integer primary key,
salary integer not null
);
insert into A values
(1, 20000),
(2, 30000),
(3, 20000); -- This row isn't in B
insert into B values
(1, 20000), -- Combination in A
(2, 20000), -- Individual values in A
(3, 50000); -- Only emp number in A
select A.employee_number, A.salary
from A
where (A.employee_number, A.salary) NOT IN (select employee_number, salary from B)
and A.employee_number IN (select employee_number from B)
and A.salary IN (select salary from B)
output: 3, 20000

Find duplicate row "details" in table

OrderId OrderCode Description
-------------------------------
1 Z123 Stuff
2 ABC999 Things
3 Z123 Stuff
I have duplicates in a table like the above. I'm trying to get a report of which Orders are duplicates, and what Order they are duplicates of, so I can figure out how they got into the database.
So ideally I'd like to get an output something like;
OrderId IsDuplicatedBy
-------------------------
1 3
3 1
I can't work out how to code this in SQL.
You can use the same table twice in one query and join on the fields you need to check against. T1.OrderID <> T2.OrderID is needed to not find a duplicate for the same row.
declare #T table (OrderID int, OrderCode varchar(10), Description varchar(50))
insert into #T values
(1, 'Z123', 'Stuff'),
(2, 'ABC999', 'Things'),
(3, 'Z123', 'Stuff')
select
T1.OrderID,
T2.OrderID as IsDuplicatedBy
from #T as T1
inner join #T as T2
on T1.OrderCode = T2.OrderCode and
T1.Description = T2.Description and
T1.OrderID <> T2.OrderID
Result:
OrderID IsDuplicatedBy
1 3
3 1