Count Values associated with key in Sql Server - sql

I have three tables
Table Category
CategoryID Category
1 Climate
2 Area
Table CategoryDetail
DetailID CategoryID Desc
1 1 Hot
2 1 Cold
3 2 Area1
Table CategoryDetailValues
PK AnotherFK CategoryDetailID
1 1 1
2 1 1
3 1 2
4 2 1
Here AnotherFK is foreign key referring to another table. In record 1 and 2 duplicate exists that's ok but AnotherFK 1 has reference of CategoryDetailID 1 and 2 which has categoryID of 1 which is not ok
So from above tables
this result is valid from above three table
PK AnotherFK CategoryID DetailID Desc
1 1 1 1 Hot
2 1 1 1 Hot
But below result is not valid
PK AnotherFK CategoryID DetailID Desc
2 1 1 1 Hot
3 1 1 2 Cold
I can not put same AnotherFK in two different DetailID which has same CategoryID. I could have eliminated this by introducing CategoryID in CategoryDetailValues table and creating unique constraint but I am not allowed to do so.
Now my aim is to find all those record in CategoryDetailValues table which has different DetailID that are associated with same CategoryID. So that I can delete them.
Trying to achieve this in SQL Server 2012.

If your goal is to highlight all AnotherFK cases that have the same CategoryID, but differenty DetailIDs, the following ought to do the trick (pseudo-code):
SELECT * FROM (SELECT AnotherFK, ROW_NUMBER() OVER
(ORDER BY AnotherFK, CategoryID) AS rn FROM #myTable) AS a
WHERE rn > 1
Sample code:
CREATE TABLE #myTable
(
AnotherFK int
, CategoryID int
, DetailID int
) ;
INSERT INTO #myTable (
AnotherFK
, CategoryID
, DetailID
)
VALUES (1, 1, 1)
, (1, 1, 2);
SELECT * FROM (SELECT AnotherFK, ROW_NUMBER() OVER (ORDER BY AnotherFK, CategoryID) AS rn FROM #myTable) AS a
WHERE rn > 1
DROP TABLE #myTable
If this is not what you are after, please elaborate

I think you could use something like this:
Script to create sample tables:
CREATE TABLE mytable(
PK INTEGER NOT NULL PRIMARY KEY
,AnotherFK INTEGER NOT NULL
,CategoryDetailID INTEGER NOT NULL
);
INSERT INTO mytable(PK,AnotherFK,CategoryDetailID) VALUES (1,1,1);
INSERT INTO mytable(PK,AnotherFK,CategoryDetailID) VALUES (2,1,1);
INSERT INTO mytable(PK,AnotherFK,CategoryDetailID) VALUES (3,1,2);
INSERT INTO mytable(PK,AnotherFK,CategoryDetailID) VALUES (4,2,1);
INSERT INTO mytable(PK,AnotherFK,CategoryDetailID) VALUES (5,1,3);
INSERT INTO mytable(PK,AnotherFK,CategoryDetailID) VALUES (6,1,3);
INSERT INTO mytable(PK,AnotherFK,CategoryDetailID) VALUES (7,1,3);
CREATE TABLE mytable2(
DetailID INTEGER NOT NULL
,CategoryID INTEGER NOT NULL
,Descr VARCHAR(5) NOT NULL
);
Query to show "suspect" record (I think you have to decide what records delete...):
SELECT * FROM (
SELECT * ,COUNT(*) OVER (PARTITION BY CategoryID, ANotherFK) AS X
, COUNT(*) OVER (PARTITION BY CategoryID, DetailID, ANotherFK) AS X1
FROM mytable A
INNER JOIN mytable2 B ON A.CategoryDetailID= B.DetailID
)C
WHERE X-X1 >0
Output:
+--+----+-----------+------------------+----------+------------+-------+---+----+
| | PK | AnotherFK | CategoryDetailID | DetailID | CategoryID | Descr | X | X1 |
+--+----+-----------+------------------+----------+------------+-------+---+----+
| | 1 | 1 | 1 | 1 | 1 | Hot | 3 | 2 |
| | 2 | 1 | 1 | 1 | 1 | Hot | 3 | 2 |
| | 3 | 1 | 2 | 2 | 1 | Cold | 3 | 1 |
+--+----+-----------+------------------+----------+------------+-------+---+----+

This query will look in Categorydetail for records with duplicate DetailID. Than join the tables to provide you the details. It's still up to you to decide which records should be deleted.
select *
from(
Select CategoryID
from CategoryDetail
group by CategoryID
having count(DetailID)>1)aggr
join CategoryDetail c on aggr.CategoryID = c.CategoryID
join CategoryDetailValues v on c.CategoryDetailID = v.CategoryDetailID

You want one value per AnotherFK and Category. So the third table should have a composite key:
CategoryDetailValues(AnotherFK, CategoryID, DetailID, HowMany)
with a unique constraint on AnotherFK, CategoryID and both building a foreign key to CategoryDetail(CategoryID, DetailID).
In order to clean up data first, you'd have to look for ambiguities:
select AnotherFK, CategoryID, DetailID
from
(
select
cdv.AnotherFK, cd.CategoryID, cdv.DetailID,
count(distinct cd.DetailID) over (partition by cdv.AnotherFK, cd.CategoryID) as cnt
from CategoryDetailValues cdv
join CategoryDetail cd on cd.DetailID = cdv.CategoryDetailID
)
where cnt > 1
order by AnotherFK, CategoryID, DetailID

You could try this solution that comes with following assumption: within CDV table, for every [AnotherFK] value (ex. 1) should be displayed only those rows with the minimum [CategoryDetailID] (ex. 1)
SELECT *
FROM (
SELECT cdv.PK, cdv.AnotherFK, cd.CategoryID, cd.[Desc],
Rnk = DENSE_RANK() OVER(PARTITION BY cdv.AnotherFK ORDER BY cdv.CategoryDetailID)
FROM dbo.CategoryDetailValues cdv
JOIN dbo.CategoryDetail cd ON cd.DetailID = cdv.DetailID
WHERE cdv.AnotherFK = 1
) x
WHERE x.Rnk = 1

Related

SQL How to filter table with values having more than one unique value of another column

I have data table Customers that looks like this:
ID | Sequence No |
1 | 1 |
1 | 2 |
1 | 3 |
2 | 1 |
2 | 1 |
2 | 1 |
3 | 1 |
3 | 2 |
I would like to filter the table so that only IDs with more than 1 distinct count of Sequence No remain.
Expected output:
ID | Sequence No |
1 | 1 |
1 | 2 |
1 | 3 |
3 | 1 |
3 | 2 |
I tried
select ID, Sequence No
from Customers
where count(distinct Sequence No) > 1
order by ID
but I'm getting error. How to solve this?
You can get the desired result by using the below query. This is similar to what you were trying -
Sample Table & Data
Declare #Data table
(Id int, [Sequence No] int)
Insert into #Data
values
(1 , 1 ),
(1 , 2 ),
(1 , 3 ),
(2 , 1 ),
(2 , 1 ),
(2 , 1 ),
(3 , 1 ),
(3 , 2 )
Query
Select * from #Data
where ID in(
select ID
from #Data
Group by ID
Having count(distinct [Sequence No]) > 1
)
Using analytic functions, we can try:
WITH cte AS (
SELECT *, MIN([Sequence No]) OVER (PARTITION BY ID) min_seq,
MAX([Sequence No]) OVER (PARTITION BY ID) max_seq
FROM Customers
)
SELECT ID, [Sequence No]
FROM cte
WHERE min_seq <> max_seq
ORDER BY ID, [Sequence No];
Demo
We are checking for a distinct count of sequence number by asserting that the minimum and maximum sequence numbers are not the same for a given ID. The above query could benefit from the following index:
CREATE INDEX idx ON Customers (ID, [Sequence No]);
This would let the min and max values be looked up faster.

INSERT SELECT loop

I am trying to transfer data from one table to another. But in the process I need to do something extra I am just wondering is it possible to do something like this in SQL or PL/SQL alone.
source target
------------------- ------------------------
| id | name | qty | | id | source_id | qty |
------------------- ------------------------
| 1 | test | 2 | | 1 | 1 | 1 |
------------------- ------------------------
| 2 | ago | 1 | | 2 | 1 | 1 |
------------------- ------------------------
| 3 | 2 | 1 |
-----------------------
Here based on the quantity in source table I will have to insert multiple records. Quantity could be of any number. ID in target table is auto incremented. I tried this
INSERT INTO target (SELECT id, qty FROM source);
But this does not take care of the qty loop.
Plain SQL:
with
inputs ( id, qty ) as (
select 1, 2 from dual union all
select 2, 1 from dual union all
select 3, 5 from dual
)
-- end of test data; solution (SQL query) begins below this line
select row_number() over (order by id) as id, id as source_id, 1 as qty
from inputs
connect by level <= qty
and prior id = id
and prior sys_guid() is not null
;
NOTE - if the id is generated automatically, just drop the row_number().... as id column; the rest is unchanged.
ID SOURCE_ID QTY
-- --------- --
1 1 1
2 1 1
3 2 1
4 3 1
5 3 1
6 3 1
7 3 1
8 3 1
This is possible using SQL. Use CTE to generate amount of rows that matches your maximum qty from source table and use non-equi JOIN for generating rows. Use row_number analytic function to assign each row it's unique id (if you have it in your target table, check below on my Edit):
with gen_numbers(r) as (
select rownum r
from dual
connect by rownum <= (select max(qty) from src) -- our maximum limit of rows needed
)
select
row_number() over (order by src.id) as id,
src.id as source_id,
1 as qty
from src
join gen_numbers on src.qty <= gen_numbers.r; -- clone rows qty times
Note that you can safely put in constans value 1 in the output of qty.
Your test data:
create table src (id int, name varchar(255), qty int);
insert into src (id, name, qty)
select 1, 'test', 2 from dual union all
select 2, 'ago', 1 from dual
;
Result:
ID SOURCE_ID QTY
1 2 1
2 2 1
3 1 1
Edit: Since your target id column is auto incremented, you don't need the row_number. Just specify it like that to perform an INSERT:
with gen_numbers(r) as (
select rownum r
from dual
connect by rownum <= (select max(qty) from src) -- our maximum limit of rows needed
)
insert into target_table(source_id, qty)
select
src.id as source_id,
1 as qty
from src
join gen_numbers on src.qty <= gen_numbers.r; -- clone rows qty times
order by src.id
Notice that I've added an ORDER BY clause to ensure proper ordering of inserting values.
INSERT INTO TARGET(source_id, qty)
WITH
output
AS
(
SELECT id, qty FROM source
UNION ALL
SELECT id, qty - 1 FROM source WHERE qty > 1
)
SELECT
id, count(*) as qty
FROM output
group by
id, quantity
ORDER BY
id

SQL Server tough Query

I'm having an issue writing a tough query. I have the following table (as an example)
fusionId | productId | departmentId
1 | 1 | 1
2 | 1 | 2
3 | 2 | 1
4 | 3 | 2
I want a query that I can pass two departmentId's to (1,2) and for the query to return the productId only if both departmentId match the same productId
So for example if I sent the departmentId's 1 & 2 I would get the result
productId
1
Is this possible?
SELECT productId
FROM YourTable
WHERE departmentId IN (1,2)
GROUP BY productId
HAVING COUNT(DISTINCT departmentId) = 2
Or
SELECT productId
FROM YourTable
WHERE departmentId = 1
INTERSECT
SELECT productId
FROM YourTable
WHERE departmentId = 2
Create Table Temporary
(
fusionId int,
productId int,
departmentId int,
)
insert into Temporary values (1,1,1),(2,1,2),(3,2,1),(4,3,2)
Select productId from Temporary
Where productId in (Select productId from Temporary where departmentId = 1) and departmentId =2
SELECT productId
FROM YourTable
WHERE departmentId IN (1,2)
GROUP bY productId
HAVING COUNT(productId) > 1

SQL - Clone a record and its descendants

I would like to be able to clone a record and its descendants in the same table. An example of my table would be the following:
Table1
id | parentid | name
---------------------
1 | 0 | 'Food'
2 | 1 | 'Taste'
3 | 1 | 'Price'
4 | 2 | 'Taste Requirements'
The "id" column is the primary key and auto-increments. The 'Food' record (i.e. where id = 1) has two records underneath it called 'Taste' and 'Price'. The 'Taste' record has a record underneath it called 'Taste Requirements'. I would like to be able to clone the 'Food' record so that Table1 would look like the following:
Table1
id | parentid | name
---------------------
1 | 0 | 'Food'
2 | 1 | 'Taste'
3 | 1 | 'Price'
4 | 2 | 'Taste Requirements'
5 | 0 | 'Cookies'
6 | 5 | 'Taste'
7 | 5 | 'Price'
8 | 6 | 'Taste Requirements'
(where 'Cookies' is the name of the new category that I want to create). I am able to select all the descendants of 'Food' using:
with Table1_CTE( id, parentid, name )
as
(
select t.id, t.parentid, t.name from Table1 t
where t.id = 1
union all
select t.id, t.parentid,t. name from Table1 t
inner join Table1_CTE as tc
on t.parentid = tc.id
)
select id, parentid, name from Table1_CTE
and I am able to clone just the 'Food' record (i.e. where id = 1) using:
insert into Table1 ( parentid, name )
select ( parentid, 'Cookies' )
from Table1 where id = 1
but I am having problems trying to combine the two queries to clone the descendants of 'Food'. Also, I am trying to avoid using stored procedures, triggers, curosrs, etc. Is what I am trying to do possible? I have seen some examples on the web but have been unable to apply them to my requirements.
As Martin suggested, you need to enable IDENTITY_INSERT so that you can push your own identity values. You may also need to acquire a table lock to ensure that Max( Id ) returns the correct value.
If object_id('tempdb..#TestData') is not null
Drop Table #TestData
GO
Create Table #TestData
(
Id int not null identity(1,1) Primary Key
, ParentId int not null
, Name varchar(50) not null
)
GO
Set Identity_Insert #TestData On
GO
Insert #TestData( Id, ParentId, Name )
Values( 1,0,'Food' )
, ( 2,1,'Taste' )
, ( 3,1,'Price' )
, ( 4,2,'Taste Requirement' );
With Data As
(
Select Cast(MaxId.Id + 1 As int) As Id
, T.ParentId
, 'Copy Of ' + T.name As Name
, T.Id As OldId
, 0 As OldParentId
From #TestData As T
Cross Join( Select Max( id ) As Id From #TestData ) As MaxId
Where T.Name = 'Food'
Union All
Select Cast(Parent.id + Row_Number() Over( Order By Child.Id ) + 1 As int)
, Parent.Id
, 'Copy of ' + Child.Name
, Child.Id
, Child.ParentId
From Data As Parent
Join #TestData As Child
On Child.ParentId = Parent.OldId
)
Insert #TestData( Id, ParentId, Name )
Select Id, ParentId, Name
From Data
GO
Set Identity_Insert #TestData Off
GO
Results
id | parentid | name
-- | -------- | -----------------
1 | 0 | Food
2 | 1 | Taste
3 | 1 | Price
4 | 2 | Taste Requirement
5 | 0 | Copy Of Food
7 | 5 | Copy of Taste
8 | 5 | Copy of Price
9 | 7 | Copy of Taste Requirement
Assuming your CTE picks a root record and all it's descendents (it didn't seem to when I reproduced using your data above), then you can clone all selected records and insert like this:
with Table1_CTE( id, parentid, name )
as
(
select t.id, t.parentid, t.name from Table1 t
where c.icategoryid = 1
union all
select t.id, t.parentid,t. name from Table1
inner join Table1_CTE as tc
on t.parentid = tc.id
)
insert into dbo.testinsertheirarchy ( parentid, name )
select parentid, name from Table1_CTE

Getting the Next Available Row

How can I get a List all the JobPositionNames having the lowest jobPositionId when ContactId = 1
Tablel :
| JobPositionId | JobPositionName | JobDescriptionId | JobCategoryId | ContactId
---------------------------------------------------------------------------------
1 | Audio Cables | 1 | 1 | 1
2 |Audio Connections| 2 | 1 | 1
3 |Audio Connections| 2 | 1 | 0
4 |Audio Connections| 2 | 1 | 0
5 | Sound Board | 3 | 1 | 0
6 | Tent Pen | 4 | 3 | 0
eg the result of this table should be lines 1,3,5,6
I can't figure out the solution.
Only lack of something, but I can give some code for you view.
Maybe it can help you.
--create table
create table t
(
JobPositionId int identity(1,1) primary key,
JobPositionName nvarchar(100) not null,
JobDescriptionId int,
JobCategoryId int,
ContactId int
)
go
--insert values
BEGIN TRAN
INSERT INTO t VALUES ('AudioCables', 1,1,1)
INSERT INTO t VALUES ('AudioConnections',2,1,1)
INSERT INTO t VALUES ('AudioConnections',2,1,0)
INSERT INTO t VALUES ('AudioConnections',2,1,0)
INSERT INTO t VALUES ('SoundBoard',3,1,0)
INSERT INTO t VALUES ('TentPen',4,3,0)
COMMIT TRAN
GO
SELECT
Min(JobPositionId) AS JobPositionId, JobPositionName, ContactId
INTO
#tempTable
FROM
t
GROUP BY JobPositionName, ContactId
SELECT * FROM #tempTable
WHERE JobPositionId IN (
SELECT JobPositionId
FROM #tempTable
GROUP BY JobPositionName
--... lack of sth, I can't figure out ,sorry.
)
drop table t
GO
For per-group maximum/minimum queries you can use a null-self-join as well as strategies like subselects. This is generally faster in MySQL.
SELECT j0.JobPositionId, j0.JobPositionName, j0.ContactId
FROM Jobs AS j0
LEFT JOIN Jobs AS j1 ON j1.JobPositionName=j0.JobPositionName
AND (
(j1.ContactId<>0)<(j0.ContactId<>0)
OR ((j1.ContactId<>0)=(j0.ContactId<>0) AND j1.JobPositionId<j0.JobPositionId))
)
WHERE j1.JobPositionName IS NULL
This says, for each JobPositionName, find a row for which there exists no other row with a lower ordering value. The ordering value here is a composite [ContactId-non-zeroness, JobPositionId].
(Aside: shouldn't JobPositionName and JobCategoryId be normalised out into a table keyed on JobDescriptionId? And shouldn't unassigned ContactIds be NULL?)
SELECT jp.*
FROM (
SELECT JobPositionName, JobPositionId, COUNT(*) AS cnt
FROM JobPosisions
) jpd
JOIN JobPosisions jp
ON jp.JobPositionId =
IF(
cnt = 1,
jpd.JobPositionId,
(
SELECT MIN(JobPositionId)
FROM JobPositions jpi
WHERE jpi.JobPositionName = jpd.JobPositionName
AND jpi.ContactID = 0
)
)
Create an index on (JobPositionName, ContactId, JobPositionId) for this to work fast.
Note that if will not return the jobs having more than one position, neither of which has ContactID = 0