Count of empty values in string array in PostgreSQL - sql

I want to check Projects column values have the same values for all the same values of PartNo and PartName columns. Projects column data type is character variyng[].
For example:
PartNo
PartName
Projects
1
3
6;5
1
3
1
3
3
2
5;5
In this case, Projects have different values (6;5) and () for the same PartName(3) and PartNo(1).
This is my query, but it does not work with empty character variyng[] in projects column!
SELECT COUNT(*) from (
select c.partno, c.partname
FROM unnest(items) as c
GROUP BY c.partno, c.partname
HAVING COUNT(distinct c.projects) > 1) as xxx
INTO errCount;
IF errCount > 0 THEN
RETURN QUERY
SELECT 0 as status, format('Projects value should be the same for all Codes of the Part No %s and Name %s',c.partno,c.partname) as message
FROM unnest(items) as c
GROUP BY c.partno, c.partname
HAVING COUNT(distinct c.projects) > 1
;
RETURN;
END IF;
In the case of two different values in projects (not empty array), it works.

you can use a query like this with
coalesce
function to convert null in array[null]
WITH tt AS (
SELECT
partno,
partname,
COALESCE ( project, ARRAY [null] ) AS pro
FROM
tab1
) SELECT
*,
COUNT ( pro ) AS num
FROM
tt
GROUP BY
partno,
partname,
pro
to create test table:
CREATE TABLE "tab1" (
"pk" serial4 primary key,
"partno" int4,
"partname" int4,
"project" varchar[]
);
INSERT INTO "tab1" (partno,partname,project) VALUES ( 1, 3, '{6,5}');
INSERT INTO "tab1" (partno,partname,project) VALUES ( 1, 3, NULL);
INSERT INTO "tab1" (partno,partname,project) VALUES ( 1, 3, NULL);
INSERT INTO "tab1" (partno,partname,project) VALUES ( 3, 2, '{5,5}');

Related

Insert Missing Values Into Table Using SQL

Objective: I need to fully populate a table with a matrix of values for each column by [PropertyId] grouping. Several [PropertyId] have all the necessary values for each column (Table 1), however, many are missing some values (Table 2). Furthermore, not every [PropertyId] needs these values as they have completely different regional values. Therefore, I need to identify which [PropertyId] both need the values populated and don't have all the necessary values.
Examples:
Table 1. Each identified [PropertyId] grouping should have 23 distinct records for these four columns [ReportingVolumeSettingId],[SpeciesGroupInventoryID],[CropCategoryID],[SortOrder].
Table 2. Here is an example of a PropertyID that is missing a value combination as it only has 22 records:
Both of these example results were queried from the same table [ReportingVolume]. I have not been successful in even identifying which record combination per [PropertyID] are missing. I would like to identify each missing record combination and then insert that record combination into the [ReportingVolume] table.
Problem to Solve -- The SQL Code below is my attempt to 1. Identify the correct List of Values; 2. Identify which properties should have matching values; 3. Identify which properties are missing values; 4. Identify the missing values per property.
;with CORRECT_LIST as
(
select
SpeciesGroupInventoryName, SpeciesGroupInventoryId, CropCategoryName,CropCategoryID, UnitOfMeasure, SortOrder
--*
from [GIS].[RST].[vPropertyDefaultTimberProductAndUnitOfMeasure]
where PropertyId in (1)
)
,
property_list as
(
select distinct rvs.propertyid as Volume_Property, pd.PropertyName, pd.PropertyId from RMS.GIS.ReportingVolumeSetting rvs
right outer JOIN RMS.GIS.PropertyDetail AS pd ON rvs.PropertyId = pd.PropertyId
left outer JOIN RMS.GIS.SpeciesGroupInventory AS sgi ON rvs.SpeciesGroupInventoryId = sgi.SpeciesGroupInventoryId
where sgi.SpeciesGroupInventoryId in (1,2,3)
or pd.PropertyId = 171
)
, Partial_LISTS as
(
select Count(distinct ReportingVolumeSettingId) as CNT_REPORT, pd.PropertyName, pd.PropertyId
from [GIS].[ReportingVolumeSetting] rvs
right outer JOIN property_list AS pd ON rvs.PropertyId = pd.PropertyId
group by pd.propertyId, pd.PropertyName
)
, Add_Props as
(
select propertyName, propertyId, SUM(CNT_REPORT) as CNT_RECORDS from Partial_LISTS
where CNT_REPORT < 23
group by propertyName, propertyId
)
, RVS_RECORDS_PROPS as
(
select addProps.PropertyName, rvs.* from [GIS].[ReportingVolumeSetting] rvs
join Add_Props addProps on addprops.PropertyId = rvs.PropertyID
where rvs.PropertyId in (select PropertyId from Add_Props)
)
select rp.PropertyName, cl.*, rp.SpeciesGroupInventoryId from correct_list cl
left outer join RVS_Records_Props rp
on rp.SpeciesGroupInventoryId = cl.SpeciesGroupInventoryId
and rp.CropCategoryId = cl.CropCategoryID
and rp.SortOrder = cl.SortOrder
Order by rp.PropertyName
How can I modify the code or create a new code block identifies the missing values and inserts them into the table per PropertyId?
I am using SQL SMSS v15.
Thanks so much.
This should identify missing entries. You could simply add an INSERT INTO command on top of this. Keep in mind as the ReportingVolumeSettingId is unique and unknown it's not covered here.
SELECT * FROM (SELECT DISTINCT PropertyId FROM ReportingVolume) rv
CROSS APPLY
(
SELECT DISTINCT SpeciesGroupInventoryId
, CropCategoryId
, SortOrder
FROM ReportingVolume
) x
EXCEPT
SELECT PropertyId, SpeciesGroupInventoryId, CropCategoryId, SortOrder FROM ReportingVolume
I don't have access to your data, so I cannot provide an example specific to your environment, but I can provide you a simple example using SQL Server's EXCEPT operator.
Run the following example in SSMS:
-- Create a list of required values.
DECLARE #Required TABLE ( required_id INT, required_val VARCHAR(50) );
INSERT INTO #Required ( required_id, required_val ) VALUES
( 1, 'Required value 1.' ), ( 2, 'Required value 2.' ), ( 3, 'Required value 3.' ), ( 4, 'Required value 4.' ), ( 5, 'Required value 5.' );
-- Create some sample data to compare against.
DECLARE #Data TABLE ( PropertyId INT, RequiredId INT );
INSERT INTO #Data ( PropertyId, RequiredId ) VALUES
( 1, 1 ), ( 1, 2 ), ( 1, 3 ), ( 2, 1 ), ( 2, 2 ), ( 2, 4 ), ( 2, 5 );
-- Set a property id value to query.
DECLARE #PropertyId INT = 1;
-- Preview #Data's rows for the specified #PropertyId.
SELECT * FROM #Data WHERE PropertyId = #PropertyId ORDER BY PropertyId, RequiredId;
At this point, I've created a list of required values (required_id 1 through 5) and some dummy data to check them against. This initial SELECT shows the current resultset for the specified #PropertyID:
+------------+------------+
| PropertyId | RequiredId |
+------------+------------+
| 1 | 1 |
| 1 | 2 |
| 1 | 3 |
+------------+------------+
You can see that required_id values 4 and 5 are missing for the current property. Next, we can compare #Required against #Data and INSERT any missing required values using the EXCEPT operator and then return the corrected resultset.
-- Insert any missing required values for #PropertyId.
INSERT INTO #Data ( PropertyId, RequiredId )
SELECT #PropertyId, required_id FROM #Required
EXCEPT
SELECT PropertyId, RequiredId FROM #Data WHERE PropertyId = #PropertyId;
-- Preview #Data's revised rows for #PropertyId.
SELECT * FROM #Data WHERE PropertyId = #PropertyId ORDER BY PropertyId, RequiredId;
The updated resultset now looks like the following:
+------------+------------+
| PropertyId | RequiredId |
+------------+------------+
| 1 | 1 |
| 1 | 2 |
| 1 | 3 |
| 1 | 4 |
| 1 | 5 |
+------------+------------+
You can run this again against #PropertyId = 2 to see a different scenario.
Note the order to using EXCEPT. The required rows comes first, followed by the EXCEPT operator, and then the current rows to be validated. This is important. EXCEPT is saying show me rows from #Required that are not in #Data--which allows for the inserting of any missing required values into #Data.
I know this example doesn't represent your existing data with the 23 rows requirement, but hopefully, it will get you moving with a solution for your needs. You can read more about the EXCEPT operator here.
Here is the complete SSMS example:
-- Create a list of required values.
DECLARE #Required TABLE ( required_id INT, required_val VARCHAR(50) );
INSERT INTO #Required ( required_id, required_val ) VALUES
( 1, 'Required value 1.' ), ( 2, 'Required value 2.' ), ( 3, 'Required value 3.' ), ( 4, 'Required value 4.' ), ( 5, 'Required value 5.' );
-- Create some sample data to compare against.
DECLARE #Data TABLE ( PropertyId INT, RequiredId INT );
INSERT INTO #Data ( PropertyId, RequiredId ) VALUES
( 1, 1 ), ( 1, 2 ), ( 1, 3 ), ( 2, 1 ), ( 2, 2 ), ( 2, 4 ), ( 2, 5 );
-- Set a property id value to query.
DECLARE #PropertyId INT = 1;
-- Preview #Data's rows for the specified #PropertyId.
SELECT * FROM #Data WHERE PropertyId = #PropertyId ORDER BY PropertyId, RequiredId;
-- Insert any missing required values for #PropertyId.
INSERT INTO #Data ( PropertyId, RequiredId )
SELECT #PropertyId, required_id FROM #Required
EXCEPT
SELECT PropertyId, RequiredId FROM #Data WHERE PropertyId = #PropertyId;
-- Preview #Data's revised rows for #PropertyId.
SELECT * FROM #Data WHERE PropertyId = #PropertyId ORDER BY PropertyId, RequiredId;

How to secure table for avoid duplicate data

I cant resolve the problem how secure my table to avoid duplicate combination of attributes_positions. The best way to show you what I mean is the following image
column id_combination represents number of combination. Combination consists of attributes_positions. So Combination is sequence of attributes_positions.
And now I would secure table from insert exaclty the same sequence of attributes_positions.
Of course if already inserted combination contains one additional attributes_positions or one less than inserting combination is ok
image I show the different bettwen duplicate and not duplicate combination.
Is there a some way how I can do that?? Meaby something like 'before update'. But how to implement for this example. I`m not so pretty good with advanced sql.
The database where I trying to secure table is postgresql 9.4
I will be grateful for help
-- The data
CREATE TABLE theset (
set_id INTEGER NOT NULL PRIMARY KEY
, set_name text UNIQUE
);
INSERT INTO theset(set_id, set_name) VALUES
( 1, 'one'), ( 2, 'two'), ( 3, 'three'), ( 4, 'four');
CREATE TABLE theitem (
item_id integer NOT NULL PRIMARY KEY
, item_name text UNIQUE
);
INSERT INTO theitem(item_id, item_name) VALUES
( 1, 'one'), ( 2, 'two'), ( 3, 'three'), ( 4, 'four'), ( 5, 'five');
CREATE TABLE set_item (
set_id integer NOT NULL REFERENCES theset (set_id)
, item_id integer NOT NULL REFERENCES theitem(item_id)
, PRIMARY KEY (set_id,item_id)
);
-- swapped index is indicated for junction tables
CREATE UNIQUE INDEX ON set_item(item_id, set_id);
INSERT INTO set_item(set_id,item_id) VALUES
(1,1), (1,2), (1,3), (1,4),
(2,1), (2,2), (2,3), -- (2,4),
(3,1), (3,2), (3,3), (3,4), (3,5),
(4,1), (4,2), (4,4);
CREATE FUNCTION set_item_unique_set( ) RETURNS TRIGGER AS
$func$
BEGIN
IF EXISTS ( -- other set
SELECT * FROM theset oth
-- WHERE oth.set_id <> NEW.set_id -- only for insert/update
WHERE TG_OP = 'DELETE' AND oth.set_id <> OLD.set_id
OR TG_OP <> 'DELETE' AND oth.set_id <> NEW.set_id
-- count (common) members in the two sets
-- items not in common will have count=1
AND NOT EXISTS (
SELECT item_id FROM set_item x1
WHERE (x1.set_id = NEW.set_id OR x1.set_id = oth.set_id )
GROUP BY item_id
HAVING COUNT(*) = 1
)
) THEN
RAISE EXCEPTION 'Not unique set';
RETURN NULL;
ELSE
RETURN NEW;
END IF;
END;
$func$ LANGUAGE 'plpgsql'
;
CREATE CONSTRAINT TRIGGER check_item_set_unique
AFTER UPDATE OR INSERT OR DELETE
-- BEFORE UPDATE OR INSERT
ON set_item
FOR EACH ROW
EXECUTE PROCEDURE set_item_unique_set()
;
-- Test it
INSERT INTO set_item(set_id,item_id) VALUES(4,5); -- success
INSERT INTO set_item(set_id,item_id) VALUES(2,4); -- failure
DELETE FROM set_item WHERE set_id=1 AND item_id= 4; -- failure
Note: There should also be a trigger for the DELETE case.
UPDATE: added handling of DELETE
(the handling of deletes is not perfect; imagine the case where the last element from a set is removed)
My answer assumes that the target is without dupes, and that we want to insert a new set - which happens to be a duplicate. I choose the group of 4 with the id_comb of 1.
You would have to put the group of 4 into a staging table. Then, you have to pivot both staging and target horizontally - so that you get 5 columns named attr_pos1 to attr_pos5 (the biggest group in your example is 5). To pivot, you need a sequence number, which we get by using ROW_NUMBER(). That's for both tables, staging and target. Then, you pivot both. Then, you try to join pivoted staging and target on all 5 attr_pos# columns, and count the rows. If you get 0, you have no duplicates. If you get 1, you have duplicates.
Here's the whole scenario:
WITH
-- input section: a) target table, no dupes
target(id_comb,attr_pos) AS (
SELECT 2,1
UNION ALL SELECT 2,2
UNION ALL SELECT 2,3
UNION ALL SELECT 2,4
UNION ALL SELECT 3,1
UNION ALL SELECT 3,2\
UNION ALL SELECT 3,3
UNION ALL SELECT 3,4
UNION ALL SELECT 3,5
UNION ALL SELECT 4,1
UNION ALL SELECT 4,2
UNION ALL SELECT 4,3
)
,
-- input section: b) staging, input, would be a dupe
staging(id_comb,attr_pos) AS (
SELECT 1,1
UNION ALL SELECT 1,2
UNION ALL SELECT 1,3
UNION ALL SELECT 1,4
)
,
-- query section:
-- add sequence numbers to stage and target
target_s AS (
SELECT
ROW_NUMBER() OVER(PARTITION BY id_comb ORDER BY attr_pos) AS seq
, *
FROM target
)
,
staging_s AS (
SELECT
ROW_NUMBER() OVER(PARTITION BY id_comb ORDER BY attr_pos) AS seq
, *
FROM staging
)
,
-- horizontally pivot target, NULLS as -1 for later join
target_h AS (
SELECT
id_comb
, IFNULL(MAX(CASE seq WHEN 1 THEN attr_pos END),-1) AS attr_pos1
, IFNULL(MAX(CASE seq WHEN 2 THEN attr_pos END),-1) AS attr_pos2
, IFNULL(MAX(CASE seq WHEN 3 THEN attr_pos END),-1) AS attr_pos3
, IFNULL(MAX(CASE seq WHEN 4 THEN attr_pos END),-1) AS attr_pos4
, IFNULL(MAX(CASE seq WHEN 5 THEN attr_pos END),-1) AS attr_pos5
FROM target_s
GROUP BY id_comb ORDER BY id_comb
)
,
-- horizontally pivot staging, NULLS as -1 for later join
staging_h AS (
SELECT
id_comb
, IFNULL(MAX(CASE seq WHEN 1 THEN attr_pos END),-1) AS attr_pos1
, IFNULL(MAX(CASE seq WHEN 2 THEN attr_pos END),-1) AS attr_pos2
, IFNULL(MAX(CASE seq WHEN 3 THEN attr_pos END),-1) AS attr_pos3
, IFNULL(MAX(CASE seq WHEN 4 THEN attr_pos END),-1) AS attr_pos4
, IFNULL(MAX(CASE seq WHEN 5 THEN attr_pos END),-1) AS attr_pos5
FROM staging_s
GROUP BY id_comb ORDER BY id_comb
)
SELECT
COUNT(*)
FROM target_h
JOIN staging_h USING (
attr_pos1
, attr_pos2
, attr_pos3
, attr_pos4
, attr_pos5
);
Hope this helps ----
Marco
Interesting but not very useful solution by #wildplasser. I create script to insert sample data:
WITH param AS (
SELECT 8 AS max
), maxarray AS (
SELECT array_agg(i) as ma FROM (SELECT generate_series(1, max) as i FROM param) as i
), pre AS (
SELECT
*
FROM (
SELECT
*, CASE WHEN (id >> mbit) & 1 = 1 THEN ma[mbit + 1] END AS item_id
FROM (
SELECT *,
generate_series(0, array_upper(ma, 1) - 1) as mbit
FROM (
SELECT *,
generate_series(1,(2^max - 1)::int8) AS id
FROM param, maxarray
) AS pre1
) AS pre2
) AS pre3
WHERE item_id IS NOT NULL
), ins_item AS (
INSERT INTO theitem (item_id, item_name) SELECT i, i::text FROM generate_series(1, (SELECT max FROM param)) as i RETURNING *
), ins_set AS (
INSERT INTO theset (set_id, set_name)
SELECT id, id::text FROM generate_series(1, (SELECT 2^max - 1 FROM param)::int8) as id
RETURNING *
), ins_set_item AS (
INSERT INTO set_item (set_id, item_id)
SELECT id, item_id FROM pre WHERE (SELECT count(*) FROM ins_item) > 0 AND (SELECT count(*) FROM ins_set) > 0
RETURNING *
)
SELECT
'sets', count(*)
FROM ins_set
UNION ALL
SELECT
'items', count(*)
FROM ins_item
UNION ALL
SELECT
'sets_items', count(*)
FROM ins_set_item
;
When I call it with 8 (1024 - 2^8 rows for set_item) it run 21 seconds. It is very bad. When I off trigger it took less then 1 milliseconds.
My proposal
It is very interesting to use arrays in this case. Unfortunatelly PostgreSQL does not support foreighn key for arrays, but it may be done by TRIGGERs. I remove set_item table and add items int[] field for theset:
-- The data
CREATE TABLE theitem (
item_id integer NOT NULL PRIMARY KEY
, item_name text UNIQUE
);
CREATE TABLE theset (
set_id INTEGER NOT NULL PRIMARY KEY
, set_name text UNIQUE
, items integer[] UNIQUE NOT NULL
);
CREATE INDEX i1 ON theset USING gin (items);
CREATE OR REPLACE FUNCTION check_item_CU() RETURNS TRIGGER AS $sql$
BEGIN
IF (SELECT count(*) > 0 FROM unnest(NEW.items) AS u LEFT JOIN theitem ON (item_id = u) WHERE item_id IS NULL) THEN
RETURN NULL;
END IF;
NEW.items = ARRAY(SELECT unnest(NEW.items) ORDER BY 1);
RETURN NEW;
END;
$sql$ LANGUAGE plpgsql;
CREATE TRIGGER check_item_CU BEFORE INSERT OR UPDATE ON theset FOR EACH ROW EXECUTE PROCEDURE check_item_CU();
CREATE OR REPLACE FUNCTION check_item_UD() RETURNS TRIGGER AS $sql$
BEGIN
IF (TG_OP = 'DELETE' OR TG_OP = 'UPDATE' AND NEW.item_id != OLD.item_id) AND (SELECT count(*) > 0 FROM theset WHERE OLD.item_id = ANY(items)) THEN
RAISE EXCEPTION 'item_id % still used', OLD.item_id;
RETURN NULL;
END IF;
RETURN NEW;
END;
$sql$ LANGUAGE plpgsql;
CREATE TRIGGER check_item_UD BEFORE DELETE OR UPDATE ON theitem FOR EACH ROW EXECUTE PROCEDURE check_item_UD();
WITH param AS (
SELECT 10 AS max
), maxarray AS (
SELECT array_agg(i) as ma FROM (SELECT generate_series(1, max) as i FROM param) as i
), pre AS (
SELECT
*
FROM (
SELECT
*, CASE WHEN (id >> mbit) & 1 = 1 THEN ma[mbit + 1] END AS item_id
FROM (
SELECT *,
generate_series(0, array_upper(ma, 1) - 1) as mbit
FROM (
SELECT *,
generate_series(1,(2^max - 1)::int8) AS id
FROM param, maxarray
) AS pre1
) AS pre2
) AS pre3
WHERE item_id IS NOT NULL
), pre_arr AS (
SELECT id, array_agg(item_id) AS items
FROM pre
GROUP BY 1
), ins_item AS (
INSERT INTO theitem (item_id, item_name) SELECT i, i::text FROM generate_series(1, (SELECT max FROM param)) as i RETURNING *
), ins_set AS (
INSERT INTO theset (set_id, set_name, items)
SELECT id, id::text, items FROM pre_arr WHERE (SELECT count(*) FROM ins_item) > 0
RETURNING *
)
SELECT
'sets', count(*)
FROM ins_set
UNION ALL
SELECT
'items', count(*)
FROM ins_item
;
This variant run less than 1ms

How to return only numbers from query where column is nvarchar

I have a simple query that is returning records where "column2" > 0
Here is the data in the database
Column1 Column2
1 123456789
2 123456781
3 13-151-1513
4 alsdjf
5
6 000000000
Her is the query
select column1, replace(a.Payroll_id,'-','')
from table1
where isnumeric(column2) = 1
I'd like to return the following:
Column1 Column2
1 123456789
2 123456781
3 131511513
This mean, I won't select any records when the column is blank (or null), will not return a row if it's not an integer, and will drop out the '-', and would not show row 6 since it's all 0.
How can I do this?
I think you can use something like this :
USE tempdb
GO
CREATE TABLE #Temp
(
ID INT IDENTITY
,VALUE VARCHAR(30)
)
INSERT INTO #Temp (VALUE) VALUES ('1213213'), ('1213213'), ('121-32-13'), ('ASDFASF2123')
GO
WITH CteData
AS
(
SELECT REPLACE(VALUE,'-','') as Valor FROM #Temp
)
SELECT * FROM CteData WHERE (ISNUMERIC(Valor) = 1 AND valor not like '%[0-0]%')
DROP TABLE #Temp
then you can apply validations for empty, NULL,0 etc
If you are using SQL2012 or above you can also use TRY_PARSE that is more selective in its parsing. This function will return NULL if a record can't be converted. You could use it like this:
CREATE TABLE #temp
(
ID INT IDENTITY ,
VALUE VARCHAR(30)
)
INSERT INTO #temp
( VALUE )
VALUES ( '1213213' ),
( '1213213' ),
( '121-32-13' ),
( 'ASDFASF2123' ),
( '0000000' )
SELECT ParsedValue
FROM #temp
CROSS APPLY ( SELECT TRY_PARSE(
Value AS INT ) AS ParsedValue
) details
WHERE ParsedValue IS NOT NULL
AND ParsedValue>0

Homogenous Containers with GROUP BY Part II

I have three tables, one of which I need to update:
CREATE TABLE Containers (
ID int PRIMARY KEY,
FruitType int
)
CREATE TABLE Contents (
ContainerID int,
ContainedFruit int
)
CREATE TABLE Fruit (
FruitID int,
Name VARCHAR(16)
)
INSERT INTO Fruit VALUES ( 0, 'Mixed' )
INSERT INTO Fruit VALUES ( 1, 'Apple' )
INSERT INTO Fruit VALUES ( 2, 'Banana' )
INSERT INTO Fruit VALUES ( 3, 'Cherry' )
INSERT INTO Fruit VALUES ( 4, 'Date' )
INSERT INTO Containers VALUES ( 101, 0 )
INSERT INTO Containers VALUES ( 102, 0 )
INSERT INTO Containers VALUES ( 103, 0 )
INSERT INTO Containers VALUES ( 104, 3 )
INSERT INTO Contents VALUES ( 101, 1 )
INSERT INTO Contents VALUES ( 101, 1 )
INSERT INTO Contents VALUES ( 102, 1 )
INSERT INTO Contents VALUES ( 102, 2 )
INSERT INTO Contents VALUES ( 102, 3 )
INSERT INTO Contents VALUES ( 103, 3 )
INSERT INTO Contents VALUES ( 103, 4 )
INSERT INTO Contents VALUES ( 104, 3 )
Let's assume this is the state of my database. Please note the Fruit table is used twice. Once to describe the contents of the container, and once to describe if the container is meant to contain only one type of fruit or if it can be mixed. Bad design IMO, but too late to change it.
The problem is that container 101 is incorrectly marked as MIXED when it should really be APPLE. Containers with multiple contents of the same type are still homogenous containers and should be marked as such.
I know how to do a query that finds the containers that are incorrectly marked as mixed:
SELECT Contents.ContainerID
FROM Contents
INNER JOIN Containers ON
Contents.ContainerID = Containers.ID AND
Containers.FruitType = 0
GROUP BY Contents.ContainerID
HAVING COUNT( DISTINCT Contents.ContainedFruit ) = 1
However, I don't know how to update every row in Container where this error has been made. That's my question.
This will do:
UPDATE Container
SET Container.FruitType = Proper.ProperType
FROM Container
INNER JOIN
(
SELECT Contents.ContainerID,
MAX(Contents.ContainedFruit) ProperType
FROM Contents
INNER JOIN Container ON
Contents.ContainerID = Container.ID AND
Container.FruitType = 0
GROUP BY Contents.ContainerID
HAVING COUNT( DISTINCT Contents.ContainedFruit ) = 1
) Proper
ON Container.ID = Proper.ContainerID
SQL Fiddle

How to increment a second primary key column in a table automatically when a new entry is added for the first primary key column

I am trying to find a way to increment a second primary key column in a table automatically when a new entry is added for the first primary key column. I suppose an example would be best here so here goes.
Suppose I have a table:
CREATE TABLE T
(
SecNum INT NOT NULL,
EntryID INT NOT NULL,
Value FLOAT,
) CONSTRAINT [PK_T] PRIMARY KEY CLUSTERED
(
[SecNum] ASC,
[EntryID] ASC
)
I would run the following statement:
INSERT INTO T (SecNum, Value) VALUES (0, 10)
My table should look like:
SECNUM | ENTRYID | VALUE
-------------------------
0 0 10
I would run the following statement:
INSERT INTO T (SecNum, Value) VALUES (0, 10)
My table should look like:
SECNUM | ENTRYID | VALUE
-------------------------
0 0 10
0 1 10
I would run the following statement:
INSERT INTO T (SecNum, Value) VALUES (1, 20)
My table should look like:
SECNUM | ENTRYID | VALUE
-------------------------
0 0 10
0 1 10
1 0 20
This is possible using an INSTEAD OF trigger:
CREATE TRIGGER TriggerName
ON T
INSTEAD OF INSERT
AS
-- THIS TOP BIT IS OPTIONAL, IT WILL ALLOW ENTRY ID TO BE OVERRIDDEN IF
-- IT IS SUPPLIED TO THE INSERT AND WILL NOT VIOLATE THE PRIMARY KEY
IF NOT EXISTS
( SELECT 1
FROM T
INNER JOIN inserted i
ON i.SecNum = T.secNum
AND i.EntryID = T.EntryID
UNION
SELECT 1
FROM inserted
WHERE EntryID IS NULL
)
BEGIN
INSERT T (SecNum, EntryID, Value)
SELECT SecNum, EntryID, Value
FROM inserted
END
ELSE
-- IF OVERRIDE ABILITY IS NOT REQUIRED JUST USE THE BELOW INSERT
BEGIN
INSERT T (SecNum, EntryID, Value)
SELECT i.SecNum, COALESCE(LastID, 0), i.Value
FROM inserted I
LEFT JOIN
( SELECT SecNum, MAX(T.EntryID) + 1 [LastID]
FROM T
GROUP BY SecNum
) T
ON T.SecNum = i.SecNum
END
Example here
HOWEVER this is not very elegant. It could be worth asking is it really necessary? Could you get away with using a surrogate primary key, and use ROW_NUMBER() to create Entry ID's on the fly?
How about something like this:
INSERT INTO T (SecNum, Value, EntryId)
SELECT 0, 10, count(*)
FROM T WHERE SecNum = 0
It is not the cleanest solution and will perform pretty poorly too. But it should get the job done.
This is how to do it without storing the value in the table (I'm not sure why you want to store it)
TABLE
DECLARE #T TABLE
(
SecNum INT NOT NULL,
EntryID INT,
Value FLOAT
)
DATA
INSERT INTO #T
( SecNum, Value )
VALUES ( 0, 10 )
INSERT INTO #T
( SecNum, Value )
VALUES ( 0, 10 )
INSERT INTO #T
( SecNum, Value )
VALUES ( 1, 20 )
QUERY
SELECT SecNum,
ROW_NUMBER() OVER ( PARTITION BY value ORDER BY Value ) - 1 AS EntryID,
Value
FROM #T
RESULT
SecNum EntryID Value
0 0 10
0 1 10
1 0 20
If the EntryID changes with SecNum AND Value use this query:
SELECT SecNum,
ROW_NUMBER() OVER ( PARTITION BY Value,SecNum ORDER BY Value, SecNum ) - 1 AS EntryID,
Value
FROM #t
RESULT 2
SecNum EntryID Value
0 0 10
0 1 10
1 0 10
1 0 20
Your problem can be solved by using an instead of insert trigger
create trigger Trigger1 on T INSTEAD OF INSERT
as
begin
insert into T(SecNum,EntryID,Value)
select SecNum,
(select count(*) from T where SecNum = i.SecNum) as EntryID,
value
from inserted i
end