Create KeyValuePair<TKey, TValue> from columns in a single data row - sql-server

I have the following table in Sql Server that stores the permissions the Foreign Key UserId has to the Foreign Keyed BlogId. What I would like to do is write a query with Dapper that takes each column after BlogId and returns it as a KeyValuePair<TKey, TValue>.
CREATE TABLE [dbo].[UserPermission] (
[UserPermissionId] INT IDENTITY (1, 1) NOT NULL,
[BlogId] INT NOT NULL,
[UserId] INT NOT NULL,
[CanCreateNewPosts] BIT NOT NULL,
[CanEditExistingPosts] BIT NOT NULL,
[CanDeleteExistingPosts] BIT NOT NULL,
[CanPublishDraftPosts] BIT NOT NULL,
CONSTRAINT [PK_UserPermission] PRIMARY KEY CLUSTERED ([UserPermissionId] ASC),
CONSTRAINT [FK_UserPermission_Blog] FOREIGN KEY ([BlogId]) REFERENCES [dbo].[Blog] ([BlogId]),
CONSTRAINT [FK_UserPermission_User] FOREIGN KEY ([UserId]) REFERENCES [dbo].[User] ([UserId])
);
I would like to query via Dapper like this:
using (var connection = new SqlConnection(this.connectionString))
{
string sql = "SELECT * FROM [Permission] WHERE UserId = #UserId";
await connection.OpenAsync();
IEnumerable<KeyValuePair<string, bool>> results = await connection.QueryAsync(
sql,
new { UserId = this.userId });
}
var p1 = results.First();
var p2 = results.Skip(1).First();
In the above example, I would like p1 to result in a KeyValuePair with the Key being CanCreateNewPosts and the Value being the column value, either true or false. Same applies with p2, where the Key would be CanEditExistingPosts, with it's corresponding value.
The underlying need for this is to simplify transforming the record into a list of Claims in Identity, one claim per column.
I looked at splitOn:, to try and split after the UserId column but that doesn't seem like it's what I want. It would require n-generic arguments for each column I split. Ideally I'd like to add columns to this table in the future and my security/data/servicing layer just handles turning it into a Claim - letting me just focus on the Controller Action that needs to check for the claim. Having the query and Dapper return map the column-name/values into a collection of KeyValuePairs would facilitate that need for me.

You can use the SQL UNPIVOT operation to transform columns into column values. Here's an example for your particular case:
SELECT u.BlogId, u.permission
FROM UserPermissions up
UNPIVOT
(
permission for perms in (
CanCreateNewPosts,
CanEditExistingPosts,
CanDeleteExistingPosts,
CanPublishDraftPosts
)
) u;

Related

Update data when having UNIQUE Constraint

I am having a simple scores table for HTML5 game with columns: name, email and score. Email value should be a unique value, but when the same users play the game again to better their scores, the score should be updated for that user. Now it returns an error because of the unique value. How should I create a table that will update the data?
The table I have created so far:
CREATE TABLE `scores` (
`id` INT NOT NULL AUTO_INCREMENT,
`name` VARCHAR( 20 ) NOT NULL,
`email` VARCHAR( 320 ) NOT NULL UNIQUE,
`score` INT NOT NULL,
PRIMARY KEY ( `id` )
) ENGINE = InnoDB;
You could use an insert statement with an on duplicate key clause:
INSERT INTO scores (name, email, score)
VALUES ('some_name', 'some_email', 123) -- Values from your application
ON DUPLICATE KEY UPDATE
score = CASE VALUES(score) > score THEN VALUES(score) ELSE score END
It is not about your create table query, it is about your insert query.
Use a try catch method to update the unique value
try
{
// inserting the line
}
catch (exception)
{
// Drop the row with that unique value
// inserting the same line which you have added in "try" section
}

Ensure foreign key of a foreign key matches a base foreign key

Basically let's say I have a "Business" that owns postal codes that it services. Let's also suppose I have another relational table that sets up fees.
CREATE TABLE [dbo].[BusinessPostalCodes]
(
[BusinessPostalCodeId] INT IDENTITY (1, 1) NOT NULL,
[BusinessId] INT NOT NULL,
[PostalCode] VARCHAR (10) NOT NULL
)
CREATE TABLE [dbo].[BusinessPostalCodeFees]
(
[BusinessId] INT NOT NULL,
[BusinessProfileFeeTypeId] INT NOT NULL,
[BusinessPostalCodeId] INT NOT NULL,
[Fee] SMALLMONEY NULL
)
I want to know if it's possible to set up a foreign key (or something) on BusinessPostalCodeFees that ensures that the related BusinessId of BusinessPostalCodes is the same as the BusinessId of BusinessPostalCodeFees.
I realize that I can remove BusinessId entirely, but I would much rather keep this column and have a way of guaranteeing they will be the same. Is there anything I can do?
It sounds like (and correct me if I'm wrong) that you're trying to make sure that any entry into BusinessPostalCodeFees' BusinessId and BusinessPostalCodeId columns match an entry in the BusinessPostalCodes table. If that's the case, then yes, you can definitely have a foreign key that references a compound primary key.
However, if you need to keep the BusinessId, I'd recommend normalizing your tables a step further than you have. You'll end up with duplicate data as-is.
On a side note, I would recommend you don't use the money data types in SQL: See here.
In the end, Jeffrey's solution didn't quite work for my particular situation. Both columns in the relation have to be unique (like a composite key). Turns out the answer here (for me) is a Checked Constraint.
Create a function that you want to have the constraint pass or fail:
CREATE FUNCTION [dbo].[MatchingBusinessIdPostalCodeAndProfileFeeType]
(
#BusinessId int,
#BusinessPostalCodeId int,
#BusinessProfileFeeTypeId int
)
RETURNS BIT
AS
BEGIN
-- This works because BusinessPostalCodeId is a unique Id.
-- If businessId doesn't match, its filtered out.
DECLARE #pcCount AS INT
SET #pcCount = (SELECT COUNT(*)
FROM BusinessPostalCodes
WHERE BusinessPostalCodeId = #BusinessPostalCodeId AND
BusinessId = #BusinessId)
-- This works because BusinessProfileFeeTypeId is a unique Id.
-- If businessId doesn't match, its filtered out.
DECLARE #ftCount AS INT
SET #ftCount = (SELECT COUNT(*)
FROM BusinessProfileFeeTypes
WHERE BusinessProfileFeeTypeId = #BusinessProfileFeeTypeId AND
BusinessId = #BusinessId)
-- Both should have only one record
BEGIN IF (#pcCount = 1 AND #ftCount = 1)
RETURN 1
END
RETURN 0
END
Then just add it to your table:
CONSTRAINT [CK_BusinessPostalCodeFees_MatchingBusinessIdPostalCodeAndProfileFeeType]
CHECK (dbo.MatchingBusinessIdPostalCodeAndProfileFeeType(
BusinessId,
BusinessPostalCodeId,
BusinessProfileFeeTypeId) = 1)

Returning ID of new row when ID is uniqueidentifier

I have created a SQL Server table that uses uniqueidentifier as the primary key. I set the Default Value or Binding to newid(). (I would like to set the Identity Specification for this column, but that isn't supported for uniqueidentifier types.)
I'm then using ADO.NET to add a row to this table.
SqlComment command = new SqlCommand("INSERT INTO [User] (Name) VALUES (#name);
SELECT SCOPE_IDENTITY()", Connection);
command.Parameters.AddWithValue("#name", "Joe Smoe");
Guid userId = (Guid)command.ExecuteScalar();
However, the last line fails because ExecuteScaler() returns null. It appears that, since a uniqueidentifier cannot be the table's identity, SCOPE_IDENTITY() returns null (as does ##IDENTITY).
Okay, so is there another way to retrieve the newly added ID using ADO.NET?
SCOPE_IDENTITY() is only used for Identity value, for guid values you would need to use the OUTPUT clause with a table variable.
DECLARE #NewGuid TABLE(NewValue UNIQUEIDENTIFIER);
INSERT INTO [User] (Name)
OUTPUT inserted.pk_ColName INTO #NewGuid(NewValue)
VALUES (#name);
SELECT * FROM #NewGuid --<-- here you will have the new GUID Value
C# code would look something like....
string cmd = "DECLARE #NewGuid TABLE(NewValue UNIQUEIDENTIFIER);
INSERT INTO [User] (Name)
OUTPUT inserted.pk_ColName INTO #NewGuid(NewValue)
VALUES (#name);
SELECT #newID = NewValue FROM #NewGuid;"
SqlCommand command = new SqlCommand(cmd, Connection);
cmd.Parameters.AddWithValue("#name", "Joe Smoe");
cmd.Parameters.Add("#newID", SqlDbType.UniqueIdentifier).Direction = ParameterDirection.Output;
Guid userId = (Guid)cmd.ExecuteScalar();
Personally I would put the whole thing in a stored procedure.
Scope_Identity() focuses on an IDENTITY field, so it will never yield anything. You need to output from INSERTED instead. Even though this page is not focused on your particular problem, it should give you some clues:
Return ID on INSERT?
My normal direction is a stored procedure, but you can chain commands, as you have done. The stored procedure makes things a bit easier, as you can create an output parameter for the procedure, but outputting a value works fine.
EDITED to show specific example:
Assume the following table:
CREATE TABLE [dbo].[MyTable]
(
[Id] [uniqueidentifier] PRIMARY KEY NOT NULL DEFAULT NEWID(),
[Name] [varchar](50) NOT NULL,
)
The following program will output the new GUID created from NewID():
class Program
{
static void Main(string[] args)
{
var connString = ConfigurationManager.ConnectionStrings["testDB"].ToString();
var cmdString = "INSERT INTO MyTable (Name) OUTPUT Inserted.Id VALUES ('Name')";
var connection = new SqlConnection(connString);
var command = new SqlCommand(cmdString, connection);
Guid outputValue;
try
{
connection.Open();
//Convert to Guid here instead
Console.WriteLine(command.ExecuteScalar().ToString());
}
finally
{
connection.Dispose();
}
Console.Read();
}
}

Querying a jsonb array in postgres

Table:
CREATE TABLE appointment
(
id bigserial NOT NULL,
date_of_visit timestamp without time zone NOT NULL,
symptoms text[],
diseases text[],
lab_tests text[],
prescription_id bigint NOT NULL,
medicines jsonb,
CONSTRAINT appointment_pkey PRIMARY KEY (id),
CONSTRAINT appointment_prescription_id_fkey FOREIGN KEY (prescription_id)
REFERENCES prescription (id) MATCH SIMPLE
ON UPDATE NO ACTION ON DELETE NO ACTION
)
WITH (
OIDS=FALSE
);
Insert statement:
INSERT INTO appointment values(
1,
now(),
'{"abc","def","ghi"}',
'{"abc","def","ghi"}',
'{"abc","def","ghi"}',
1,
'[{"sku_id": 1, "company": "Magnafone"}, {"sku_id": 2, "company": "Magnafone"}]')
I am trying to query against a jsonb array type column in postgres. I had some solution in hand which is as below. Somehow it is not working The error is - Cannot extract elements from a scalar.
SELECT distinct(prescription_id)
FROM appointment
WHERE to_json(array(SELECT jsonb_array_elements(medicines) ->>'sku_id'))::jsonb ?|array['1']
LIMIT 2;
Update:
The query runs just fine. There was some unwanted value in the column for some other rows because of which it was not running.
There are rows in the table containing a scalar value in column medicines instead of array.
You should inspect and properly update the data. You can find these rows with this query:
select id, medicines
from appointment
where jsonb_typeof(medicines) <> 'array';
Alternatively, you can check the type of values in this column in the query:
select prescription_id
from (
select distinct on (prescription_id)
prescription_id,
case
when jsonb_typeof(medicines) = 'array' then jsonb_array_elements(medicines) ->>'sku_id'
else null
end as sku_id
from appointment
) alias
where sku_id = '1'
limit 2;
or simply exclude non-array values in where clause:
select prescription_id
from (
select distinct on (prescription_id)
prescription_id,
jsonb_array_elements(medicines) ->>'sku_id' as sku_id
from appointment
where jsonb_typeof(medicines) = 'array'
) alias
where sku_id = '1'
limit 2;

Simple While loop database search table

first of all: Thanks for all the great help I already received through finding answers to questions others posted.
I have a small and easy question for you:
I'm trying to randomly generate a number, but if it exists in the database table, it should keep generating a new numbers until it finds a unique number.
Help would be much appreciated!
$klantnr = rand(1,9);
$kn = mysql_num_rows(mysql_query('select username from users where id="'.$klantnr.'"'));
while($kn!=0){
$klantnr = rand(1,9);
}
echo $klantnr;
Create table with:
id integer primary key auto_increment
insert into users (id, username) values (NULL, 'name')
insert into users (username) values ('name')
MySQL will generate unique id for you
=== update
create table random_id (
id integer,
used tinyint,
primary key(id),
key idx_used(used)
);
for (0..9999) {
rand_id = get_random_id();
insert into random_id (id, used) values (rand_id, 0);
}
When need a random id, just fetch one from random_id table:
rand_id = select id from random_id where used = 0;
update random_id set used = 1 where id = :rand_id
use rand_id
Really it's pre generated random id, if you guarantee the users table's id only from random table, you doesn't need dealing with conflict ids :)

Resources